transport.go

  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)