working codeviewer
This commit is contained in:
123
ui/codeviewer/model.go
Normal file
123
ui/codeviewer/model.go
Normal 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()
|
||||||
|
}
|
||||||
13
ui/model.go
13
ui/model.go
@@ -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,
|
||||||
|
|||||||
36
ui/update.go
36
ui/update.go
@@ -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":
|
||||||
|
|||||||
38
ui/view.go
38
ui/view.go
@@ -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).
|
||||||
|
|||||||
Reference in New Issue
Block a user