Documentation
¶
Overview ¶
Package lsp provides a Language Server Protocol (LSP) client system for communicating with language servers using JSON-RPC 2.0 over stdio or TCP.
The package implements a layered architecture:
- models.go: Core LSP data types (Diagnostic, Position, Range, Location, etc.)
- protocol.go: JSON-RPC 2.0 transport and connection management
- client.go: LSP client interface for single-server communication
- server.go: ServerManager for multi-server lifecycle management
Basic usage:
mgr := lsp.NewServerManager(launcher, lsp.WithMaxParallel(4))
err := mgr.StartServer(ctx, "go")
client, err := mgr.GetClient("go")
diagnostics, err := client.Diagnostics(ctx, "file:///path/to/file.go")
err = mgr.StopServer(ctx, "go")
Error handling uses sentinel errors that can be checked with errors.Is:
if errors.Is(err, lsp.ErrServerNotRunning) {
// server is not running
}
All long-running operations accept context.Context as the first parameter and respect cancellation and timeout signals.
Index ¶
- Constants
- Variables
- func EncodeMessage(body []byte) []byte
- type Client
- type Conn
- type Diagnostic
- type DiagnosticSeverity
- type DiagnosticsProvider
- type DocumentSymbol
- type HoverProvider
- type HoverResult
- type InitializeResult
- type Initializer
- type JSONRPCError
- type Location
- type ManagerOption
- type MessageTransport
- type NavigationProvider
- type Position
- type ProcessHandle
- type Range
- type ServerLauncher
- type ServerManager
- type StreamTransport
- type SymbolKind
- type SymbolsProvider
Constants ¶
const ( CodeParseError = -32700 CodeInvalidRequest = -32600 CodeMethodNotFound = -32601 CodeInvalidParams = -32602 CodeInternalError = -32603 )
Standard JSON-RPC 2.0 error codes.
Variables ¶
var ( // ErrServerNotRunning indicates a request was made to a server that is not running. ErrServerNotRunning = errors.New("lsp: server not running") // ErrServerStartFailed indicates a language server process failed to start. ErrServerStartFailed = errors.New("lsp: server failed to start") // ErrInitializeFailed indicates the LSP initialize handshake failed. ErrInitializeFailed = errors.New("lsp: initialization failed") // ErrConnectionClosed indicates the connection to the language server was closed. ErrConnectionClosed = errors.New("lsp: connection closed") )
Sentinel errors for LSP operations.
Functions ¶
func EncodeMessage ¶
EncodeMessage encodes a JSON body with LSP Content-Length headers. This is a utility function for testing and low-level protocol work.
Types ¶
type Client ¶
type Client interface {
Initializer
DiagnosticsProvider
NavigationProvider
HoverProvider
SymbolsProvider
}
Client composes all LSP capabilities and communicates with a single Language Server over JSON-RPC 2.0. All methods accept a context.Context for cancellation and timeout control.
This interface composes the following focused interfaces for consumers that only need a subset of functionality:
- Initializer: Server lifecycle (Initialize, Shutdown)
- DiagnosticsProvider: Diagnostic retrieval
- NavigationProvider: References and Definition
- HoverProvider: Hover information
- SymbolsProvider: Document symbols
Example usage:
client := lsp.NewClient(conn)
if err := client.Initialize(ctx, "file:///project"); err != nil { ... }
diagnostics, err := client.Diagnostics(ctx, "file:///project/main.go")
if err := client.Shutdown(ctx); err != nil { ... }
type Conn ¶
type Conn interface {
// Call sends a JSON-RPC request and unmarshals the response result into result.
// result must be a pointer to the expected response type, or nil.
Call(ctx context.Context, method string, params any, result any) error
// Notify sends a JSON-RPC notification (no response expected).
Notify(ctx context.Context, method string, params any) error
// Close closes the connection and releases resources.
Close() error
}
Conn represents a JSON-RPC 2.0 connection that can send requests and notifications.
func NewConn ¶
func NewConn(transport MessageTransport) Conn
NewConn creates a new JSON-RPC connection over the given transport. It starts a background goroutine to read and dispatch responses.
type Diagnostic ¶
type Diagnostic struct {
// Range is the range at which the message applies.
Range Range `json:"range"`
// Severity is the diagnostic's severity level.
Severity DiagnosticSeverity `json:"severity"`
// Code is the diagnostic's code (e.g., "E0001"). May be empty.
Code string `json:"code,omitempty"`
// Source identifies the tool that produced this diagnostic (e.g., "gopls").
Source string `json:"source,omitempty"`
// Message is the diagnostic's human-readable message.
Message string `json:"message"`
}
Diagnostic represents a compiler error, warning, or informational message.
func (Diagnostic) IsError ¶
func (d Diagnostic) IsError() bool
IsError reports whether this diagnostic is an error.
func (Diagnostic) IsWarning ¶
func (d Diagnostic) IsWarning() bool
IsWarning reports whether this diagnostic is a warning.
type DiagnosticSeverity ¶
type DiagnosticSeverity int
DiagnosticSeverity represents the severity level of a diagnostic. Values match the LSP 3.17 specification.
const ( // SeverityError reports an error (severity 1). SeverityError DiagnosticSeverity = 1 // SeverityWarning reports a warning (severity 2). SeverityWarning DiagnosticSeverity = 2 // SeverityInfo reports an information message (severity 3). SeverityInfo DiagnosticSeverity = 3 // SeverityHint reports a hint (severity 4). SeverityHint DiagnosticSeverity = 4 )
func (DiagnosticSeverity) String ¶
func (s DiagnosticSeverity) String() string
String returns the human-readable name of the severity level.
type DiagnosticsProvider ¶
type DiagnosticsProvider interface {
// Diagnostics retrieves diagnostics for the given document URI.
Diagnostics(ctx context.Context, uri string) ([]Diagnostic, error)
}
DiagnosticsProvider provides diagnostic information from the language server. Use this interface when you only need to retrieve diagnostics.
type DocumentSymbol ¶
type DocumentSymbol struct {
// Name is the symbol's name.
Name string `json:"name"`
// Kind is the symbol's kind (function, class, variable, etc.).
Kind SymbolKind `json:"kind"`
// Range is the range enclosing this symbol, not including leading/trailing whitespace.
Range Range `json:"range"`
// Children contains child symbols (e.g., struct fields, class methods).
Children []DocumentSymbol `json:"children,omitempty"`
}
DocumentSymbol represents a programming construct like a variable, class, or function that appears in a document. Symbols can be hierarchical via Children.
type HoverProvider ¶
type HoverProvider interface {
// Hover returns hover information for the symbol at the given position.
Hover(ctx context.Context, uri string, pos Position) (*HoverResult, error)
}
HoverProvider provides hover information for symbols. Use this interface when you only need hover documentation.
type HoverResult ¶
type HoverResult struct {
// Contents is the hover information content (may be markdown).
Contents string `json:"contents"`
// Range is the optional range for the symbol being hovered.
Range *Range `json:"range,omitempty"`
}
HoverResult represents the result of a hover request.
type InitializeResult ¶
type InitializeResult struct {
// Capabilities describes the server's capabilities.
Capabilities json.RawMessage `json:"capabilities"`
}
InitializeResult represents the result of an LSP initialize request.
type Initializer ¶
type Initializer interface {
// Initialize sends the LSP initialize request and initialized notification.
Initialize(ctx context.Context, rootURI string) error
// Shutdown sends the LSP shutdown request followed by an exit notification.
Shutdown(ctx context.Context) error
}
Initializer handles LSP server lifecycle management. Use this interface when you only need to start or stop the server.
type JSONRPCError ¶
type JSONRPCError struct {
// Code is the error code.
Code int `json:"code"`
// Message is a short description of the error.
Message string `json:"message"`
// Data contains additional information about the error.
Data json.RawMessage `json:"data,omitempty"`
}
JSONRPCError represents a JSON-RPC 2.0 error object.
func (*JSONRPCError) Error ¶
func (e *JSONRPCError) Error() string
Error implements the error interface for JSONRPCError.
type Location ¶
type Location struct {
// URI is the resource identifier (e.g., "file:///path/to/file.go").
URI string `json:"uri"`
// Range is the range within the resource.
Range Range `json:"range"`
}
Location represents a location inside a resource, such as a line in a text file.
type ManagerOption ¶
type ManagerOption func(*serverManager)
ManagerOption configures a serverManager.
func WithMaxParallel ¶
func WithMaxParallel(n int) ManagerOption
WithMaxParallel sets the maximum number of concurrent server startups. The default is 4.
type MessageTransport ¶
type MessageTransport interface {
// ReadMessage reads a complete LSP message and returns the JSON body.
ReadMessage(ctx context.Context) (json.RawMessage, error)
// WriteMessage writes a JSON body as a complete LSP message with headers.
WriteMessage(ctx context.Context, data json.RawMessage) error
// Close closes the transport connection.
Close() error
}
MessageTransport handles reading and writing LSP base protocol messages (Content-Length headers + JSON body) over a byte stream.
type NavigationProvider ¶
type NavigationProvider interface {
References(ctx context.Context, uri string, pos Position) ([]Location, error)
Definition(ctx context.Context, uri string, pos Position) ([]Location, error)
}
NavigationProvider provides code navigation features. Use this interface when you only need references or go-to-definition.
type Position ¶
type Position struct {
// Line is the zero-based line number.
Line int `json:"line"`
// Character is the zero-based character offset on the line.
Character int `json:"character"`
}
Position represents a zero-based position in a text document.
type ProcessHandle ¶
type ProcessHandle interface {
// Kill forcefully terminates the server process.
Kill() error
// Wait blocks until the server process exits.
Wait() error
// IsRunning reports whether the server process is still running.
IsRunning() bool
}
ProcessHandle abstracts a language server process for lifecycle management.
type Range ¶
type Range struct {
// Start is the range's start position (inclusive).
Start Position `json:"start"`
// End is the range's end position (exclusive).
End Position `json:"end"`
}
Range represents a range in a text document defined by start and end positions.
type ServerLauncher ¶
type ServerLauncher interface {
// Launch starts a language server for the given language and returns
// the initialized client and a handle to the server process.
Launch(ctx context.Context, lang string) (Client, ProcessHandle, error)
}
ServerLauncher abstracts launching a language server process. Implementations handle process creation, transport setup, and LSP initialization.
Example implementation:
type stdioLauncher struct{}
func (l *stdioLauncher) Launch(ctx context.Context, lang string) (Client, ProcessHandle, error) {
cmd := exec.CommandContext(ctx, serverPath, args...)
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
cmd.Start()
transport := NewStreamTransport(stdout, stdin, cmd)
conn := NewConn(transport)
client := NewClient(conn)
client.Initialize(ctx, rootURI)
return client, &processHandle{cmd: cmd}, nil
}
type ServerManager ¶
type ServerManager interface {
// StartServer starts a language server for the given language.
// Returns nil if the server is already running (idempotent).
StartServer(ctx context.Context, lang string) error
// StopServer stops the language server for the given language.
// Returns nil if the server is not running (idempotent).
StopServer(ctx context.Context, lang string) error
// GetClient returns the LSP client for the given language.
// Returns ErrServerNotRunning if the server is not running.
GetClient(lang string) (Client, error)
// ActiveServers returns a sorted list of languages with running servers.
ActiveServers() []string
// HealthCheck checks the health of all active servers.
// Returns a map from language to error (nil means healthy).
HealthCheck(ctx context.Context) map[string]error
// StartAll starts servers for all given languages concurrently.
// Individual failures are non-fatal (graceful degradation).
StartAll(ctx context.Context, langs []string) error
// StopAll stops all running servers.
StopAll(ctx context.Context) error
// CollectAllDiagnostics collects diagnostics from all active servers concurrently.
// Individual server errors are non-fatal.
CollectAllDiagnostics(ctx context.Context, uri string) ([]Diagnostic, error)
}
@MX:ANCHOR: [AUTO] ServerManager is the core interface managing concurrent lifecycle of multi-language servers. All methods are designed to be thread-safe. @MX:REASON: fan_in=10+, entry point for LSP server management, called from multiple locations ServerManager manages multiple language server lifecycles concurrently. All methods are safe for concurrent use.
Example usage:
mgr := lsp.NewServerManager(launcher, lsp.WithMaxParallel(4))
err := mgr.StartAll(ctx, []string{"go", "python", "typescript"})
diags, err := mgr.CollectAllDiagnostics(ctx, "file:///project/main.go")
err = mgr.StopAll(ctx)
func NewServerManager ¶
func NewServerManager(launcher ServerLauncher, opts ...ManagerOption) ServerManager
NewServerManager creates a new ServerManager with the given launcher and options.
type StreamTransport ¶
type StreamTransport struct {
// contains filtered or unexported fields
}
StreamTransport implements MessageTransport over an io.ReadWriteCloser using the LSP base protocol (Content-Length headers).
func NewStreamTransport ¶
NewStreamTransport creates a new StreamTransport from separate reader, writer, and closer. For stdio transport, reader is stdout pipe, writer is stdin pipe, closer closes both.
func TCPTransport ¶
func TCPTransport(addr string) (*StreamTransport, error)
TCPTransport creates a MessageTransport over a TCP connection.
func (*StreamTransport) Close ¶
func (t *StreamTransport) Close() error
Close closes the underlying stream.
func (*StreamTransport) ReadMessage ¶
func (t *StreamTransport) ReadMessage(_ context.Context) (json.RawMessage, error)
ReadMessage reads a complete LSP message from the stream. It parses the Content-Length header and reads exactly that many bytes.
func (*StreamTransport) WriteMessage ¶
func (t *StreamTransport) WriteMessage(_ context.Context, data json.RawMessage) error
WriteMessage writes a JSON body with Content-Length header to the stream.
type SymbolKind ¶
type SymbolKind int
SymbolKind represents the kind of a document symbol. Values match the LSP 3.17 specification.
const ( SymbolKindFile SymbolKind = 1 SymbolKindModule SymbolKind = 2 SymbolKindNamespace SymbolKind = 3 SymbolKindPackage SymbolKind = 4 SymbolKindClass SymbolKind = 5 SymbolKindMethod SymbolKind = 6 SymbolKindProperty SymbolKind = 7 SymbolKindField SymbolKind = 8 SymbolKindConstructor SymbolKind = 9 SymbolKindEnum SymbolKind = 10 SymbolKindInterface SymbolKind = 11 SymbolKindFunction SymbolKind = 12 SymbolKindVariable SymbolKind = 13 SymbolKindConstant SymbolKind = 14 SymbolKindString SymbolKind = 15 SymbolKindNumber SymbolKind = 16 SymbolKindBoolean SymbolKind = 17 SymbolKindArray SymbolKind = 18 SymbolKindObject SymbolKind = 19 SymbolKindKey SymbolKind = 20 SymbolKindNull SymbolKind = 21 SymbolKindEnumMember SymbolKind = 22 SymbolKindStruct SymbolKind = 23 SymbolKindEvent SymbolKind = 24 SymbolKindOperator SymbolKind = 25 SymbolKindTypeParameter SymbolKind = 26 )
LSP symbol kind constants.
type SymbolsProvider ¶
type SymbolsProvider interface {
// Symbols returns the document symbols for the given document URI.
Symbols(ctx context.Context, uri string) ([]DocumentSymbol, error)
}
SymbolsProvider provides document symbol information. Use this interface when you only need to query document symbols.