z_mirror/pesticide

git clone https://git.raiguard.me/z_mirror/pesticide

Caleb HeuerFix crash when stackframe source is nild788097

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