diff --git a/ui/codeviewer/model.go b/ui/codeviewer/model.go new file mode 100644 index 0000000..58f3ac3 --- /dev/null +++ b/ui/codeviewer/model.go @@ -0,0 +1,123 @@ +package codeviewer + +import ( + "bytes" + "fmt" + "strings" + + "github.com/alecthomas/chroma/v2/formatters" + "github.com/alecthomas/chroma/v2/lexers" + "github.com/alecthomas/chroma/v2/styles" + tea "github.com/charmbracelet/bubbletea" +) + +type CodeViewer struct { + lines []string + + Width, Height int + cursor int + offset int +} + +func NewCodeViewer(text string) CodeViewer { + cv := CodeViewer{ + Width: 0, + Height: 0, + cursor: 0, + offset: 0, + } + + return cv.colorize(text) +} + +func (c CodeViewer) Init() tea.Cmd { + return nil +} + +func (c CodeViewer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + return c.handleKeyMsg(msg) + // case tea.WindowSizeMsg: + // return c.UpdateWindowSize(msg) + } + + return c, nil +} + +func (c CodeViewer) handleKeyMsg(key tea.KeyMsg) (tea.Model, tea.Cmd) { + switch key.String() { + case "k": + c.cursor = max(0, c.cursor-1) + topThreshold := c.offset + int(float64(c.Height)*0.1) + if c.cursor < topThreshold && c.offset > 0 { + c.offset -= 1 + } + case "j": + c.cursor = min(len(c.lines)-1, c.cursor+1) + bottomThreshold := c.offset + int(float64(c.Height)*0.9) + if c.cursor > bottomThreshold && c.offset < len(c.lines) { + c.offset += 1 + } + + // case "b": + // lineNumber := c.cursor + 1 + // + // c.bridge.Breakpoint(m.currentFile, lineNumber) + // if file, ok := m.breakpoints[m.currentFile]; ok { + // m.breakpoints[m.currentFile] = append(file, lineNumber) + // } else { + // m.breakpoints[m.currentFile] = []int{ + // lineNumber, + // } + // } + // case "s": + // m.bridge.Step() + // case "c": + // m.bridge.Continue() + // case "r": + // m.messages = make([]string, 0) + // m.stdoutOutput.SetContent("") + // err := m.bridge.Start() + // if err != nil { + // slog.Error("could not start brige", "error", err) + // } + } + + return c, nil +} + +func (c CodeViewer) colorize(text string) CodeViewer { + var buf bytes.Buffer + lexer := lexers.Get("python") + style := styles.Get("monokai") + formatter := formatters.Get("terminal16m") + + iterator, _ := lexer.Tokenise(nil, text) + formatter.Format(&buf, style, iterator) + + c.lines = strings.Split(buf.String(), "\n") + return c +} + +func (c CodeViewer) View() string { + var out strings.Builder + + lines := c.lines[max(0, c.offset):min(c.offset+c.Height, len(c.lines))] + for i, line := range lines { + breakpoint := " " + cursor := " " + executor := " " + if c.offset+i == c.cursor { + cursor = ">" + } + fmt.Fprintf(&out, "%-4d%s%s%s %s\n", c.offset+i+1, breakpoint, executor, cursor, line) + } + if len(lines)-c.offset < c.Height { + for range c.Height - len(lines) { + fmt.Fprintf(&out, "\n") + } + } + + return out.String() +} diff --git a/ui/model.go b/ui/model.go index eb72a0e..b6d0d8d 100644 --- a/ui/model.go +++ b/ui/model.go @@ -1,17 +1,14 @@ package ui import ( - "strings" - "git.pablu.de/pablu/pybug/internal/bridge" + "git.pablu.de/pablu/pybug/ui/codeviewer" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" ) type Model struct { currentFile string - text string - textLines int width int height int @@ -25,9 +22,8 @@ type Model struct { messages []string stdoutOutput viewport.Model - codeViewer viewport.Model + codeViewer codeviewer.CodeViewer localsViewer viewport.Model - cursor int breakpoints map[string][]int } @@ -37,13 +33,11 @@ func NewModel(b *bridge.Bridge, file string, text string) Model { c2 := b.SubscribeStopped() stdoutOutput := viewport.New(0, 0) - codeViewer := viewport.New(0, 0) + codeViewer := codeviewer.NewCodeViewer(text) localsViewer := viewport.New(0, 0) return Model{ currentFile: file, - text: text, - textLines: len(strings.Split(text, "\n")), bridge: b, listenBridge: c, @@ -57,7 +51,6 @@ func NewModel(b *bridge.Bridge, file string, text string) Model { breakpoints: make(map[string][]int), messages: make([]string, 0), - cursor: 0, codeViewer: codeViewer, stdoutOutput: stdoutOutput, localsViewer: localsViewer, diff --git a/ui/update.go b/ui/update.go index 8e5e81c..52cdc67 100644 --- a/ui/update.go +++ b/ui/update.go @@ -5,10 +5,14 @@ import ( "strings" "git.pablu.de/pablu/pybug/internal/bridge" + "git.pablu.de/pablu/pybug/ui/codeviewer" tea "github.com/charmbracelet/bubbletea" ) func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + updatedCv, cmd := m.codeViewer.Update(msg) + m.codeViewer = updatedCv.(codeviewer.CodeViewer) + switch msg := msg.(type) { case tea.KeyMsg: return m.HandleKeyMsg(msg) @@ -27,7 +31,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.localsViewer.SetContent(strings.Join(flattenDict(m.currLocals, 0), "\n")) } - return m, nil + return m, cmd } func (m Model) GetLocals() tea.Cmd { @@ -50,7 +54,7 @@ func (m Model) UpdateWindowSize(msg tea.WindowSizeMsg) (tea.Model, tea.Cmd) { outputHeight := msg.Height - editorHeight - 4 m.codeViewer.Width = msg.Width - m.codeViewer.Height = editorHeight + m.codeViewer.Height = editorHeight - 2 m.stdoutOutput.Width = msg.Width / 2 m.stdoutOutput.Height = outputHeight @@ -67,23 +71,17 @@ func (m Model) HandleKeyMsg(key tea.KeyMsg) (tea.Model, tea.Cmd) { switch key.String() { case "q", "ctrl+c": return m, tea.Quit - case "k": - m.codeViewer.ScrollUp(1) - m.cursor = max(0, m.cursor-1) - case "j": - m.codeViewer.ScrollDown(1) - m.cursor = min(m.textLines-1, m.cursor+1) - case "b": - lineNumber := m.cursor + 1 - - m.bridge.Breakpoint(m.currentFile, lineNumber) - if file, ok := m.breakpoints[m.currentFile]; ok { - m.breakpoints[m.currentFile] = append(file, lineNumber) - } else { - m.breakpoints[m.currentFile] = []int{ - lineNumber, - } - } + // case "b": + // lineNumber := m.cursor + 1 + // + // m.bridge.Breakpoint(m.currentFile, lineNumber) + // if file, ok := m.breakpoints[m.currentFile]; ok { + // m.breakpoints[m.currentFile] = append(file, lineNumber) + // } else { + // m.breakpoints[m.currentFile] = []int{ + // lineNumber, + // } + // } case "s": m.bridge.Step() case "c": diff --git a/ui/view.go b/ui/view.go index 384b11b..7abade9 100644 --- a/ui/view.go +++ b/ui/view.go @@ -1,14 +1,9 @@ package ui import ( - "bytes" "fmt" - "slices" "strings" - "github.com/alecthomas/chroma/v2/formatters" - "github.com/alecthomas/chroma/v2/lexers" - "github.com/alecthomas/chroma/v2/styles" "github.com/charmbracelet/lipgloss" ) @@ -30,39 +25,6 @@ func flattenDict(m map[string]interface{}, indent int) []string { } func (m Model) View() string { - var buf bytes.Buffer - lexer := lexers.Get("python") - style := styles.Get("monokai") - formatter := formatters.Get("terminal16m") - - iterator, _ := lexer.Tokenise(nil, m.text) - formatter.Format(&buf, style, iterator) - - var out strings.Builder - - lines := strings.Split(buf.String(), "\n") - breakpoints := m.breakpoints[m.currentFile] - for i, line := range lines { - breakpoint := " " - cursor := " " - executor := " " - - if slices.Contains(breakpoints, i+1) { - breakpoint = "O" - } - - if i+1 == m.currExecutionPoint.Line { - executor = "!" - } - - if i == m.cursor { - cursor = ">" - } - fmt.Fprintf(&out, "%-4d%s%s%s %s\n", i+1, breakpoint, executor, cursor, line) - } - - m.codeViewer.SetContent(out.String()) - hFrame, wFrame := panelStyle.GetFrameSize() topPanel := panelStyle. Height(m.codeViewer.Height - hFrame).