transport.go

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