Stepping and showing execution point now work

This commit is contained in:
2026-03-28 17:18:26 +01:00
parent 41d0dd3b14
commit 587c8f9396
7 changed files with 184 additions and 88 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
app.log

View File

@@ -1,53 +1,27 @@
package main package main
import ( import (
// "fmt"
// "log/slog"
// "time"
"log/slog" "log/slog"
"os"
b "git.pablu.de/pablu/pybug/internal/bridge" b "git.pablu.de/pablu/pybug/internal/bridge"
"git.pablu.de/pablu/pybug/ui" "git.pablu.de/pablu/pybug/ui"
) )
func main() { func main() {
// slog.SetLogLoggerLevel(slog.LevelDebug) f, err := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
//
// fmt.Println("Started bridge")
//
// err = bridge.Breakpoint("test.py", 5)
// bridge.OnBreakpoint("test.py", 5, func() {
// locals, err := bridge.Locals()
// if err != nil {
// slog.Error("Encountered error on callback", "error", err)
// return
// }
//
// for key, val := range locals {
// slog.Info("found local variable", "key", key, "value", val)
// }
// })
//
// bridge.Continue()
//
// time.Sleep(5 * time.Second)
//
// bridge.Continue()
//
// err = bridge.Wait()
// if err != nil {
// panic(err)
// }
slog.SetLogLoggerLevel(slog.LevelError)
bridge := b.NewBridge("test.py")
err := bridge.Start()
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer f.Close()
handler := slog.NewTextHandler(f, &slog.HandlerOptions{
AddSource: true,
})
slog.SetDefault(slog.New(handler))
slog.SetLogLoggerLevel(slog.LevelDebug)
bridge := b.NewBridge("test.py")
err = ui.Run(bridge) err = ui.Run(bridge)
if err != nil { if err != nil {

View File

@@ -11,6 +11,14 @@ import (
"sync" "sync"
) )
var ErrNotRunning = errors.New("bridge not running")
var ErrAlreadyStarted = errors.New("bridge is already running")
type ExecutionPoint struct {
File string
Line int
}
type Bridge struct { type Bridge struct {
stdin io.Writer stdin io.Writer
stdout *bufio.Reader stdout *bufio.Reader
@@ -22,6 +30,9 @@ type Bridge struct {
outputLock *sync.RWMutex outputLock *sync.RWMutex
output []chan string output []chan string
executionStopLock *sync.RWMutex
executionStop []chan ExecutionPoint
registry map[string]chan string registry map[string]chan string
registryLock *sync.RWMutex registryLock *sync.RWMutex
@@ -29,18 +40,15 @@ type Bridge struct {
callbacksLock *sync.RWMutex callbacksLock *sync.RWMutex
callbacks map[string]map[int]func() callbacks map[string]map[int]func()
running bool
path string
} }
func NewBridge(path string) *Bridge { func NewBridge(path string) *Bridge {
cmd := exec.Command(
"python",
"-u",
"python/pybug_runtime.py",
path,
)
return &Bridge{ return &Bridge{
cmd: cmd, path: path,
input: make(chan string), input: make(chan string),
registry: make(map[string]chan string), registry: make(map[string]chan string),
registryLock: &sync.RWMutex{}, registryLock: &sync.RWMutex{},
@@ -51,16 +59,31 @@ func NewBridge(path string) *Bridge {
output: make([]chan string, 0), output: make([]chan string, 0),
outputLock: &sync.RWMutex{}, outputLock: &sync.RWMutex{},
executionStop: make([]chan ExecutionPoint, 0),
executionStopLock: &sync.RWMutex{},
running: false,
} }
} }
func (b *Bridge) Start() error { func (b *Bridge) Start() error {
if b.running {
return ErrAlreadyStarted
}
b.cmd = exec.Command(
"python",
"-u",
"python/pybug_runtime.py",
b.path,
)
var err error var err error
b.stdin, err = b.cmd.StdinPipe() b.stdin, err = b.cmd.StdinPipe()
if err != nil { if err != nil {
return err return err
} }
// b.cmd.Stdout = os.Stdout
reader, err := b.cmd.StdoutPipe() reader, err := b.cmd.StdoutPipe()
if err != nil { if err != nil {
@@ -73,6 +96,7 @@ func (b *Bridge) Start() error {
return err return err
} }
b.running = true
go b.readLoop() go b.readLoop()
go b.writeLoop() go b.writeLoop()
@@ -89,7 +113,21 @@ func (b *Bridge) Subscribe() chan string {
return c return c
} }
func (b *Bridge) SubscribeStopped() chan ExecutionPoint {
b.executionStopLock.Lock()
defer b.executionStopLock.Unlock()
c := make(chan ExecutionPoint)
b.executionStop = append(b.executionStop, c)
return c
}
func (b *Bridge) Locals() (map[string]any, error) { func (b *Bridge) Locals() (map[string]any, error) {
if !b.running {
return nil, ErrNotRunning
}
requestId, cmd := makeCommand(LocalsCommand, map[string]any{}) requestId, cmd := makeCommand(LocalsCommand, map[string]any{})
c := b.sendCommand(requestId, cmd) c := b.sendCommand(requestId, cmd)
@@ -110,8 +148,22 @@ func (b *Bridge) Locals() (map[string]any, error) {
return vars, nil return vars, nil
} }
func (b *Bridge) Step() error {
if !b.running {
return ErrNotRunning
}
_, cmd := makeCommand(StepCommand, nil)
b.sendCommandNoResponse(cmd)
return nil
}
func (b *Bridge) Breakpoint(file string, line int) error { func (b *Bridge) Breakpoint(file string, line int) error {
// Check if breakpoint already exists here // Check if breakpoint already exists here
if !b.running {
return ErrNotRunning
}
requestId, cmd := makeCommand(BreakCommand, map[string]any{ requestId, cmd := makeCommand(BreakCommand, map[string]any{
"file": file, "file": file,
@@ -137,10 +189,15 @@ func (b *Bridge) Breakpoint(file string, line int) error {
return nil return nil
} }
func (b *Bridge) Continue() { func (b *Bridge) Continue() error {
if !b.running {
return ErrNotRunning
}
_, cmd := makeCommand(ContinueCommand, map[string]any{}) _, cmd := makeCommand(ContinueCommand, map[string]any{})
b.sendCommandNoResponse(cmd) b.sendCommandNoResponse(cmd)
return nil
} }
func (b *Bridge) sendCommandNoResponse(command string) { func (b *Bridge) sendCommandNoResponse(command string) {
@@ -165,6 +222,10 @@ func (b *Bridge) writeLoop() {
for { for {
cmd := <-b.input cmd := <-b.input
if !b.running {
return
}
slog.Info("Received command", "cmd", cmd) slog.Info("Received command", "cmd", cmd)
_, err := b.stdin.Write([]byte(cmd + "\n")) _, err := b.stdin.Write([]byte(cmd + "\n"))
@@ -186,6 +247,7 @@ func (b *Bridge) readLoop() {
slog.Error("Error occured while reading from stdout", "error", err) slog.Error("Error occured while reading from stdout", "error", err)
continue continue
} else if errors.Is(err, io.EOF) { } else if errors.Is(err, io.EOF) {
b.running = false
return return
} }
@@ -212,28 +274,64 @@ func (b *Bridge) readLoop() {
c <- line c <- line
} else { } else {
// TODO: set to stopped b.handleStopped(msg)
if event, ok := msg["event"]; !ok || event != "stopped" {
slog.Warn("received unkown event", "msg", msg)
}
file := msg["file"].(string)
line, ok := toInt(msg["line"])
if !ok {
slog.Error("could not convert line to int", "line", msg["line"])
}
slog.Info("received stopped event")
b.callbacksLock.RLock()
if callback, ok := b.callbacks[file][line]; ok {
slog.Info("found callback, now running", "file", file, "line", line)
go callback()
}
b.callbacksLock.RUnlock()
} }
} }
} }
func (b *Bridge) handleStopped(msg map[string]any) error {
// TODO: set to stopped
if event, ok := msg["event"]; !ok || event != "stopped" {
slog.Warn("received unkown event", "msg", msg)
return errors.New("unknown event encountered")
}
file := msg["file"].(string)
line, ok := toInt(msg["line"])
if !ok {
slog.Error("could not convert line to int", "line", msg["line"])
return errors.New("could not convert line to int")
}
slog.Info("received stopped event")
b.callbacksLock.RLock()
defer b.callbacksLock.RUnlock()
if callback, ok := b.callbacks[file][line]; ok {
slog.Info("found callback, now running", "file", file, "line", line)
go callback()
}
b.executionStopLock.RLock()
defer b.executionStopLock.RUnlock()
for _, c := range b.executionStop {
c <- ExecutionPoint{
File: file,
Line: line,
}
}
return nil
}
func (b *Bridge) OnBreakpoint(file string, line int, callback func()) {
b.callbacksLock.Lock()
defer b.callbacksLock.Unlock()
if f, ok := b.callbacks[file]; ok {
f[line] = callback
} else {
b.callbacks[file] = map[int]func(){
line: callback,
}
}
}
func (b *Bridge) Wait() error {
return b.cmd.Wait()
}
func toInt(v any) (int, bool) { func toInt(v any) (int, bool) {
switch x := v.(type) { switch x := v.(type) {
case int: case int:
@@ -254,20 +352,3 @@ func toInt(v any) (int, bool) {
return 0, false return 0, false
} }
} }
func (b *Bridge) OnBreakpoint(file string, line int, callback func()) {
b.callbacksLock.Lock()
defer b.callbacksLock.Unlock()
if f, ok := b.callbacks[file]; ok {
f[line] = callback
} else {
b.callbacks[file] = map[int]func(){
line: callback,
}
}
}
func (b *Bridge) Wait() error {
return b.cmd.Wait()
}

View File

@@ -12,6 +12,7 @@ const (
ContinueCommand CommandType = "continue" ContinueCommand CommandType = "continue"
BreakCommand CommandType = "break" BreakCommand CommandType = "break"
LocalsCommand CommandType = "locals" LocalsCommand CommandType = "locals"
StepCommand CommandType = "step"
) )
func makeCommand(cmd CommandType, values map[string]any) (requestId string, request string) { func makeCommand(cmd CommandType, values map[string]any) (requestId string, request string) {

View File

@@ -16,8 +16,11 @@ type Model struct {
width int width int
height int height int
bridge *bridge.Bridge bridge *bridge.Bridge
listenBridge chan string listenBridge chan string
listenBridgeExecutionsStopped chan bridge.ExecutionPoint
currExecutionPoint bridge.ExecutionPoint
messages []string messages []string
stdoutOutput viewport.Model stdoutOutput viewport.Model
@@ -29,6 +32,7 @@ type Model struct {
func NewModel(b *bridge.Bridge, file string, text string) Model { func NewModel(b *bridge.Bridge, file string, text string) Model {
c := b.Subscribe() c := b.Subscribe()
c2 := b.SubscribeStopped()
stdoutOutput := viewport.New(0, 0) stdoutOutput := viewport.New(0, 0)
codeViewer := viewport.New(0, 0) codeViewer := viewport.New(0, 0)
@@ -38,9 +42,14 @@ func NewModel(b *bridge.Bridge, file string, text string) Model {
text: text, text: text,
textLines: len(strings.Split(text, "\n")), textLines: len(strings.Split(text, "\n")),
bridge: b, bridge: b,
listenBridge: c, listenBridge: c,
listenBridgeExecutionsStopped: c2,
currExecutionPoint: bridge.ExecutionPoint{
File: file,
Line: 0,
},
breakpoints: make(map[string][]int), breakpoints: make(map[string][]int),
messages: make([]string, 0), messages: make([]string, 0),
@@ -57,8 +66,19 @@ func ListenBridge(ch <-chan string) tea.Cmd {
} }
} }
func ListenBridgeExecutionsStopped(ch <-chan bridge.ExecutionPoint) tea.Cmd {
return func() tea.Msg {
msg := <-ch
return ExecutionStoppedMsg(msg)
}
}
type ExecutionStoppedMsg bridge.ExecutionPoint
type StdoutMsg string type StdoutMsg string
func (m Model) Init() tea.Cmd { func (m Model) Init() tea.Cmd {
return ListenBridge(m.listenBridge) return tea.Batch(
ListenBridge(m.listenBridge),
ListenBridgeExecutionsStopped(m.listenBridgeExecutionsStopped),
)
} }

View File

@@ -1,8 +1,10 @@
package ui package ui
import ( import (
"log/slog"
"strings" "strings"
"git.pablu.de/pablu/pybug/internal/bridge"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
@@ -17,6 +19,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.stdoutOutput.SetContent(strings.Join(m.messages, "")) m.stdoutOutput.SetContent(strings.Join(m.messages, ""))
m.stdoutOutput.GotoBottom() m.stdoutOutput.GotoBottom()
return m, ListenBridge(m.listenBridge) return m, ListenBridge(m.listenBridge)
case ExecutionStoppedMsg:
m.currExecutionPoint = bridge.ExecutionPoint(msg)
return m, ListenBridgeExecutionsStopped(m.listenBridgeExecutionsStopped)
} }
return m, nil return m, nil
@@ -61,8 +66,17 @@ func (m Model) HandleKeyMsg(key tea.KeyMsg) (tea.Model, tea.Cmd) {
lineNumber, lineNumber,
} }
} }
case "s":
m.bridge.Step()
case "c": case "c":
m.bridge.Continue() 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 m, nil return m, nil

View File

@@ -30,15 +30,20 @@ func (m Model) View() string {
for i, line := range lines { for i, line := range lines {
breakpoint := " " breakpoint := " "
cursor := " " cursor := " "
executor := " "
if slices.Contains(breakpoints, i+1) { if slices.Contains(breakpoints, i+1) {
breakpoint = "O" breakpoint = "O"
} }
if i+1 == m.currExecutionPoint.Line {
executor = "!"
}
if i == m.cursor { if i == m.cursor {
cursor = ">" cursor = ">"
} }
fmt.Fprintf(&out, "%-4d%s%s %s\n", i+1, breakpoint, cursor, line) fmt.Fprintf(&out, "%-4d%s%s%s %s\n", i+1, breakpoint, executor, cursor, line)
} }
m.codeViewer.SetContent(out.String()) m.codeViewer.SetContent(out.String())