Stepping and showing execution point now work
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
app.log
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,26 +274,62 @@ func (b *Bridge) readLoop() {
|
|||||||
|
|
||||||
c <- line
|
c <- line
|
||||||
} else {
|
} else {
|
||||||
|
b.handleStopped(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bridge) handleStopped(msg map[string]any) error {
|
||||||
// TODO: set to stopped
|
// TODO: set to stopped
|
||||||
if event, ok := msg["event"]; !ok || event != "stopped" {
|
if event, ok := msg["event"]; !ok || event != "stopped" {
|
||||||
slog.Warn("received unkown event", "msg", msg)
|
slog.Warn("received unkown event", "msg", msg)
|
||||||
|
return errors.New("unknown event encountered")
|
||||||
}
|
}
|
||||||
|
|
||||||
file := msg["file"].(string)
|
file := msg["file"].(string)
|
||||||
line, ok := toInt(msg["line"])
|
line, ok := toInt(msg["line"])
|
||||||
if !ok {
|
if !ok {
|
||||||
slog.Error("could not convert line to int", "line", msg["line"])
|
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")
|
slog.Info("received stopped event")
|
||||||
b.callbacksLock.RLock()
|
b.callbacksLock.RLock()
|
||||||
|
defer b.callbacksLock.RUnlock()
|
||||||
|
|
||||||
if callback, ok := b.callbacks[file][line]; ok {
|
if callback, ok := b.callbacks[file][line]; ok {
|
||||||
slog.Info("found callback, now running", "file", file, "line", line)
|
slog.Info("found callback, now running", "file", file, "line", line)
|
||||||
go callback()
|
go callback()
|
||||||
}
|
}
|
||||||
b.callbacksLock.RUnlock()
|
|
||||||
|
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) {
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
22
ui/model.go
22
ui/model.go
@@ -18,6 +18,9 @@ type Model struct {
|
|||||||
|
|
||||||
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)
|
||||||
@@ -40,7 +44,12 @@ func NewModel(b *bridge.Bridge, file string, text string) Model {
|
|||||||
|
|
||||||
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),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
14
ui/update.go
14
ui/update.go
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user