// Package lsp is a small Language Server Protocol client: enough of it to ask // a language server for completions, hovers, definitions and diagnostics. // // The protocol is JSON-RPC 2.0 inside HTTP-style frames. The JSON-RPC half is // jsonrpc's, shared with every other protocol built on it; what is here is the // framing, the methods, and the conversation a language server expects. package lsp import ( "bufio" "errors" "fmt" "io" "net/textproto" "strconv" ) // MaxMessageBytes caps how large a single message may be. A language server // should never send anything near this; the limit is there so that a corrupt // or hostile Content-Length cannot make the editor allocate without bound. const MaxMessageBytes = 64 << 20 // ErrMessageTooLarge is returned when a frame announces more bytes than // MaxMessageBytes. var ErrMessageTooLarge = errors.New("lsp: message too large") // contentLengthHeader is the one header the protocol actually requires. const contentLengthHeader = "Content-Length" // Framing is the Language Server Protocol's way of marking one message off // from the next: a Content-Length header, a blank line, then the JSON. // // It is the jsonrpc.Framer a language server connection is built with. // // conn := jsonrpc.NewConn(stream, lsp.Framing{}, onNotify, onRequest) type Framing struct{} // ReadMessage reads one framed message and returns its body. func (Framing) ReadMessage(r *bufio.Reader) ([]byte, error) { return ReadMessage(r) } // WriteMessage frames body and writes it out. func (Framing) WriteMessage(w io.Writer, body []byte) error { return WriteMessage(w, body) } // WriteMessage frames body and writes it out. // // The frame is the header, a blank line, then the JSON — the same shape HTTP // uses, which is what the specification asks for. func WriteMessage(w io.Writer, body []byte) error { if _, err := fmt.Fprintf(w, "%s: %d\r\n\r\n", contentLengthHeader, len(body)); err != nil { return fmt.Errorf("lsp: writing the header: %w", err) } if _, err := w.Write(body); err != nil { return fmt.Errorf("lsp: writing the body: %w", err) } return nil } // ReadMessage reads one framed message and returns its body. // // It returns io.EOF when the stream ends cleanly between messages, which is // how a server shutting down is told apart from one that died mid-message. func ReadMessage(r *bufio.Reader) ([]byte, error) { length, err := readContentLength(r) if err != nil { return nil, err } body := make([]byte, length) if _, err := io.ReadFull(r, body); err != nil { return nil, fmt.Errorf("lsp: reading a %d-byte body: %w", length, err) } return body, nil } // readContentLength reads the header block and returns the announced size. func readContentLength(r *bufio.Reader) (int, error) { header, err := textproto.NewReader(r).ReadMIMEHeader() if err != nil { if errors.Is(err, io.EOF) { return 0, io.EOF } return 0, fmt.Errorf("lsp: reading the header: %w", err) } return parseContentLength(header.Get(contentLengthHeader)) } // parseContentLength turns the header's value into a size, rejecting the ones // that are missing, malformed, or large enough to be an attack. func parseContentLength(raw string) (int, error) { if raw == "" { return 0, fmt.Errorf("lsp: the frame has no %s header", contentLengthHeader) } length, err := strconv.Atoi(raw) if err != nil || length < 0 { return 0, fmt.Errorf("lsp: %s is %q, which is not a size", contentLengthHeader, raw) } if length > MaxMessageBytes { return 0, fmt.Errorf("%w: %d bytes", ErrMessageTooLarge, length) } return length, nil }