gary/debugger/ui.go

178 lines
4.5 KiB
Go

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
}