working codeviewer

This commit is contained in:
2026-03-28 19:24:17 +01:00
parent d254d5cfd0
commit 387804dbd0
4 changed files with 143 additions and 67 deletions

123
ui/codeviewer/model.go Normal file
View File

@@ -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()
}

View File

@@ -1,17 +1,14 @@
package ui package ui
import ( import (
"strings"
"git.pablu.de/pablu/pybug/internal/bridge" "git.pablu.de/pablu/pybug/internal/bridge"
"git.pablu.de/pablu/pybug/ui/codeviewer"
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
type Model struct { type Model struct {
currentFile string currentFile string
text string
textLines int
width int width int
height int height int
@@ -25,9 +22,8 @@ type Model struct {
messages []string messages []string
stdoutOutput viewport.Model stdoutOutput viewport.Model
codeViewer viewport.Model codeViewer codeviewer.CodeViewer
localsViewer viewport.Model localsViewer viewport.Model
cursor int
breakpoints map[string][]int breakpoints map[string][]int
} }
@@ -37,13 +33,11 @@ func NewModel(b *bridge.Bridge, file string, text string) Model {
c2 := b.SubscribeStopped() c2 := b.SubscribeStopped()
stdoutOutput := viewport.New(0, 0) stdoutOutput := viewport.New(0, 0)
codeViewer := viewport.New(0, 0) codeViewer := codeviewer.NewCodeViewer(text)
localsViewer := viewport.New(0, 0) localsViewer := viewport.New(0, 0)
return Model{ return Model{
currentFile: file, currentFile: file,
text: text,
textLines: len(strings.Split(text, "\n")),
bridge: b, bridge: b,
listenBridge: c, listenBridge: c,
@@ -57,7 +51,6 @@ func NewModel(b *bridge.Bridge, file string, text string) Model {
breakpoints: make(map[string][]int), breakpoints: make(map[string][]int),
messages: make([]string, 0), messages: make([]string, 0),
cursor: 0,
codeViewer: codeViewer, codeViewer: codeViewer,
stdoutOutput: stdoutOutput, stdoutOutput: stdoutOutput,
localsViewer: localsViewer, localsViewer: localsViewer,

View File

@@ -5,10 +5,14 @@ import (
"strings" "strings"
"git.pablu.de/pablu/pybug/internal/bridge" "git.pablu.de/pablu/pybug/internal/bridge"
"git.pablu.de/pablu/pybug/ui/codeviewer"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 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) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
return m.HandleKeyMsg(msg) 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")) m.localsViewer.SetContent(strings.Join(flattenDict(m.currLocals, 0), "\n"))
} }
return m, nil return m, cmd
} }
func (m Model) GetLocals() tea.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 outputHeight := msg.Height - editorHeight - 4
m.codeViewer.Width = msg.Width m.codeViewer.Width = msg.Width
m.codeViewer.Height = editorHeight m.codeViewer.Height = editorHeight - 2
m.stdoutOutput.Width = msg.Width / 2 m.stdoutOutput.Width = msg.Width / 2
m.stdoutOutput.Height = outputHeight m.stdoutOutput.Height = outputHeight
@@ -67,23 +71,17 @@ func (m Model) HandleKeyMsg(key tea.KeyMsg) (tea.Model, tea.Cmd) {
switch key.String() { switch key.String() {
case "q", "ctrl+c": case "q", "ctrl+c":
return m, tea.Quit return m, tea.Quit
case "k": // case "b":
m.codeViewer.ScrollUp(1) // lineNumber := m.cursor + 1
m.cursor = max(0, m.cursor-1) //
case "j": // m.bridge.Breakpoint(m.currentFile, lineNumber)
m.codeViewer.ScrollDown(1) // if file, ok := m.breakpoints[m.currentFile]; ok {
m.cursor = min(m.textLines-1, m.cursor+1) // m.breakpoints[m.currentFile] = append(file, lineNumber)
case "b": // } else {
lineNumber := m.cursor + 1 // m.breakpoints[m.currentFile] = []int{
// lineNumber,
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": case "s":
m.bridge.Step() m.bridge.Step()
case "c": case "c":

View File

@@ -1,14 +1,9 @@
package ui package ui
import ( import (
"bytes"
"fmt" "fmt"
"slices"
"strings" "strings"
"github.com/alecthomas/chroma/v2/formatters"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
) )
@@ -30,39 +25,6 @@ func flattenDict(m map[string]interface{}, indent int) []string {
} }
func (m Model) View() 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() hFrame, wFrame := panelStyle.GetFrameSize()
topPanel := panelStyle. topPanel := panelStyle.
Height(m.codeViewer.Height - hFrame). Height(m.codeViewer.Height - hFrame).