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