lsp

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package lsp is a from-scratch Language Server Protocol client — the way Kram gets real semantic navigation (diagnostics, go-to-definition, find-references) instead of only grep/glob's text matching. Like internal/mcp, this is hand-rolled JSON-RPC 2.0 rather than an SDK dependency, but the wire framing is different: LSP servers speak `Content-Length: N\r\n\r\n<json>` over stdio, not MCP's newline-delimited JSON, so it needs its own transport rather than reusing internal/mcp's.

This implements one vertical slice, not the whole protocol: initialize, textDocument/didOpen (+didClose, to reopen cleanly on every query), textDocument/publishDiagnostics, textDocument/definition and textDocument/references. No code actions, no rename, no call hierarchy, hover is not implemented.

Index

Constants

View Source
const (
	SeverityError       = 1
	SeverityWarning     = 2
	SeverityInformation = 3
	SeverityHint        = 4
)

Diagnostic severities, per the LSP spec (textDocument/publishDiagnostics).

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is one live connection to one language server process. Every exported method is safe to call from multiple goroutines.

func (*Client) Close

func (c *Client) Close()

Close shuts the server connection down. Safe to call more than once.

type Diagnostic

type Diagnostic struct {
	Range    Range  `json:"range"`
	Severity int    `json:"severity,omitempty"`
	Code     any    `json:"code,omitempty"`
	Source   string `json:"source,omitempty"`
	Message  string `json:"message"`
}

Diagnostic is one server-reported problem with a file.

func (Diagnostic) SeverityLabel

func (d Diagnostic) SeverityLabel() string

SeverityLabel renders a diagnostic's severity as short lowercase text ("error", "warning", "info", "hint") for display in tool output.

type Location

type Location struct {
	URI   string `json:"uri"`
	Range Range  `json:"range"`
}

Location is a range within one file, identified by URI.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager owns every language server connection for one workspace, one per language, started lazily on first use. Nothing here touches a filesystem or spawns a process at construction time — the extension table itself is built lazily on first ClientFor call — so building a Manager at daemon startup (as tools.NewRegistry does) costs nothing and starts nothing, satisfying the "no server starts before it's actually asked for" invariant.

func NewManager

func NewManager(workspace string) *Manager

NewManager builds a manager scoped to workspace. Cheap and side-effect free: no config file is read and no process is started until the first ClientFor call.

func (*Manager) ClientFor

func (m *Manager) ClientFor(ctx context.Context, relPath string) (*Client, error)

ClientFor returns the language server connection that owns relPath's extension, starting it if this is the first request for that language in this workspace. Concurrent callers requesting the same language before it's finished starting all wait on the same in-flight attempt rather than racing to start it twice.

A missing extension mapping, a missing/unusable server binary, or a failed initialize handshake all come back as a plain error — this method never panics and never brings down the caller; it's the tool layer's job to turn that error into "LSP capability unavailable for language X: <reason>" text for the model, per Kram's rule that a third-party tool being broken costs only its own capability.

func (*Manager) Close

func (m *Manager) Close()

Close shuts down every language server this manager started (including any still mid-startup — Close waits for those to finish before closing them, so nothing is left running as an orphan after the daemon exits).

func (*Manager) Definition

func (m *Manager) Definition(ctx context.Context, relPath string, line, character int) ([]Location, error)

Definition resolves the symbol at a 0-indexed line/character in relPath.

func (*Manager) Diagnostics

func (m *Manager) Diagnostics(ctx context.Context, relPath string) ([]Diagnostic, error)

Diagnostics returns whatever the language server has published for relPath after (re)opening it fresh.

func (*Manager) DisplayPath

func (m *Manager) DisplayPath(uri string) string

DisplayPath converts a Location's URI to a path suitable for showing the model: workspace-relative when the location is inside the workspace (the common case), an absolute path when it isn't (e.g. a definition resolving into GOROOT or node_modules), and the raw URI as a last resort if it isn't even a file:// URI.

func (*Manager) References

func (m *Manager) References(ctx context.Context, relPath string, line, character int) ([]Location, error)

References finds every reference to the symbol at a 0-indexed line/character in relPath, including the declaration itself.

type Position

type Position struct {
	Line      int `json:"line"`
	Character int `json:"character"`
}

Position is 0-indexed on both axes, the native LSP convention: line 0 is the file's first line, character 0 is the first column. Kram's LSP tools intentionally keep this convention rather than translating to 1-indexed, and document it in each tool's description instead — see internal/daemon/tools/lsp.go.

type Range

type Range struct {
	Start Position `json:"start"`
	End   Position `json:"end"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL