internal/lsp/lsproto/baseproto.go (75 lines of code) (raw):

package lsproto import ( "bufio" "bytes" "errors" "fmt" "io" "strconv" ) // https://microsoft.github.io/language-server-protocol/specifications/base/0.9/specification/ var ( ErrInvalidHeader = errors.New("lsp: invalid header") ErrInvalidContentLength = errors.New("lsp: invalid content length") ErrNoContentLength = errors.New("lsp: no content length") ) type BaseReader struct { r *bufio.Reader } func NewBaseReader(r io.Reader) *BaseReader { return &BaseReader{ r: bufio.NewReader(r), } } func (r *BaseReader) Read() ([]byte, error) { var contentLength int64 for { line, err := r.r.ReadBytes('\n') if err != nil { if errors.Is(err, io.EOF) { return nil, io.EOF } return nil, fmt.Errorf("lsp: read header: %w", err) } if bytes.Equal(line, []byte("\r\n")) { break } key, value, ok := bytes.Cut(line, []byte(":")) if !ok { return nil, fmt.Errorf("%w: %q", ErrInvalidHeader, line) } if bytes.Equal(key, []byte("Content-Length")) { contentLength, err = strconv.ParseInt(string(bytes.TrimSpace(value)), 10, 64) if err != nil { return nil, fmt.Errorf("%w: parse error: %w", ErrInvalidContentLength, err) } if contentLength < 0 { return nil, fmt.Errorf("%w: negative value %d", ErrInvalidContentLength, contentLength) } } } if contentLength <= 0 { return nil, ErrNoContentLength } data := make([]byte, contentLength) if _, err := io.ReadFull(r.r, data); err != nil { return nil, fmt.Errorf("lsp: read content: %w", err) } return data, nil } type BaseWriter struct { w *bufio.Writer } func NewBaseWriter(w io.Writer) *BaseWriter { return &BaseWriter{ w: bufio.NewWriter(w), } } func (w *BaseWriter) Write(data []byte) error { if _, err := fmt.Fprintf(w.w, "Content-Length: %d\r\n\r\n", len(data)); err != nil { return err } if _, err := w.w.Write(data); err != nil { return err } return w.w.Flush() }