Open-Source Wikis

/

Helix

/

Packages

/

helix-lsp

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 helix-lsp-types companion crate (9,900 LOC) is the protocol type definitions, vendored to keep helix-lsp self-contained.

Purpose

helix-lsp is a thin, async LSP client. It owns:

  • A Client per running language server (one process, one stdin/stdout pair).
  • A Registry of clients keyed by LanguageServerId, indexed per language.
  • A JSON-RPC layer translating typed requests to serde_json payloads.
  • A Transport that pumps the child process and a file_event watcher for workspace/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 requests

Key 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 response

The 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_servers is the Registry. Documents register and deregister themselves on open/close.
  • helix-term/src/handlers/completion, signature_help, diagnostics, document_highlight, and document_links are 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-dap is 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 existing call::<Method>(params) helpers.
  • Wiring a new feature flag: add to LanguageServerFeature in helix-core/src/syntax/config.rs and gate calls on it via Document::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::initialize in client.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.