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
DiagnosticsBusis 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 callsadd_config()for each entry.
Structs§
- LspSupervisor
- One LSP subsystem per editor instance.
- LspSupervisor
Handle - 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. - Restart
Report - 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. - Supervisor
Snapshot - Wait-free read view of supervisor state.
Type Aliases§
- Actor
Key - Stable key for an actor: workspace root + server id. Public
so
SupervisorSnapshotconsumers can pattern-match on the actor map without re-typing the tuple.