lsp

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jun 14, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package lsp bridges an LSP server's symbol operations to Båge's byte-range edit model. The load-bearing part is the pure conversion from LSP positions (zero-based line + UTF-16 code-unit character) to UTF-8 byte offsets, and from a protocol.WorkspaceEdit to a slice of locator.FileEdit. Per code_graph_architecture.md §10 the UTF-16↔UTF-8 conversion is centralized here at the single LSP boundary so the rest of Båge stays byte-addressed.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ByteOffset

func ByteOffset(src []byte, pos protocol.Position) (int, error)

ByteOffset maps a zero-based LSP position to a UTF-8 byte offset within src.

An LSP Position is (Line, Character) where Line counts '\n'-terminated lines from zero and Character counts UTF-16 code units from the line start. A rune in the astral planes (> 0xFFFF) is encoded as a surrogate pair and therefore counts as TWO UTF-16 code units; every other rune counts as one. Because Båge addresses regions by UTF-8 byte offset, this function walks src rune-by-rune, advancing the byte cursor by each rune's UTF-8 width while debiting the requested UTF-16 budget.

Clamping follows the LSP spec: a Line beyond the last line resolves to end of src; a Character beyond the line's content resolves to the line end (the byte index of the terminating '\n', or end of src for the final line). The '\n' itself is never crossed by the character walk.

Policy: the only rejected input is a malformed UTF-8 sequence encountered while consuming characters on the target line — Båge requires valid UTF-8 source, so rather than silently mis-count, ByteOffset returns an error. Empty src with a zero position yields offset 0.

func WorkspaceEditToFileEdits

func WorkspaceEditToFileEdits(we protocol.WorkspaceEdit, read func(path string) ([]byte, error)) ([]locator.FileEdit, error)

WorkspaceEditToFileEdits flattens a protocol.WorkspaceEdit into a slice of locator.FileEdit. Both representations are honored: the legacy we.Changes map (DocumentURI → []TextEdit) and the versioned we.DocumentChanges ([]TextDocumentEdit). For each TextEdit the file's current bytes are obtained via the injected read function and the edit's UTF-16 Range is converted to UTF-8 StartByte/EndByte via ByteOffset, centralizing the boundary conversion.

read is injected so callers (and tests) control file access; it is invoked at most once per distinct file URI. URIs are resolved to filesystem paths via DocumentURI.Filename(). Edits are returned grouped by file in a deterministic order: Changes first (in URI iteration order is non-deterministic, so files are not sorted here — ApplyFileEdits reverse-sorts per file by offset), then DocumentChanges. An error from read or from ByteOffset aborts and is wrapped.

Types

type Client

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

Client is a thin LSP client over a spawned language-server subprocess. It speaks JSON-RPC 2.0 (LSP framing) across the server's stdio and exposes only the minimal surface Båge needs: lifecycle (Initialize/Close) and symbol rename. All byte-offset conversion lives in the pure functions in convert.go; this type is glue. A Client is not safe for concurrent use.

func NewClient

func NewClient(ctx context.Context, command []string) (*Client, error)

NewClient spawns the LSP server described by command (e.g. []string{"gopls"}) and wires a JSON-RPC connection over its stdio. The connection's read loop is started immediately; incoming server-to-client requests (e.g. window/logMessage) are answered with method-not-found, which is sufficient for the rename path. The returned Client must be Closed to release the subprocess.

func NewClientFromConn

func NewClientFromConn(ctx context.Context, conn io.ReadWriteCloser) (*Client, error)

NewClientFromConn wires a Client over an already-established bidirectional connection — typically a net.Conn dialed to a language server listening on a TCP socket (e.g. a containerized gopls started with `gopls -listen`). Unlike NewClient there is no local subprocess, so Close tears down only the JSON-RPC connection and the supplied transport; lifecycle of the remote server is the caller's responsibility. conn is adopted by the Client and closed by Close.

func (*Client) Close

func (c *Client) Close(ctx context.Context) error

Close requests an orderly LSP shutdown (shutdown + exit), closes the connection and stdio, and waits for the subprocess to exit. Errors from each stage are joined into the returned error; a best-effort shutdown still proceeds to Close the connection.

func (*Client) Diagnostics added in v0.2.0

func (c *Client) Diagnostics(ctx context.Context, path, content string) ([]Diagnostic, error)

Diagnostics opens path in the language server (textDocument/didOpen with the given content) and collects the first textDocument/publishDiagnostics notification the server pushes for that document, mapping each entry to a Diagnostic in Båge's reporting shape. Unlike Rename, the result is delivered as a server→client NOTIFICATION (not a request response), so it is gathered from the read-loop handler via the Client's diags channel.

The caller must have completed Initialize first. Diagnostics blocks until the server publishes (the common case — a server publishes once it has analyzed the opened document) or ctx is done; on ctx expiry it returns the context error wrapped, so a server that never publishes surfaces as a clear timeout rather than a hang. An empty publish (a clean file) returns an empty, non-nil slice.

func (*Client) Initialize

func (c *Client) Initialize(ctx context.Context, rootURI protocol.DocumentURI) error

Initialize performs the LSP initialize/initialized handshake rooted at rootURI (a file:// URI for the workspace root).

func (*Client) Rename

func (c *Client) Rename(ctx context.Context, path, content string, line, col uint32, newName string) (protocol.WorkspaceEdit, error)

Rename opens the file at path, requests a textDocument/rename of the symbol at the zero-based (line, col) UTF-16 position, and returns the server's WorkspaceEdit. col is a UTF-16 code-unit offset per the LSP spec; convert the result to byte offsets with WorkspaceEditToFileEdits. content is the file's current text, sent via textDocument/didOpen so the server has an authoritative view before the rename.

type Diagnostic added in v0.2.0

type Diagnostic struct {
	// Severity is the human label ("Error", "Warning", "Information", "Hint", or
	// a numeric fallback for an unknown code).
	Severity string
	// Source names the diagnostic's origin (server-provided; may be "").
	Source string
	// Message is the diagnostic text.
	Message string
	// StartLine is the 1-based start line of the diagnostic range.
	StartLine int
	// StartCol is the 1-based start column of the diagnostic range.
	StartCol int
	// EndLine is the 1-based end line of the diagnostic range.
	EndLine int
	// EndCol is the 1-based end column of the diagnostic range.
	EndCol int
}

Diagnostic is one server-reported problem, flattened into Båge's reporting shape: a human severity string, the 1-based line/col range of the offending span, the message, and the diagnostic source (e.g. "compiler", "staticcheck"). It is what `bage diagnose --lsp` surfaces per textDocument/publishDiagnostics entry (SPEC §10.5). Lines and columns are 1-based, converted from the LSP wire protocol's 0-based positions.

Jump to

Keyboard shortcuts

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