package main import ( "fmt" "strings" "unicode" tea "github.com/charmbracelet/bubbletea" lip "github.com/charmbracelet/lipgloss" ) func UI(dbg *Debugger) error { p := tea.NewProgram(initialModel(dbg)) //failed{0, 0, true}, tea.WithAltScreen()) 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 msgErr struct { err error } type msgUpdateStatus struct { status string } type debugger struct { width int height int dbg *Debugger hex HexView lastErr error bottomMsg string } func initialModel(dbg *Debugger) debugger { ret := debugger{ width: 0, height: 0, dbg: dbg, bottomMsg: "", } hex := NewHexView(128*1024, ret.writeByte) hex.AddrStyle = text hex.ZeroStyle = faintText hex.HexStyle = text ret.hex = hex return ret } func (m debugger) Init() tea.Cmd { return m.dumpMemory(0x200) } func staticMsg(msg tea.Msg) tea.Cmd { return func() tea.Msg { return msg } } func (m debugger) dumpMemory(count int) tea.Cmd { var ret []tea.Cmd for i := 0; i < count; i += 16 { ret = append(ret, func() tea.Msg { mem, err := m.dbg.Dump(i, 16) if err != nil { return msgErr{err} } return HexViewUpdateMem{i, mem} }) } return tea.Sequence(ret...) } func (m debugger) writeByte(addr int, val byte) tea.Cmd { return tea.Sequence( staticMsg(msgUpdateStatus{"Writing..."}), func() tea.Msg { if err := m.dbg.Write(addr, val); err != nil { return msgUpdateStatus{fmt.Sprintf("Write failed: %v", err)} } return tea.BatchMsg{ staticMsg(HexViewUpdateMem{addr, []byte{val}}), staticMsg(msgUpdateStatus{"Written!"}), } }, m.dumpMemory(0x200), ) } 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 case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "r": return m, m.dumpMemory(0x200) } case msgErr: 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 } func renderByte(b byte, format string) string { ret := fmt.Sprintf(format, b) if b == 0 { return faintText.Render(ret) } return text.Render(ret) } func renderBytes(bs []byte, format string, sep string) string { var s strings.Builder for i, b := range bs { s.WriteString(renderByte(b, format)) if i < len(bs)-1 { s.WriteString(faintText.Render(sep)) } } return s.String() } func dimZero(s string) string { allZero := true for _, r := range s { if !unicode.IsSpace(r) && r != '0' { allZero = false break } } if allZero { return faintText.Render(s) } return text.Render(s) }