1package lsp
2
3import (
4 "bufio"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
9 "log"
10 "os"
11 "strings"
12)
13
14var debug = os.Getenv("DEBUG") != ""
15
16// Write writes an LSP message to the given writer
17func WriteMessage(w io.Writer, msg *Message) error {
18 data, err := json.Marshal(msg)
19 if err != nil {
20 return fmt.Errorf("failed to marshal message: %w", err)
21 }
22
23 if debug {
24 log.Printf("%v", msg.Method)
25 log.Printf("-> Sending: %s", string(data))
26 }
27
28 _, err = fmt.Fprintf(w, "Content-Length: %d\r\n\r\n", len(data))
29 if err != nil {
30 return fmt.Errorf("failed to write header: %w", err)
31 }
32
33 _, err = w.Write(data)
34 if err != nil {
35 return fmt.Errorf("failed to write message: %w", err)
36 }
37
38 return nil
39}
40
41// ReadMessage reads a single LSP message from the given reader
42func ReadMessage(r *bufio.Reader) (*Message, error) {
43 // Read headers
44 var contentLength int
45 for {
46 line, err := r.ReadString('\n')
47 if err != nil {
48 return nil, fmt.Errorf("failed to read header: %w", err)
49 }
50 line = strings.TrimSpace(line)
51
52 if debug {
53 log.Printf("<- Header: %s", line)
54 }
55
56 if line == "" {
57 break // End of headers
58 }
59
60 if strings.HasPrefix(line, "Content-Length: ") {
61 _, err := fmt.Sscanf(line, "Content-Length: %d", &contentLength)
62 if err != nil {
63 return nil, fmt.Errorf("invalid Content-Length: %w", err)
64 }
65 }
66 }
67
68 if debug {
69 log.Printf("<- Reading content with length: %d", contentLength)
70 }
71
72 // Read content
73 content := make([]byte, contentLength)
74 _, err := io.ReadFull(r, content)
75 if err != nil {
76 return nil, fmt.Errorf("failed to read content: %w", err)
77 }
78
79 if debug {
80 log.Printf("<- Received: %s", string(content))
81 }
82
83 // Parse message
84 var msg Message
85 if err := json.Unmarshal(content, &msg); err != nil {
86 return nil, fmt.Errorf("failed to unmarshal message: %w", err)
87 }
88
89 return &msg, nil
90}
91
92// handleMessages reads and dispatches messages in a loop
93func (c *Client) handleMessages() {
94 for {
95 msg, err := ReadMessage(c.stdout)
96 if err != nil {
97 if debug {
98 log.Printf("Error reading message: %v", err)
99 }
100 return
101 }
102
103 // Handle server->client request (has both Method and ID)
104 if msg.Method != "" && msg.ID != 0 {
105 if debug {
106 log.Printf("Received request from server: method=%s id=%d", msg.Method, msg.ID)
107 }
108
109 response := &Message{
110 JSONRPC: "2.0",
111 ID: msg.ID,
112 }
113
114 // Look up handler for this method
115 c.serverHandlersMu.RLock()
116 handler, ok := c.serverRequestHandlers[msg.Method]
117 c.serverHandlersMu.RUnlock()
118
119 if ok {
120 result, err := handler(msg.Params)
121 if err != nil {
122 response.Error = &ResponseError{
123 Code: -32603,
124 Message: err.Error(),
125 }
126 } else {
127 rawJSON, err := json.Marshal(result)
128 if err != nil {
129 response.Error = &ResponseError{
130 Code: -32603,
131 Message: fmt.Sprintf("failed to marshal response: %v", err),
132 }
133 } else {
134 response.Result = rawJSON
135 }
136 }
137 } else {
138 response.Error = &ResponseError{
139 Code: -32601,
140 Message: fmt.Sprintf("method not found: %s", msg.Method),
141 }
142 }
143
144 // Send response back to server
145 if err := WriteMessage(c.stdin, response); err != nil {
146 log.Printf("Error sending response to server: %v", err)
147 }
148
149 continue
150 }
151
152 // Handle notification (has Method but no ID)
153 if msg.Method != "" && msg.ID == 0 {
154 c.notificationMu.RLock()
155 handler, ok := c.notificationHandlers[msg.Method]
156 c.notificationMu.RUnlock()
157
158 if ok {
159 if debug {
160 log.Printf("Handling notification: %s", msg.Method)
161 }
162 go handler(msg.Params)
163 } else if debug {
164 log.Printf("No handler for notification: %s", msg.Method)
165 }
166 continue
167 }
168
169 // Handle response to our request (has ID but no Method)
170 if msg.ID != 0 && msg.Method == "" {
171 c.handlersMu.RLock()
172 ch, ok := c.handlers[msg.ID]
173 c.handlersMu.RUnlock()
174
175 if ok {
176 if debug {
177 log.Printf("Sending response for ID %d to handler", msg.ID)
178 }
179 ch <- msg
180 close(ch)
181 } else if debug {
182 log.Printf("No handler for response ID: %d", msg.ID)
183 }
184 }
185 }
186}
187
188// Call makes a request and waits for the response
189func (c *Client) Call(ctx context.Context, method string, params any, result any) error {
190 id := c.nextID.Add(1)
191
192 if debug {
193 log.Printf("Making call: method=%s id=%d", method, id)
194 }
195
196 msg, err := NewRequest(id, method, params)
197 if err != nil {
198 return fmt.Errorf("failed to create request: %w", err)
199 }
200
201 // Create response channel
202 ch := make(chan *Message, 1)
203 c.handlersMu.Lock()
204 c.handlers[id] = ch
205 c.handlersMu.Unlock()
206
207 defer func() {
208 c.handlersMu.Lock()
209 delete(c.handlers, id)
210 c.handlersMu.Unlock()
211 }()
212
213 // Send request
214 if err := WriteMessage(c.stdin, msg); err != nil {
215 return fmt.Errorf("failed to send request: %w", err)
216 }
217
218 if debug {
219 log.Printf("Waiting for response to request ID: %d", id)
220 }
221
222 // Wait for response
223 resp := <-ch
224
225 if debug {
226 log.Printf("Received response for request ID: %d", id)
227 }
228
229 if resp.Error != nil {
230 return fmt.Errorf("request failed: %s (code: %d)", resp.Error.Message, resp.Error.Code)
231 }
232
233 if result != nil {
234 // If result is a json.RawMessage, just copy the raw bytes
235 if rawMsg, ok := result.(*json.RawMessage); ok {
236 *rawMsg = resp.Result
237 return nil
238 }
239 // Otherwise unmarshal into the provided type
240 if err := json.Unmarshal(resp.Result, result); err != nil {
241 return fmt.Errorf("failed to unmarshal result: %w", err)
242 }
243 }
244
245 return nil
246}
247
248// Notify sends a notification (a request without an ID that doesn't expect a response)
249func (c *Client) Notify(ctx context.Context, method string, params any) error {
250 if debug {
251 log.Printf("Sending notification: method=%s", method)
252 }
253
254 msg, err := NewNotification(method, params)
255 if err != nil {
256 return fmt.Errorf("failed to create notification: %w", err)
257 }
258
259 if err := WriteMessage(c.stdin, msg); err != nil {
260 return fmt.Errorf("failed to send notification: %w", err)
261 }
262
263 return nil
264}
265
266type (
267 NotificationHandler func(params json.RawMessage)
268 ServerRequestHandler func(params json.RawMessage) (any, error)
269)