202 lines
4.8 KiB
Go
202 lines
4.8 KiB
Go
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)
|
|
}
|