1package lsp
2
3import (
4 "bufio"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
9 "log/slog"
10 "strings"
11
12 "github.com/charmbracelet/crush/internal/config"
13)
14
15// WriteMessage 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 slog.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 slog.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 slog.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 slog.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 slog.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 slog.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 slog.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 slog.Debug("Handling notification", "method", msg.Method)
162 }
163 go handler(msg.Params)
164 } else if cfg.Options.DebugLSP {
165 slog.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 slog.Debug("Received response for request", "id", msg.ID)
179 }
180 ch <- msg
181 close(ch)
182 } else if cfg.Options.DebugLSP {
183 slog.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 if !c.IsMethodSupported(method) {
192 return fmt.Errorf("method not supported by server: %s", method)
193 }
194 id := c.nextID.Add(1)
195
196 cfg := config.Get()
197 if cfg.Options.DebugLSP {
198 slog.Debug("Making call", "method", method, "id", id)
199 }
200
201 msg, err := NewRequest(id, method, params)
202 if err != nil {
203 return fmt.Errorf("failed to create request: %w", err)
204 }
205
206 // Create response channel
207 ch := make(chan *Message, 1)
208 c.handlersMu.Lock()
209 c.handlers[id] = ch
210 c.handlersMu.Unlock()
211
212 defer func() {
213 c.handlersMu.Lock()
214 delete(c.handlers, id)
215 c.handlersMu.Unlock()
216 }()
217
218 // Send request
219 if err := WriteMessage(c.stdin, msg); err != nil {
220 return fmt.Errorf("failed to send request: %w", err)
221 }
222
223 if cfg.Options.DebugLSP {
224 slog.Debug("Request sent", "method", method, "id", id)
225 }
226
227 // Wait for response
228 select {
229 case <-ctx.Done():
230 return ctx.Err()
231 case resp := <-ch:
232 if cfg.Options.DebugLSP {
233 slog.Debug("Received response", "id", id)
234 }
235
236 if resp.Error != nil {
237 return fmt.Errorf("request failed: %s (code: %d)", resp.Error.Message, resp.Error.Code)
238 }
239
240 if result != nil {
241 // If result is a json.RawMessage, just copy the raw bytes
242 if rawMsg, ok := result.(*json.RawMessage); ok {
243 *rawMsg = resp.Result
244 return nil
245 }
246 // Otherwise unmarshal into the provided type
247 if err := json.Unmarshal(resp.Result, result); err != nil {
248 return fmt.Errorf("failed to unmarshal result: %w", err)
249 }
250 }
251
252 return nil
253 }
254}
255
256// Notify sends a notification (a request without an ID that doesn't expect a response)
257func (c *Client) Notify(ctx context.Context, method string, params any) error {
258 cfg := config.Get()
259 if !c.IsMethodSupported(method) {
260 if cfg.Options.DebugLSP {
261 slog.Debug("Skipping notification: method not supported by server", "method", method)
262 }
263 return nil
264 }
265 if cfg.Options.DebugLSP {
266 slog.Debug("Sending notification", "method", method)
267 }
268
269 msg, err := NewNotification(method, params)
270 if err != nil {
271 return fmt.Errorf("failed to create notification: %w", err)
272 }
273
274 if err := WriteMessage(c.stdin, msg); err != nil {
275 return fmt.Errorf("failed to send notification: %w", err)
276 }
277
278 return nil
279}
280
281type (
282 NotificationHandler func(params json.RawMessage)
283 ServerRequestHandler func(params json.RawMessage) (any, error)
284)