lattice_lsp/events.rs
1// `linkme`'s distributed-slice expansion uses `#[link_section]`
2// declarations, which the workspace's `unsafe_code = "deny"`
3// lint flags. Same shape `lattice-config::core_options` and
4// `lattice-protocol::event_registry` use.
5#![allow(unsafe_code)]
6
7//! LSP-owned editor-bus events (M.5.3.b).
8//!
9//! Three concrete event types replacing the LSP variants that
10//! used to live in the central `lattice_protocol::Event` enum.
11//! Each registers via [`lattice_protocol::register_event!`] so
12//! `:describe-events` (M.5.3.c) and any future event-introspection
13//! tooling sees them. The editor's
14//! [`lattice_runtime::EventBus::publish_typed`] /
15//! [`lattice_runtime::EventBus::subscribe_typed`] surface dispatches
16//! and downcasts.
17//!
18//! Why these live here: the central `Event` enum was a single
19//! sealed type that every feature crate had to edit to publish.
20//! Moving LSP events to `lattice-lsp` puts them under their
21//! owner's authority -- same model `lattice-mode` uses for the
22//! `Mode` trait.
23
24use std::path::{Path, PathBuf};
25use std::sync::Arc;
26
27use lattice_protocol::event::AppliedEdit;
28use lattice_protocol::ids::DocumentId;
29
30/// Fired after `lsp-mode` activates on a buffer and the
31/// supervisor has queued the `textDocument/didOpen`. The
32/// semantic counterpart to LSP's didOpen on the editor's bus:
33/// the buffer is now LSP-tracked. Subscribers (statusline,
34/// future plugin hooks, telemetry) react without polling
35/// `App::active_modes`.
36///
37/// Carries only the `DocumentId` so the event can be published
38/// from `LspMode::on_activate` without the mode needing a
39/// host-side path lookup. Subscribers that need the buffer's
40/// path resolve it themselves via their App handle.
41#[derive(Debug, Clone)]
42pub struct LspBufferAttached {
43 pub id: DocumentId,
44}
45
46lattice_protocol::register_event!(
47 LspBufferAttached,
48 "lsp.buffer-attached",
49 "Fired after lsp-mode activates on a buffer and the \
50 supervisor's didOpen request has been queued.",
51 "lattice-lsp",
52);
53
54/// Fired when `lsp-mode` deactivates on a buffer and the
55/// supervisor has sent `textDocument/didClose` to attached
56/// servers. The buffer remains open in the editor; only LSP
57/// tracking ends. Server connection persists if other buffers
58/// are still attached.
59#[derive(Debug, Clone)]
60pub struct LspBufferDetached {
61 pub id: DocumentId,
62}
63
64lattice_protocol::register_event!(
65 LspBufferDetached,
66 "lsp.buffer-detached",
67 "Fired after lsp-mode deactivates and the supervisor \
68 has sent didClose to attached servers.",
69 "lattice-lsp",
70);
71
72/// Fired when [`crate::LspLogger::log`] appends a record to a
73/// log ring (subsystem-wide when `server_id` is `None`,
74/// per-server otherwise). Subscribers (the App's drain hook)
75/// refresh open `*lsp*` / `*lsp:<server>*` /
76/// `*lsp:<server>:trace*` help buffers from the logger
77/// snapshot so log views update live as records arrive.
78///
79/// Carries primitive `String` fields rather than typed
80/// `LogLevel` / `LogSource` enums because the legacy bus
81/// shape predated the typed-event surface. Future revisions
82/// may switch to typed enums now that the boundary moved
83/// inside `lattice-lsp`; subscribers can re-snapshot through
84/// the logger when they need the typed value.
85#[derive(Debug, Clone)]
86pub struct LspLogPushed {
87 /// `None` for subsystem-wide records; `Some(id)` per-instance.
88 /// Pairs with `workspace` -- both are `Some` for per-instance
89 /// records (post-B'.2), both `None` for subsystem-wide.
90 pub server_id: Option<Arc<str>>,
91 /// `None` for subsystem-wide records; `Some(path)` per-instance.
92 /// The workspace root the `(server_id, workspace)` actor was
93 /// spawned against. Two `rust-analyzer` instances on different
94 /// workspaces stay distinct via this field.
95 pub workspace: Option<Arc<Path>>,
96 /// Severity tag (`"trace"`, `"debug"`, `"info"`, `"warn"`,
97 /// `"error"`).
98 pub level: String,
99 /// Source tag (`"client"`, `"stderr"`, `"log"`, `"show"`,
100 /// `"trace"`).
101 pub source: String,
102 /// The record's message text.
103 pub message: String,
104}
105
106lattice_protocol::register_event!(
107 LspLogPushed,
108 "lsp.log-pushed",
109 "Fired when LspLogger::log appends a record to a log ring.",
110 "lattice-lsp",
111);
112
113/// Fired when an attached server sends a `$/progress`
114/// notification (LSP §3.16 work-done progress). The host
115/// accumulates progress entries by (server_id, token) and
116/// surfaces the most recent active one in the modeline.
117///
118/// `kind` carries the progress lifecycle phase:
119/// - `LspProgressKind::Begin` -- new operation started.
120/// - `LspProgressKind::Report` -- ongoing update.
121/// - `LspProgressKind::End` -- operation finished; host
122/// removes the entry.
123#[derive(Debug, Clone)]
124pub struct LspProgressUpdate {
125 pub server_id: Arc<str>,
126 pub token: String,
127 pub kind: LspProgressKind,
128 pub title: Option<String>,
129 pub message: Option<String>,
130 /// 0..=100 if the server reports it; `None` for
131 /// indeterminate progress.
132 pub percentage: Option<u32>,
133 pub cancellable: bool,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub enum LspProgressKind {
138 Begin,
139 Report,
140 End,
141}
142
143lattice_protocol::register_event!(
144 LspProgressUpdate,
145 "lsp.progress-update",
146 "Fired when a server sends $/progress. Host accumulates by \
147 (server_id, token) and surfaces in the modeline.",
148 "lattice-lsp",
149);
150
151/// L2: fired when a server sends `experimental/serverStatus`
152/// (rust-analyzer's readiness extension). `quiescent` is the
153/// gold-standard "ready" signal -- `false` while the server is
154/// scanning / indexing, `true` once it has finished and features
155/// (hover, diagnostics, completion) are reliable. `health` is the
156/// server's self-assessed condition. The host turns this into the
157/// modeline's ✓/⟳/✗ readiness glyph.
158#[derive(Debug, Clone)]
159pub struct LspServerStatusChanged {
160 pub server_id: Arc<str>,
161 pub quiescent: bool,
162 pub health: LspServerHealth,
163 pub message: Option<String>,
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum LspServerHealth {
168 Ok,
169 Warning,
170 Error,
171}
172
173lattice_protocol::register_event!(
174 LspServerStatusChanged,
175 "lsp.server-status-changed",
176 "Fired when a server sends experimental/serverStatus (quiescent / \
177 health). Host surfaces readiness in the modeline.",
178 "lattice-lsp",
179);
180
181/// Fired when an actor task exits (4.4.d). The supervisor
182/// subscribes to this event to drive crash-detection
183/// auto-restart: `Clean` exits (after a `Shutdown` command)
184/// are ignored; `Unexpected` exits trigger the
185/// supervisor's restart-with-backoff path. The supervisor
186/// is the single subscriber today; plugin telemetry can
187/// subscribe later without further plumbing.
188#[derive(Debug, Clone)]
189pub struct LspActorExited {
190 pub server_id: Arc<str>,
191 pub reason: LspActorExitReason,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub enum LspActorExitReason {
196 /// Actor exited because the supervisor (or last
197 /// `ServerHandle`) requested shutdown -- no restart wanted.
198 Clean,
199 /// Actor exited because read_loop returned None (pipe
200 /// closed, server crashed) or another unexpected condition.
201 /// The supervisor restarts via the same path `:lsp-restart`
202 /// uses, gated on the restart-history backoff window.
203 Unexpected,
204}
205
206lattice_protocol::register_event!(
207 LspActorExited,
208 "lsp.actor-exited",
209 "Fired when an actor task exits. The supervisor uses this for \
210 crash-detection auto-restart; subscribers can also tap for \
211 telemetry.",
212 "lattice-lsp",
213);
214
215/// 4.4.g: fired when an attached server requests
216/// `workspace/inlayHint/refresh`. The host invalidates every
217/// cached inlay-hint entry for buffers attached to this
218/// server; the next render tick re-issues `inlayHint` and
219/// repopulates.
220///
221/// The actor replies `null` to the server inline (the request
222/// is fire-and-forget from the LSP spec's perspective); the
223/// cache invalidation flows over the typed event bus so the
224/// App-side drain can mutate `lsp_inlay_hints_cache` without
225/// the actor needing buffer-state access.
226#[derive(Debug, Clone)]
227pub struct LspInlayHintRefresh {
228 pub server_id: Arc<str>,
229}
230
231lattice_protocol::register_event!(
232 LspInlayHintRefresh,
233 "lsp.inlay-hint-refresh",
234 "Fired when a server requests workspace/inlayHint/refresh; the host \
235 invalidates cached hints for buffers attached to that server.",
236 "lattice-lsp",
237);
238
239/// 4.4.i: fired when an attached server requests
240/// `workspace/semanticTokens/refresh`. The host invalidates
241/// every cached semantic-tokens entry for buffers attached to
242/// this server; the next render tick re-issues
243/// `semanticTokens/full` (forcing a fresh baseline rather than
244/// a delta against a now-stale `result_id`) and repopulates.
245///
246/// Same shape as `LspInlayHintRefresh`: the actor replies
247/// `null` to the server inline, and cache invalidation flows
248/// over the typed event bus so the App-side drain can mutate
249/// `lsp_semantic_tokens_cache` without the actor reaching into
250/// buffer state.
251#[derive(Debug, Clone)]
252pub struct LspSemanticTokensRefresh {
253 pub server_id: Arc<str>,
254}
255
256lattice_protocol::register_event!(
257 LspSemanticTokensRefresh,
258 "lsp.semantic-tokens-refresh",
259 "Fired when a server requests workspace/semanticTokens/refresh; the \
260 host invalidates cached semantic tokens for buffers attached to \
261 that server.",
262 "lattice-lsp",
263);
264
265/// 4.4.j: fired when an attached server requests
266/// `workspace/diagnostic/refresh`. The host invalidates the
267/// pull-diagnostics `result_id` cache for every buffer attached
268/// to this server; the next render tick re-issues
269/// `textDocument/diagnostic` (without a `previous_result_id`,
270/// so the server must answer with a `Full` report) and the
271/// `DiagnosticsLayer` re-applies.
272///
273/// Same shape as the inlay-hint / semantic-tokens refreshes:
274/// actor replies `null` inline and publishes this event so
275/// the App-side drain can mutate `lsp_pull_diagnostics_cache`
276/// without the actor reaching into buffer state.
277#[derive(Debug, Clone)]
278pub struct LspDiagnosticRefresh {
279 pub server_id: Arc<str>,
280}
281
282lattice_protocol::register_event!(
283 LspDiagnosticRefresh,
284 "lsp.diagnostic-refresh",
285 "Fired when a server requests workspace/diagnostic/refresh; the host \
286 invalidates the pull-diagnostics result_id cache for buffers attached \
287 to that server.",
288 "lattice-lsp",
289);
290
291/// 4.5.d: server-issued `workspace/codeLens/refresh` request
292/// surfaces as this typed bus event. Same shape as the other
293/// refresh events; the App-side drain evicts cached code
294/// lenses for every buffer attached to the requesting server.
295#[derive(Debug, Clone)]
296pub struct LspCodeLensRefresh {
297 pub server_id: Arc<str>,
298}
299
300lattice_protocol::register_event!(
301 LspCodeLensRefresh,
302 "lsp.code-lens-refresh",
303 "Fired when a server requests workspace/codeLens/refresh; the host \
304 evicts cached code lenses for buffers attached to that server so the \
305 next render tick re-issues textDocument/codeLens.",
306 "lattice-lsp",
307);
308
309/// Fired when a document buffer changes *and* `lsp-mode` is
310/// active for that buffer (M.5.5). The per-actor fan-in
311/// (`crate::fan_in`) subscribes via
312/// `EventBus::subscribe_typed::<LspDocumentChanged>` and
313/// forwards each `AppliedEdit` as a `RecordEdit` actor command.
314///
315/// `path` is `None` for path-less buffers (no URI to map);
316/// fan_in skips those. The event isn't published at all when
317/// `lsp-mode` is inactive, so fan_in never sees edits the user
318/// has gated off.
319#[derive(Debug, Clone)]
320pub struct LspDocumentChanged {
321 pub id: DocumentId,
322 pub path: Option<PathBuf>,
323 pub version: u64,
324 pub edits: Vec<AppliedEdit>,
325}
326
327lattice_protocol::register_event!(
328 LspDocumentChanged,
329 "lsp.document-changed",
330 "Fired on every buffer edit when lsp-mode is active. The \
331 per-actor fan-in turns each AppliedEdit into a didChange \
332 payload bound for attached LSP servers.",
333 "lattice-lsp",
334);