package main import ( "fmt" "log" "git.sentinel65x.com/dave/gary/debugger/memory" tea "github.com/charmbracelet/bubbletea" lip "github.com/charmbracelet/lipgloss" ) func UI(mem *memory.Memory) error { initial := debugger{ width: 0, height: 0, mem: mem, bottomMsg: "Loading...", } initial.hex = NewHexView(mem, initial.commitWrites) initial.hex.AddrStyle = text initial.hex.ZeroStyle = faintText initial.hex.HexStyle = text p := tea.NewProgram(initial) if _, err := p.Run(); err != nil { return err } return nil } var ( amber = lip.Color("#ffb00") slate = lip.Color("235") text = lip.NewStyle().Foreground(amber).Background(slate) faintText = text.Faint(true) box = text.Border(lip.NormalBorder(), true, true, true, true). BorderForeground(amber). BorderBackground(slate) ) type msgMemoryChanged struct { start, count int } type msgErr struct { err error } type msgUpdateStatus struct { status string } type debugger struct { width int height int mem *memory.Memory hex HexView lastErr error bottomMsg string } func (m debugger) Init() tea.Cmd { return tea.Sequence( m.readFullMemory(), staticMsg(msgUpdateStatus{"Initial load complete."}), ) } func (m debugger) readFullMemory() tea.Cmd { var ret []tea.Cmd for i := 0; i < m.mem.Len(); i += 128 { ret = append(ret, m.readMemory(i, 128)) } return tea.Sequence(ret...) } func (m debugger) readMemory(start, count int) tea.Cmd { return func() tea.Msg { if err := m.mem.Load(start, count); err != nil { return msgErr{fmt.Errorf("loading region 0x%x+%x: %w", start, count, err)} } return msgMemoryChanged{start, count} } } func staticMsg(msg tea.Msg) tea.Cmd { return func() tea.Msg { return msg } } func (m debugger) commitWrites() tea.Cmd { return tea.Sequence( staticMsg(msgUpdateStatus{"Writing..."}), func() tea.Msg { if err := m.mem.Commit(); err != nil { return msgUpdateStatus{fmt.Sprintf("Write failed: %v", err)} } return msgUpdateStatus{"Writes complete."} }, m.readFullMemory(), ) } func (m debugger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { log.Printf("msv: %#v", msg) switch msg := msg.(type) { case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height var cmd tea.Cmd m.hex, cmd = m.hex.Update(HexViewSetHeight{m.height - 6}) // TODO: make less shit return m, cmd case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "r": start, end := m.hex.VisibleBytes() return m, m.readMemory(start, end-start) } case msgErr: log.Print(msg.err) m.lastErr = msg.err return m, nil case msgUpdateStatus: m.bottomMsg = msg.status } var cmd tea.Cmd m.hex, cmd = m.hex.Update(msg) return m, cmd } // func (m debugger) values(addr int) string { // var v [4]byte // copy(v[:], m.vram[addr:]) // var out strings.Builder // out.WriteString(text.Render("Bin: ")) // out.WriteString(renderBytes(v[:], "%08b", "_")) // out.WriteByte('\n') // out.WriteString(text.Render("Hex: ")) // out.WriteString(renderBytes(v[:], "%02x", "_")) // out.WriteByte('\n') // out.WriteString(text.Render("Dec: ")) // out.WriteString(faintText.Render("[")) // out.WriteString(dimZero(fmt.Sprintf("%3d", v[0]))) // out.WriteString(faintText.Render("] [")) // out.WriteString(dimZero(fmt.Sprintf("%5d", binary.BigEndian.Uint16(v[:])))) // out.WriteString(faintText.Render("] [")) // out.WriteString(dimZero(fmt.Sprintf("%10d", binary.BigEndian.Uint32(v[:])))) // out.WriteString(faintText.Render("]")) // return out.String() // } func (m debugger) View() string { if m.width < 80 || m.height < 20 { return lip.Place(m.width, m.height, lip.Center, lip.Center, "Please embiggen your terminal") } statusBar := text.Width(m.width).Height(1).Align(lip.Center) topSegment := statusBar.Width(m.width / 3).Reverse(true) hexBox := lip.NewStyle().Border(lip.NormalBorder()).BorderForeground(amber).BorderBackground(slate).Padding(1, 2) hexHeight := m.height - 2 - hexBox.GetVerticalFrameSize() hex := hexBox.Render(m.hex.View(hexHeight)) topLeft := text.Reverse(true).Bold(true).Render("Addr: ") + text.Reverse(true).Render(fmt.Sprintf("0x%-5x", m.hex.SelectedAddr())) topLeft = topSegment.Padding(0, 1).Align(lip.Left).Render(topLeft) topMid := topSegment.Bold(true).Render("GARY Debugger") topRight := topSegment.Render("") topStatus := lip.JoinHorizontal(lip.Top, topLeft, topMid, topRight) bottomStatus := statusBar.Render(m.bottomMsg) top := lip.JoinVertical(lip.Center, topStatus, hex, bottomStatus) return top }