package main import ( "fmt" "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, statusMsg: "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). BorderForeground(amber). BorderBackground(slate). Border(lip.NormalBorder(), false, false, false, false) faintText = text.Faint(true) box = lip.NewStyle(). Border(lip.NormalBorder()). BorderForeground(amber). BorderBackground(slate). Padding(1, 2) ) 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 statusMsg string } func (m debugger) Init() tea.Cmd { loads := m.readFullMemoryCmds() ret := make([]tea.Cmd, 0, (len(loads)*2)+1) for i, c := range loads { ret = append(ret, staticMsg(msgUpdateStatus{fmt.Sprintf("Loading block %d/%d", i+1, len(loads))}), c) } ret = append(ret, staticMsg(msgUpdateStatus{"Load complete."})) return tea.Sequence(ret...) } func (m debugger) readFullMemory() tea.Cmd { return tea.Sequence(m.readFullMemoryCmds()...) } func (m debugger) readFullMemoryCmds() []tea.Cmd { var ret []tea.Cmd for i := 0; i < m.mem.Len(); i += 32 * 1024 { ret = append(ret, m.readMemory(i, 32*1024)) } return ret } func (m debugger) readMemory(start, count int) tea.Cmd { return func() tea.Msg { if err := m.mem.Refresh(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) { 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 - box.GetVerticalFrameSize() - 1}) 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 "R": return m, m.readFullMemory() } case msgErr: m.lastErr = msg.err return m, nil case msgUpdateStatus: m.statusMsg = 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") } hex := box.Render(m.hex.View()) barText := text.Reverse(true) //.Height(1) topLeft := barText.Bold(true).PaddingLeft(1).Render("Addr: ") topLeft += barText.Render(fmt.Sprintf("0x%x (%d)", m.hex.SelectedAddr(), m.hex.SelectedAddr())) topRight := barText.PaddingRight(1).Render(m.statusMsg) title := barText.Padding(0, 1).Bold(true).Render("GARY Debugger") sideWidth := max(lip.Width(topLeft), lip.Width(topRight)) titleWidth := m.width - 2*sideWidth topLeft = barText.Width(sideWidth).Align(lip.Left).Render(topLeft) topRight = barText.Width(sideWidth).Align(lip.Right).Render(topRight) title = barText.Width(titleWidth).Align(lip.Center).Render(title) status := lip.JoinHorizontal(lip.Top, topLeft, title, topRight) all := lip.JoinVertical(lip.Center, status, hex) return all }