返回 DeepSeek-Reasonix
jsonrpc.go
根目录 / internal / lsp / jsonrpc.go
1 // Package lsp is a minimal Language Server Protocol client: it spawns a language
2 // server per language on demand, syncs queried documents from disk, and adapts a
3 // few read-only capabilities (definition, references, hover, diagnostics) to the
4 // tool.Tool interface. Servers are not bundled — they resolve on PATH and a clear
5 // install hint is returned when one is missing.
6 package lsp
7
8 import (
9 "bufio"
10 "context"
11 "encoding/json"
12 "fmt"
13 "io"
14 "strconv"
15 "strings"
16 "sync"
17 )
18
19 type rpcError struct {
20 Code int `json:"code"`
21 Message string `json:"message"`
22 }
23
24 func (e *rpcError) Error() string { return fmt.Sprintf("lsp error %d: %s", e.Code, e.Message) }
25
26 type outMsg struct {
27 JSONRPC string `json:"jsonrpc"`
28 ID *int64 `json:"id,omitempty"`
29 Method string `json:"method,omitempty"`
30 Params any `json:"params,omitempty"`
31 Result any `json:"result,omitempty"`
32 }
33
34 type inMsg struct {
35 ID *int64 `json:"id"`
36 Method string `json:"method"`
37 Result json.RawMessage `json:"result"`
38 Error *rpcError `json:"error"`
39 Params json.RawMessage `json:"params"`
40 }
41
42 // conn speaks LSP framing (Content-Length headers) over a subprocess's
43 // stdin/stdout. A single read-pump goroutine demultiplexes the stream:
44 // id-bearing responses wake the matching call; method-only messages are
45 // notifications (diagnostics); id+method messages are server→client requests
46 // that must be answered or some servers stall during initialize.
47 type conn struct {
48 w io.Writer
49 writeMu sync.Mutex
50
51 mu sync.Mutex
52 nextID int64
53 pending map[int64]chan inMsg
54
55 onNotify func(method string, params json.RawMessage)
56 onRequest func(id int64, method string, params json.RawMessage)
57
58 closeOnce sync.Once
59 closed chan struct{}
60 err error
61 }
62
63 func newConn(w io.Writer, r io.Reader,
64 onNotify func(string, json.RawMessage),
65 onRequest func(int64, string, json.RawMessage)) *conn {
66 c := &conn{
67 w: w,
68 pending: map[int64]chan inMsg{},
69 onNotify: onNotify,
70 onRequest: onRequest,
71 closed: make(chan struct{}),
72 }
73 go c.readLoop(r)
74 return c
75 }
76
77 func (c *conn) call(ctx context.Context, method string, params any) (json.RawMessage, error) {
78 c.mu.Lock()
79 c.nextID++
80 id := c.nextID
81 ch := make(chan inMsg, 1)
82 c.pending[id] = ch
83 c.mu.Unlock()
84 defer func() {
85 c.mu.Lock()
86 delete(c.pending, id)
87 c.mu.Unlock()
88 }()
89
90 if err := c.writeMsg(outMsg{JSONRPC: "2.0", ID: &id, Method: method, Params: params}); err != nil {
91 return nil, err
92 }
93 select {
94 case <-ctx.Done():
95 return nil, ctx.Err()
96 case <-c.closed:
97 return nil, c.err
98 case m := <-ch:
99 if m.Error != nil {
100 return nil, m.Error
101 }
102 return m.Result, nil
103 }
104 }
105
106 func (c *conn) notify(method string, params any) error {
107 return c.writeMsg(outMsg{JSONRPC: "2.0", Method: method, Params: params})
108 }
109
110 func (c *conn) reply(id int64, result any) error {
111 return c.writeMsg(outMsg{JSONRPC: "2.0", ID: &id, Result: result})
112 }
113
114 func (c *conn) writeMsg(v any) error {
115 b, err := json.Marshal(v)
116 if err != nil {
117 return err
118 }
119 c.writeMu.Lock()
120 defer c.writeMu.Unlock()
121 if _, err := fmt.Fprintf(c.w, "Content-Length: %d\r\n\r\n", len(b)); err != nil {
122 return err
123 }
124 _, err = c.w.Write(b)
125 return err
126 }
127
128 func (c *conn) readLoop(r io.Reader) {
129 br := bufio.NewReader(r)
130 for {
131 body, err := readFrame(br)
132 if err != nil {
133 c.fail(err)
134 return
135 }
136 var m inMsg
137 if json.Unmarshal(body, &m) != nil {
138 continue
139 }
140 switch {
141 case m.Method != "" && m.ID != nil:
142 if c.onRequest != nil {
143 c.onRequest(*m.ID, m.Method, m.Params)
144 }
145 case m.Method != "":
146 if c.onNotify != nil {
147 c.onNotify(m.Method, m.Params)
148 }
149 case m.ID != nil:
150 c.mu.Lock()
151 ch := c.pending[*m.ID]
152 c.mu.Unlock()
153 if ch != nil {
154 ch <- m
155 }
156 }
157 }
158 }
159
160 func (c *conn) fail(err error) {
161 c.closeOnce.Do(func() {
162 c.err = err
163 close(c.closed)
164 })
165 }
166
167 // maxFrameBytes caps a single LSP message body. Generous for any real response
168 // (document symbols, semantic tokens for a huge file) while stopping a corrupt or
169 // desynced Content-Length from triggering an unbounded allocation that OOMs the
170 // whole process.
171 const maxFrameBytes = 64 << 20 // 64 MiB
172
173 // readFrame reads one LSP message: header lines terminated by a blank line, then
174 // exactly Content-Length bytes of JSON body.
175 func readFrame(r *bufio.Reader) ([]byte, error) {
176 n := -1
177 for {
178 line, err := r.ReadString('\n')
179 if err != nil {
180 return nil, err
181 }
182 line = strings.TrimRight(line, "\r\n")
183 if line == "" {
184 break
185 }
186 if v, ok := strings.CutPrefix(line, "Content-Length:"); ok {
187 parsed, err := strconv.Atoi(strings.TrimSpace(v))
188 if err != nil {
189 return nil, fmt.Errorf("bad Content-Length %q: %w", v, err)
190 }
191 n = parsed
192 }
193 }
194 if n < 0 {
195 return nil, fmt.Errorf("missing Content-Length header")
196 }
197 if n > maxFrameBytes {
198 return nil, fmt.Errorf("Content-Length %d exceeds the %d-byte frame cap", n, maxFrameBytes)
199 }
200 buf := make([]byte, n)
201 if _, err := io.ReadFull(r, buf); err != nil {
202 return nil, err
203 }
204 return buf, nil
205 }
206
206 lines GO