z_mirror/pesticide
git clone https://git.raiguard.me/z_mirror/pesticide
d788097
trunk
1package ui 2 3import ( 4"errors" 5"fmt" 6"os" 7"strings" 8 9"codeberg.org/raiguard/pesticide/adapter" 10 tea"github.com/charmbracelet/bubbletea" 11"github.com/google/go-dap" 12) 13 14func (m * Model )handleAdapterMessage (msg adapter.Msg ) tea.Cmd { 15if msg .ID == "" || msg .Msg == nil { 16return nil // The adapter was quit 17 } 18var cmd tea.Cmd 19a ,ok := m .adapters [msg .ID ] 20if ! ok { 21cmd = tea .Println ("Received message for nonexistent adapter" ) 22 } 23switch msg := msg .Msg .(type ) { 24case dap.Message : 25cmd = m .handleDAPMessage (a ,msg ) 26case tea.Cmd : 27cmd = msg 28 } 29return tea .Batch (cmd ,func () tea.Msg {return a .Receive () }) 30} 31 32func (m * Model )handleDAPMessage (a * adapter.Adapter ,msg dap.Message ) tea.Cmd { 33switch msg := msg .(type ) { 34case dap.ResponseMessage : 35ctx ,ok := a .PendingRequests [msg .GetResponse ().RequestSeq ] 36if ! ok { 37return tea .Println ("Received a response to a non-existent request" ) 38 } 39delete (a .PendingRequests ,msg .GetResponse ().RequestSeq ) 40// TODO: Handle error responses 41switch msg := msg .(type ) { 42case * dap.InitializeResponse : 43return m .onInitializeResponse (a ,msg ) 44case * dap.StackTraceResponse : 45return m .onStackTraceResponse (a ,msg ,ctx .(* dap.StackTraceRequest )) 46case * dap.EvaluateResponse : 47return m .onEvaluateResponse (msg ) 48 } 49case dap.EventMessage : 50switch msg := msg .(type ) { 51case * dap.ContinuedEvent : 52a .State = adapter .Running 53case * dap.InitializedEvent : 54return m .onInitializedEvent (a ,msg ) 55case * dap.TerminatedEvent : 56a .Shutdown () 57delete (m .adapters ,a .ID ) 58if m .focusedAdapter == a { 59m .focusedAdapter = nil 60// TODO: Focus a different adapter? Decide on UX for this. 61 } 62case * dap.StoppedEvent : 63return m .onStoppedEvent (a ,msg ) 64case * dap.OutputEvent : 65return m .onOutputEvent (a ,msg ) 66 } 67 } 68return nil 69} 70 71func (m * Model )onInitializeResponse (a * adapter.Adapter ,res * dap.InitializeResponse ) tea.Cmd { 72a .Capabilities = res .Body 73a .Launch () 74return nil 75} 76 77func (m * Model )onOutputEvent (a * adapter.Adapter ,ev * dap.OutputEvent ) tea.Cmd { 78return tea .Println (strings .TrimSpace (ev .Body .Output )) 79} 80 81func (m * Model )onStoppedEvent (a * adapter.Adapter ,event * dap.StoppedEvent ) tea.Cmd { 82a .State = adapter .Stopped 83a .FocusedThread = event .Body .ThreadId 84a .Send (& dap.StackTraceRequest { 85Request :a .NewRequest ("stackTrace" ), 86Arguments : dap.StackTraceArguments {ThreadId :event .Body .ThreadId }, 87 }) 88if event .Body .Reason == "step" { 89return nil 90 } 91return tea .Println (a .ID ," stopped: " ,event .Body .Reason ,": " ,event .Body .Text ) 92} 93 94func (m * Model )onInitializedEvent (a * adapter.Adapter ,ev * dap.InitializedEvent ) tea.Cmd { 95a .State = adapter .Running 96m .sendSetBreakpointsRequest (a ) 97if a .Capabilities .SupportsConfigurationDoneRequest { 98a .Send (& dap.ConfigurationDoneRequest { 99Request :a .NewRequest ("configurationDone" ), 100Arguments :& dap.ConfigurationDoneArguments {}, 101 }) 102 } 103return nil 104} 105 106func (m * Model )sendSetBreakpointsRequest (a * adapter.Adapter ) { 107for filename ,breakpoints := range a .Breakpoints { 108a .Send (& dap.SetBreakpointsRequest { 109Request :a .NewRequest ("setBreakpoints" ), 110Arguments : dap.SetBreakpointsArguments { 111Source : dap.Source { 112Name :filename , 113Path :filename , 114 }, 115Breakpoints :breakpoints , 116 }, 117 }) 118 } 119} 120 121func (m * Model )onStackTraceResponse (a * adapter.Adapter ,res * dap.StackTraceResponse ,ctx * dap.StackTraceRequest ) tea.Cmd { 122a .StackFrames [ctx .Arguments .ThreadId ]= res .Body .StackFrames 123a .FocusedStackFrame = & a .StackFrames [ctx .Arguments .ThreadId ][0 ] 124return m .printFileLocation (a ) 125} 126 127func (m * Model )onEvaluateResponse (res * dap.EvaluateResponse ) tea.Cmd { 128return tea .Println (res .Body .Result ) 129} 130 131func (m * Model )printFileLocation (a * adapter.Adapter ) tea.Cmd { 132sf := a .FocusedStackFrame 133if sf == nil { 134return tea .Println (errors .New ("No stack frame in context" )) 135 } 136if sf .Source == nil { 137return tea .Println (errors .New ("Source cannot be found" )) 138 } 139if sf .Source .SourceReference != 0 { 140return tea .Println (errors .New ("sourceReference is unimplemented" )) 141 } 142path := sf .Source .Path 143if path == "" { 144return tea .Println (errors .New ("Path is empty" )) 145 } 146contents ,err := os .ReadFile (path ) 147if err != nil { 148return tea .Println (err ) 149 } 150lines := strings .Split (string (contents ),"\n" ) 151if len (lines )- 1 < sf .Line { 152return tea .Println (errors .New ("Invalid line number" )) 153 } 154var output string 155for i := sf .Line - 3 ;i < sf .Line + 4 ;i ++ { 156if i < 0 || i > len (lines )- 1 { 157continue 158 } 159prefix := "" 160if i == sf .Line - 1 { 161prefix = "->" 162 } 163output += fmt .Sprintf ("%3s %3d: %s\n" ,prefix ,i + 1 ,lines [i ]) 164 } 165return tea .Println (output ) 166} 167 168func (m * Model )travelStackFrame (a * adapter.Adapter ,delta int ) tea.Cmd { 169if a .FocusedStackFrame == nil { 170return tea .Println ("no stack frame is in focus" ) 171 } 172 173stackFrames := a .StackFrames [a .FocusedThread ] 174toFocus := - 1 175for i ,frame := range stackFrames { 176if frame .Id == a .FocusedStackFrame .Id { 177toFocus = i + delta 178break 179 } 180 } 181if toFocus < 0 { 182toFocus = 0 183 }else if len (stackFrames )- 1 < toFocus { 184toFocus = len (stackFrames )- 1 185 } 186a .FocusedStackFrame = & stackFrames [toFocus ] 187return m .printFileLocation (a ) 188} 189 190// func (r *Model) jumpInKak() { 191// cmd := exec.Command("kak", "-p", "Krastorio2") 192// buffer := bytes.Buffer{} 193// if r.focusedStackFrame.Source.Path == "" { 194// return 195// } 196// buffer.WriteString(fmt.Sprintf("evaluate-commands -client %%opt{jumpclient} %%{edit %s %d}", r.focusedStackFrame.Source.Path, r.focusedStackFrame.Line)) 197// cmd.Stdin = &buffer 198// cmd.Run() 199// }