z_mirror/pesticide

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

Caleb HeuerUpdate Go module for Codeberg388f8ba

trunk
3.8 KiB187 linesraw
1package adapter
2
3import (
4	"bufio"
5	"encoding/json"
6	"errors"
7	"io"
8	"log"
9	"net"
10	"os/exec"
11	"time"
12
13	tea "github.com/charmbracelet/bubbletea"
14	"github.com/google/go-dap"
15	"github.com/google/shlex"
16	"codeberg.org/raiguard/pesticide/config"
17)
18
19type AdapterState int
20
21const (
22	Initializing AdapterState = iota
23	Running
24	Stopped
25)
26
27type Adapter struct {
28	// Internally managed
29	Capabilities    dap.Capabilities
30	ID              string
31	Seq             int
32	PendingRequests map[int]dap.Message
33	// Managed by router
34	State             AdapterState
35	Threads           []dap.Thread
36	FocusedThread     int
37	StackFrames       map[int][]dap.StackFrame
38	FocusedStackFrame *dap.StackFrame
39	Breakpoints       map[string][]dap.SourceBreakpoint
40	// Internal I/O
41	rw         bufio.ReadWriter
42	cmd        *exec.Cmd
43	launchArgs json.RawMessage
44	conn       *net.Conn
45}
46
47type Msg struct {
48	ID  string
49	Msg tea.Msg
50}
51
52func New(config config.AdapterConfig) (*Adapter, error) {
53	var cmd *exec.Cmd
54	var conn *net.Conn
55	var rw *bufio.ReadWriter
56	if config.Cmd != nil {
57		args, err := shlex.Split(*config.Cmd)
58		if err != nil {
59			return nil, err
60		}
61		child := exec.Command(args[0], args[1:]...)
62		stdin, err := child.StdinPipe()
63		if err != nil {
64			return nil, err
65		}
66		stdout, err := child.StdoutPipe()
67		if err != nil {
68			return nil, err
69		}
70		// TODO: Hook stderr and show in the UI
71		err = child.Start()
72		if err != nil {
73			return nil, err
74		}
75		cmd = child
76
77		reader := bufio.NewReader(stdout)
78		writer := bufio.NewWriter(stdin)
79		rw = &bufio.ReadWriter{Reader: reader, Writer: writer}
80	}
81	if config.Addr != nil {
82		if cmd != nil {
83			time.Sleep(time.Millisecond * 500) // Give time for the cmd to init
84		}
85		conn, err := net.Dial("tcp", *config.Addr)
86		if err != nil {
87			return nil, err
88		}
89
90		reader := bufio.NewReader(conn)
91		writer := bufio.NewWriter(conn)
92
93		// TODO: Combine with STDIO input/output
94		rw = &bufio.ReadWriter{Reader: reader, Writer: writer}
95	}
96
97	if rw == nil {
98		return nil, errors.New("Adapter must either have a connection or a subprocess")
99	}
100
101	a := &Adapter{
102		Capabilities:      dap.Capabilities{},
103		ID:                config.ID,
104		Seq:               0,
105		PendingRequests:   map[int]dap.Message{},
106		Threads:           []dap.Thread{},
107		FocusedThread:     0,
108		StackFrames:       map[int][]dap.StackFrame{},
109		FocusedStackFrame: &dap.StackFrame{},
110		Breakpoints:       map[string][]dap.SourceBreakpoint{},
111		rw:                *rw,
112		cmd:               cmd,
113		launchArgs:        config.Args,
114		conn:              conn,
115	}
116
117	return a, nil
118}
119
120func (a *Adapter) Shutdown() {
121	conn := a.conn
122	if conn != nil {
123		(*conn).Close()
124	}
125	cmd := a.cmd
126	if cmd != nil {
127		cmd.Process.Kill()
128	}
129	log.Printf("[%s] EXITED\n", a.ID)
130}
131
132func (a *Adapter) Send(msg dap.Message) {
133	a.PendingRequests[msg.GetSeq()] = msg
134	err := dap.WriteProtocolMessage(a.rw.Writer, msg)
135	if err != nil {
136		log.Println("Unable to send message to adapter: ", err)
137		return
138	}
139	val, err := json.Marshal(msg)
140	if err != nil {
141		panic(err)
142	}
143	log.Printf("[%s] <- %s", a.ID, string(val))
144	a.rw.Writer.Flush()
145}
146
147func (a *Adapter) Receive() Msg {
148	if a == nil {
149		return Msg{}
150	}
151	res := Msg{ID: a.ID}
152	msg, err := dap.ReadProtocolMessage(a.rw.Reader)
153	if err != nil {
154		if !errors.Is(err, io.EOF) {
155			res.Msg = tea.Println(err)
156		}
157		return res
158	}
159	val, err := json.Marshal(msg)
160	if err != nil {
161		res.Msg = tea.Println(err)
162		return res
163	}
164	log.Printf("[%s] -> %s", a.ID, string(val))
165	// Increment seq
166	seq := msg.GetSeq()
167	if seq > a.Seq {
168		a.Seq = seq
169	}
170	res.Msg = msg
171	return res
172}
173
174func (a *Adapter) NewRequest(command string) dap.Request {
175	a.Seq++
176	return dap.Request{
177		ProtocolMessage: dap.ProtocolMessage{Seq: a.Seq, Type: "request"},
178		Command:         command,
179	}
180}
181
182func (a *Adapter) Launch() {
183	a.Send(&dap.LaunchRequest{
184		Request:   a.NewRequest("launch"),
185		Arguments: a.launchArgs,
186	})
187}