helix-editor/helix
helix-lsp
The Language Server Protocol client. Manages spawning, communicating with, and routing messages to LSP servers. 4,000 lines of Rust. The 9,900 LOC) is the protocol type definitions, vendored to keep helix-lsp-types companion crate (helix-lsp self-contained.
Purpose
helix-lsp is a thin, async LSP client. It owns:
- A
Clientper running language server (one process, one stdin/stdout pair). - A
Registryof clients keyed byLanguageServerId, indexed per language. - A JSON-RPC layer translating typed requests to
serde_jsonpayloads. - A
Transportthat pumps the child process and afile_eventwatcher forworkspace/didChangeWatchedFiles.
Helix supports multiple servers per language (since 22.05 and consolidated through 25.07). Each LanguageConfiguration in languages.toml lists language servers by name; the Registry instantiates them lazily on first use.
Directory layout
helix-lsp/src
├── lib.rs # Registry, OffsetEncoding, util:: helpers (~40k chars)
├── client.rs # Client: per-server state, request/notification API (~65k chars)
├── transport.rs # Async stdio framing, content-length parsing (~15k chars)
├── jsonrpc.rs # Call, Output, Id, Params types (vendored RPC layer)
├── file_event.rs # File watcher → workspace/didChangeWatchedFiles
└── file_operations.rs # File create/rename/delete LSP requestsKey abstractions
| Type | File | Purpose |
|---|---|---|
Client |
client.rs |
Per-server state. Spawns the process, sends initialize, tracks pending requests, exposes typed methods. |
Registry |
lib.rs |
SlotMap<LanguageServerId, Arc<Client>>. Resolves servers per language, restarts crashed processes, broadcasts did_change_configuration. |
Transport |
transport.rs |
Reads/writes Content-Length-framed JSON over the child process pipes. |
Call (enum in jsonrpc) |
jsonrpc.rs |
Request, Notification, or Response. |
OffsetEncoding |
lib.rs |
UTF-8 / UTF-16 / UTF-32 — controls position translation. UTF-16 is the default but the negotiated value wins. |
LanguageServerFeatures |
re-exported from helix-core |
Per-server feature flags (format, goto-definition, inlay-hints, etc.). |
LspProgressMap |
lib.rs |
Tracks $/progress tokens for the spinner UI. |
Submodule util |
lib.rs |
Free-standing helpers: lsp_pos_to_pos, pos_to_lsp_pos, range_to_lsp_range, generate_transaction_from_edits, apply_workspace_edit. |
Position encoding
LSP positions are line/character pairs but the meaning of "character" depends on the negotiated OffsetEncoding. Helix translates between LSP positions and rope char-offsets for every request and response. The util::lsp_pos_to_pos and util::pos_to_lsp_pos helpers are central — most bugs in this crate involve off-by-one or encoding mismatches.
Lifecycle
sequenceDiagram
participant Editor
participant Reg as Registry
participant Cli as Client
participant Tx as Transport
participant Server as LSP Server (child)
Editor->>Reg: get_or_start(language, name, root)
Reg->>Cli: spawn process
Cli->>Tx: stdin/stdout pair
Cli->>Server: initialize (JSON-RPC)
Server-->>Cli: InitializeResult (capabilities)
Cli->>Server: initialized
Editor->>Cli: text_document_did_open
Editor->>Cli: text_document_did_change
Server-->>Cli: publishDiagnostics
Cli-->>Editor: Call::Notification(diagnostics)
Editor->>Cli: completion (request)
Cli->>Server: textDocument/completion
Server-->>Cli: CompletionResponse
Cli-->>Editor: Call::MethodCall responseThe Application event loop in helix-term selects over the registry's combined stream (SelectAll<UnboundedReceiverStream<Call>>) and dispatches each Call to the appropriate handler in helix-term/src/application.rs::handle_language_server_message.
File watching
Some servers send workspace/didChangeWatchedFiles registrations when they want to be notified about config files (e.g. tsconfig.json, Cargo.toml). file_event.rs installs a notify watcher per registration and forwards events back to the server.
Workspace edits
util::apply_workspace_edit (lib.rs) handles WorkspaceEdit payloads: text edits, file creates/renames/deletes, and snippet edits. File operations are gated on the corresponding LanguageServerFeatures flag.
Integration points
Editor::language_serversis theRegistry. Documents register and deregister themselves on open/close.helix-term/src/handlers/completion,signature_help,diagnostics,document_highlight, anddocument_linksare async hooks that issue LSP requests and translate responses into editor state.- LSP commands (goto-definition, code action, rename) live in
helix-term/src/commands/lsp.rs. - DAP and LSP share the JSON-RPC framing approach;
helix-dapis a structurally similar but separate implementation because the protocols differ.
Entry points for modification
- Adding a new request type: add a method to
Client(client.rs) using the existingcall::<Method>(params)helpers. - Wiring a new feature flag: add to
LanguageServerFeatureinhelix-core/src/syntax/config.rsand gate calls on it viaDocument::has_language_server_with_feature. - Updating LSP types: edit
helix-lsp-types/src/— they are not regenerated, just hand-curated. - Changing position encoding negotiation: see
Client::initializeinclient.rs.
For the user-facing list of LSP features, see features/language-servers.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.