Skip to main content

Module supervisor

Module supervisor 

Source
Expand description

LspSupervisor – the per-buffer attachment manager (Phase 4.1.h).

Glues the wire-side primitives (ServerHandle, DocSync, DiagnosticsBus, DiagnosticsLayer, LspLogger) into one editor-facing facade. The App holds exactly one LspSupervisor; everything else flows through it.

§What the supervisor owns

  • Config registry. A Vec<Arc<ServerConfig>> – the curated builtins plus user overrides.
  • Per-(workspace, server-id) actors. One spawned actor per pair. Reused across buffers in the same workspace.
  • Per-(workspace, server-id) DocSync. One per actor. Tracks every URI the actor cares about.
  • Per-URI attachments. Which (workspace, server-id) actors care about each URI. A buffer can have multiple attachments (rust-analyzer + a clippy bridge for .rs).
  • Shared logger (cloned into every actor; cloned for subsystem-level events like supervisor decisions).
  • Shared diagnostics layer (every actor’s DiagnosticsBus is pumped into it via a tokio task spawned at actor-creation time).

§Why URI as the key (not BufferId)

lattice-lsp is below the UI layer in the crate graph; it has no concept of BufferId (which lives in lattice-ui-tui). URIs are the LSP-native identifier and map 1:1 to file paths the editor cares about. The App maintains its own BufferId → Uri mapping and threads URIs into the supervisor’s API.

§Lifecycle

  App::open_file(path, text)
    -> supervisor.open_buffer(path, text).await
        -> match_configs(path) -> [ServerConfig...]
        -> for each config:
             ensure_actor(workspace_root, &config).await
             ensure_doc_sync(actor)
             doc_sync.open(uri, language_id, text)
             record (workspace, server_id) in attachments[uri]
        -> return list of attached ServerHandles

  App::apply_edit(uri, edit)
    -> supervisor.record_edit(uri, edit)
        -> for each attached (workspace, server_id):
             syncs[(workspace, server_id)].record_edit(uri, edit)

  App::idle_flush(uri)  // 50ms after last edit
    -> supervisor.flush(uri)
        -> for each attached server: doc_sync.flush(uri)

  App::close_buffer(uri)
    -> supervisor.close_buffer(uri).await
        -> for each attached server: doc_sync.close(uri)
        -> drop the URI from attachments
        -> diagnostics.clear_uri(uri)

§Server reuse

Two .rs files in the same Cargo workspace share one rust-analyzer actor. Two .rs files in different workspaces get two actors (different roots = different indexed views). Two distinct languages in the same workspace get two actors (different ids).

§What the supervisor does NOT do (yet)

  • Per-feature dispatch (hover / goto-def / references / …). The supervisor’s job is attachment and lifecycle; the App calls servers_for(uri) and walks the list to issue feature requests. Per-feature merging (ranking, first-non-empty, union-dedupe) lands in 4.2 alongside the nav features.
  • Server crash recovery. Today’s actor detects pipe close and resolves pending with ActorGone; the supervisor’s restart-with-backoff logic lands in 4.4.
  • Reading lsp.toml – that’s the App’s responsibility (lattice-lsp has no parser dependencies). The App calls add_config() for each entry.

Structs§

LspSupervisor
One LSP subsystem per editor instance.
LspSupervisorHandle
Editor-facing handle to the LSP subsystem. Cheap to clone (every field is Arc-shaped internally); the App holds one instance and shares it with helpers that need to read or mutate supervisor state.
RestartReport
4.4.d: result of a successful LspSupervisor::restart_server (or the handle-side equivalent). The App surfaces these to the user in the echo area (“restarted rust in 1 workspace; 4 buffers replayed”) and tests assert on them.
SupervisorSnapshot
Wait-free read view of supervisor state.

Type Aliases§

ActorKey
Stable key for an actor: workspace root + server id. Public so SupervisorSnapshot consumers can pattern-match on the actor map without re-typing the tuple.