lattice_lsp/features.rs
1//! Typed wrappers around [`crate::ServerHandle::request_with_cancel`]
2//! for the LSP navigation features (DESIGN.md §5.4 + Phase 4.2).
3//!
4//! Each method is a thin shim: build the typed `*Params`, call
5//! `request_with_cancel(method_name, params, token)`, return a
6//! [`Pending<R>`] over the typed response. The wire formats live in
7//! `lsp-types` (`textDocument/hover` ↔ [`Hover`], etc.); the
8//! wrappers exist so call sites don't sprinkle method-name strings
9//! and so cancellation is impossible to forget.
10//!
11//! **Cancellation discipline.** Every wrapper takes a
12//! [`lattice_protocol::CancellationToken`]. The App passes a fresh
13//! token per request and flips it on motion / Esc / mode change so
14//! a stale response from a slow server can't drop a popup over the
15//! user's new cursor position. Local-only cancellation today (the
16//! server keeps computing; we drop its reply on arrival) -- wire-
17//! level `$/cancelRequest` is a Phase 4.2 polish item.
18//!
19//! **Multi-server merge.** Wrappers run *per-server*. The App's
20//! per-feature dispatcher fires the same wrapper across every
21//! server attached to the buffer and merges according to the
22//! per-feature strategy (hover: concat with `--- name ---` sep;
23//! definition / references: concat + dedup by URI+range; symbols:
24//! concat; completion: concat + dedup by `text`).
25
26use lattice_protocol::CancellationToken;
27use lsp_types::{
28 CompletionParams, CompletionResponse, DocumentFormattingParams, DocumentRangeFormattingParams,
29 DocumentSymbolParams, DocumentSymbolResponse, GotoDefinitionParams, GotoDefinitionResponse,
30 Hover, HoverParams, Location, PrepareRenameResponse, ReferenceParams, RenameParams,
31 SignatureHelp, SignatureHelpParams, TextDocumentPositionParams, TextEdit, WorkspaceEdit,
32 WorkspaceSymbolParams,
33 request::{
34 GotoDeclarationParams, GotoDeclarationResponse, GotoImplementationParams,
35 GotoImplementationResponse, GotoTypeDefinitionParams, GotoTypeDefinitionResponse,
36 },
37};
38
39use crate::actor::ServerHandle;
40use crate::pending::Pending;
41
42impl ServerHandle {
43 /// `textDocument/hover` (DESIGN.md §5.4 / docs/dev/notes/lsp-features.md).
44 /// Returns `None` when the server has nothing to say at the
45 /// cursor position. The body's `contents` field is what the
46 /// renderer feeds into the hover popup markdown pipeline
47 /// (App-side `HoverPopup`); the optional `range` highlights the symbol
48 /// hovered (renderer integration is Phase 4.2.b polish).
49 pub fn hover(&self, params: HoverParams, token: CancellationToken) -> Pending<Option<Hover>> {
50 self.request_with_cancel("textDocument/hover", params, token)
51 }
52
53 /// `textDocument/definition` (DESIGN.md §5.4 /
54 /// docs/dev/notes/lsp-features.md). Returns the location(s) where the
55 /// symbol under the cursor is defined. `GotoDefinitionResponse`
56 /// is an enum: single `Location`, `Vec<Location>`, or
57 /// `Vec<LocationLink>` (richer links carrying origin range).
58 /// Phase 4.2.c picks one (single → jump, multiple → list).
59 pub fn goto_definition(
60 &self,
61 params: GotoDefinitionParams,
62 token: CancellationToken,
63 ) -> Pending<Option<GotoDefinitionResponse>> {
64 self.request_with_cancel("textDocument/definition", params, token)
65 }
66
67 /// `textDocument/declaration` (DESIGN.md §5.4 /
68 /// docs/dev/notes/lsp-features.md). `gD` family. Same response shape as
69 /// `goto_definition`; servers usually point at the *forward
70 /// declaration* (header file in C / extern statement in Rust)
71 /// rather than the implementation. Multi-server merge dedups
72 /// by (uri, range.start) like definition.
73 pub fn goto_declaration(
74 &self,
75 params: GotoDeclarationParams,
76 token: CancellationToken,
77 ) -> Pending<Option<GotoDeclarationResponse>> {
78 self.request_with_cancel("textDocument/declaration", params, token)
79 }
80
81 /// `textDocument/typeDefinition` (DESIGN.md §5.4). `gy` family.
82 /// "Where is the *type* of this expression defined?" Useful
83 /// for stepping from a value to its struct / class / interface.
84 pub fn goto_type_definition(
85 &self,
86 params: GotoTypeDefinitionParams,
87 token: CancellationToken,
88 ) -> Pending<Option<GotoTypeDefinitionResponse>> {
89 self.request_with_cancel("textDocument/typeDefinition", params, token)
90 }
91
92 /// `textDocument/implementation` (DESIGN.md §5.4). `gI` family.
93 /// "Where are the implementations of this trait / interface?"
94 /// Often returns multiple locations (one per impl); we share
95 /// definition's pick-or-list dispatch.
96 pub fn goto_implementation(
97 &self,
98 params: GotoImplementationParams,
99 token: CancellationToken,
100 ) -> Pending<Option<GotoImplementationResponse>> {
101 self.request_with_cancel("textDocument/implementation", params, token)
102 }
103
104 /// `textDocument/references` (DESIGN.md §5.4). Returns every
105 /// reference site to the symbol under the cursor. The
106 /// `include_declaration` flag sits on `ReferenceContext` inside
107 /// `ReferenceParams`; callers usually want it `true` for `gr`
108 /// (vim convention).
109 pub fn references(
110 &self,
111 params: ReferenceParams,
112 token: CancellationToken,
113 ) -> Pending<Option<Vec<Location>>> {
114 self.request_with_cancel("textDocument/references", params, token)
115 }
116
117 /// `textDocument/documentSymbol` (DESIGN.md §5.4). Returns the
118 /// symbol outline for the buffer. Response is either flat
119 /// `Vec<SymbolInformation>` (legacy) or hierarchical
120 /// `Vec<DocumentSymbol>` (modern). Phase 4.2.e flattens the
121 /// hierarchy to a list with depth-indent for the picker.
122 pub fn document_symbol(
123 &self,
124 params: DocumentSymbolParams,
125 token: CancellationToken,
126 ) -> Pending<Option<DocumentSymbolResponse>> {
127 self.request_with_cancel("textDocument/documentSymbol", params, token)
128 }
129
130 /// `workspace/symbol` (DESIGN.md §5.4). Workspace-scoped
131 /// symbol search. The `query` string filters server-side; the
132 /// editor sends `query=""` for an everything-list and
133 /// re-queries as the user types in the picker (Phase 4.2.f).
134 ///
135 /// Response shape: `WorkspaceSymbolResponse` -- the
136 /// `Flat(Vec<SymbolInformation>)` variant is the legacy
137 /// shape every server emits; the
138 /// `Nested(Vec<WorkspaceSymbol>)` variant (LSP 3.17+) lets
139 /// the server defer the `location.range` and have the
140 /// client fire `workspaceSymbol/resolve` on accept. The
141 /// editor handles both shapes -- legacy rows jump
142 /// immediately; nested rows with a `WorkspaceLocation`
143 /// route through the resolve path before jumping.
144 pub fn workspace_symbol(
145 &self,
146 params: WorkspaceSymbolParams,
147 token: CancellationToken,
148 ) -> Pending<Option<lsp_types::WorkspaceSymbolResponse>> {
149 self.request_with_cancel("workspace/symbol", params, token)
150 }
151
152 /// `workspaceSymbol/resolve` (LSP 3.17+, Phase 4.2 follow-up).
153 /// Sent for `WorkspaceSymbol` rows whose `location` came back
154 /// as the `WorkspaceLocation` (URI-only) variant; the server
155 /// returns the same symbol with `location.range` populated.
156 /// Wired into the `:workspace-symbols` picker accept path
157 /// when the server advertises
158 /// `workspaceSymbolProvider.resolveProvider`.
159 pub fn workspace_symbol_resolve(
160 &self,
161 symbol: lsp_types::WorkspaceSymbol,
162 token: CancellationToken,
163 ) -> Pending<lsp_types::WorkspaceSymbol> {
164 self.request_with_cancel("workspaceSymbol/resolve", symbol, token)
165 }
166
167 /// 4.5.a: `textDocument/prepareCallHierarchy`. Asks the
168 /// server which callable(s) live at the cursor; the
169 /// response feeds the subsequent
170 /// [`Self::call_hierarchy_incoming_calls`] /
171 /// [`Self::call_hierarchy_outgoing_calls`] request.
172 /// Servers typically return a single-element vec for a
173 /// position inside a function body; macros / overloads
174 /// can produce multiple items. `None` means "no callable
175 /// here", which short-circuits the navigation.
176 pub fn prepare_call_hierarchy(
177 &self,
178 params: lsp_types::CallHierarchyPrepareParams,
179 token: CancellationToken,
180 ) -> Pending<Option<Vec<lsp_types::CallHierarchyItem>>> {
181 self.request_with_cancel("textDocument/prepareCallHierarchy", params, token)
182 }
183
184 /// 4.5.a: `callHierarchy/incomingCalls`. Given a
185 /// `CallHierarchyItem` from `prepareCallHierarchy`,
186 /// returns the call sites that invoke it. Each
187 /// `CallHierarchyIncomingCall` carries the *caller* item
188 /// (`from`) plus the ranges inside the caller where the
189 /// call appears (`from_ranges`).
190 pub fn call_hierarchy_incoming_calls(
191 &self,
192 params: lsp_types::CallHierarchyIncomingCallsParams,
193 token: CancellationToken,
194 ) -> Pending<Option<Vec<lsp_types::CallHierarchyIncomingCall>>> {
195 self.request_with_cancel("callHierarchy/incomingCalls", params, token)
196 }
197
198 /// 4.5.a: `callHierarchy/outgoingCalls`. Symmetric peer
199 /// of `incomingCalls`; given a callable, returns the
200 /// callables it invokes plus the call sites inside its
201 /// own body (`from_ranges` on the caller's text).
202 pub fn call_hierarchy_outgoing_calls(
203 &self,
204 params: lsp_types::CallHierarchyOutgoingCallsParams,
205 token: CancellationToken,
206 ) -> Pending<Option<Vec<lsp_types::CallHierarchyOutgoingCall>>> {
207 self.request_with_cancel("callHierarchy/outgoingCalls", params, token)
208 }
209
210 /// 4.5.b: `textDocument/prepareTypeHierarchy`. Same
211 /// preparation shape as `prepareCallHierarchy` but
212 /// targets type relationships (super/sub-types). Used by
213 /// `:lsp-supertypes` / `:lsp-subtypes`.
214 pub fn prepare_type_hierarchy(
215 &self,
216 params: lsp_types::TypeHierarchyPrepareParams,
217 token: CancellationToken,
218 ) -> Pending<Option<Vec<lsp_types::TypeHierarchyItem>>> {
219 self.request_with_cancel("textDocument/prepareTypeHierarchy", params, token)
220 }
221
222 /// 4.5.b: `typeHierarchy/supertypes`. Returns the types
223 /// the given item is a subtype of (e.g. trait
224 /// supertraits, class superclasses).
225 pub fn type_hierarchy_supertypes(
226 &self,
227 params: lsp_types::TypeHierarchySupertypesParams,
228 token: CancellationToken,
229 ) -> Pending<Option<Vec<lsp_types::TypeHierarchyItem>>> {
230 self.request_with_cancel("typeHierarchy/supertypes", params, token)
231 }
232
233 /// 4.5.b: `typeHierarchy/subtypes`. Returns the types that
234 /// subtype the given item (e.g. trait implementors,
235 /// class subclasses).
236 pub fn type_hierarchy_subtypes(
237 &self,
238 params: lsp_types::TypeHierarchySubtypesParams,
239 token: CancellationToken,
240 ) -> Pending<Option<Vec<lsp_types::TypeHierarchyItem>>> {
241 self.request_with_cancel("typeHierarchy/subtypes", params, token)
242 }
243
244 /// 4.5.g: `textDocument/moniker`. Returns the stable
245 /// cross-project identifier(s) for the symbol at the
246 /// cursor -- e.g. SCIP / LSIF emit monikers so a build
247 /// indexer can join symbols across repos. The response
248 /// is `Option<Vec<Moniker>>`; each moniker has a `scheme`,
249 /// `identifier`, optional `kind`, and `unique` level.
250 /// `:lsp-moniker` ex-command surfaces the list as an echo.
251 pub fn moniker(
252 &self,
253 params: lsp_types::MonikerParams,
254 token: CancellationToken,
255 ) -> Pending<Option<Vec<lsp_types::Moniker>>> {
256 self.request_with_cancel("textDocument/moniker", params, token)
257 }
258
259 /// 4.5.c: `textDocument/documentLink`. Returns the
260 /// hyperlink ranges inside the document (URLs, imports,
261 /// `file://` references emitted by certain LSPs). The
262 /// host caches the response per (BufferId, doc_version)
263 /// and consults the cache when `gx` is pressed in Normal
264 /// mode -- the first link whose range covers the cursor
265 /// wins.
266 pub fn document_link(
267 &self,
268 params: lsp_types::DocumentLinkParams,
269 token: CancellationToken,
270 ) -> Pending<Option<Vec<lsp_types::DocumentLink>>> {
271 self.request_with_cancel("textDocument/documentLink", params, token)
272 }
273
274 /// 4.5.c: `documentLink/resolve`. Lazy-resolves a link
275 /// that arrived without a `target` -- the server fills it
276 /// in on demand. `gx` triggers this when the cached link
277 /// at the cursor lacks `target` AND the server advertises
278 /// `documentLinkProvider.resolveProvider`. The resolved
279 /// link is then followed in the same gesture.
280 pub fn document_link_resolve(
281 &self,
282 link: lsp_types::DocumentLink,
283 token: CancellationToken,
284 ) -> Pending<lsp_types::DocumentLink> {
285 self.request_with_cancel("documentLink/resolve", link, token)
286 }
287
288 /// 4.5.d: `textDocument/codeLens`. Returns above-line
289 /// clickable annotations (run / debug / references etc.).
290 /// The host caches per (BufferId, doc_version) and renders
291 /// `title`-only items inline; clicking (`:lsp-code-lens`)
292 /// fires the lens's `command` via `executeCommand`.
293 pub fn code_lens(
294 &self,
295 params: lsp_types::CodeLensParams,
296 token: CancellationToken,
297 ) -> Pending<Option<Vec<lsp_types::CodeLens>>> {
298 self.request_with_cancel("textDocument/codeLens", params, token)
299 }
300
301 /// 4.5.d: `codeLens/resolve`. Lazy-resolves a code lens
302 /// whose `command` is missing -- some servers return only
303 /// the range + minimal title in the batched response and
304 /// fill in the actual command on demand. Triggered when
305 /// the user accepts a lens whose `command` is `None`.
306 pub fn code_lens_resolve(
307 &self,
308 lens: lsp_types::CodeLens,
309 token: CancellationToken,
310 ) -> Pending<lsp_types::CodeLens> {
311 self.request_with_cancel("codeLens/resolve", lens, token)
312 }
313
314 /// 4.5.e: `textDocument/documentColor`. Returns the color
315 /// literals (hex / named) the server detected in the
316 /// document, each with its `range` and resolved `color`
317 /// (red/green/blue/alpha in [0.0, 1.0]). The host caches
318 /// per (BufferId, doc_version) and feeds the cache to a
319 /// future renderer swatch overlay; `:lsp-color-presentation`
320 /// reads the cached entry at the cursor to drive the
321 /// alternative-format picker.
322 pub fn document_color(
323 &self,
324 params: lsp_types::DocumentColorParams,
325 token: CancellationToken,
326 ) -> Pending<Vec<lsp_types::ColorInformation>> {
327 self.request_with_cancel("textDocument/documentColor", params, token)
328 }
329
330 /// 4.5.e: `textDocument/colorPresentation`. Given a color +
331 /// the range it covers, the server returns alternative
332 /// presentations the user can replace the literal with
333 /// (e.g. `"#ff0000"` -> `"rgb(255, 0, 0)"`, `"red"`).
334 /// `:lsp-color-presentation` opens these as a picker;
335 /// accept splices the chosen `text_edit` (or `label` as
336 /// a simple replace) at the literal's range.
337 pub fn color_presentation(
338 &self,
339 params: lsp_types::ColorPresentationParams,
340 token: CancellationToken,
341 ) -> Pending<Vec<lsp_types::ColorPresentation>> {
342 self.request_with_cancel("textDocument/colorPresentation", params, token)
343 }
344
345 /// 4.5.f: `textDocument/linkedEditingRange`. Returns the
346 /// ranges that should be edited in lockstep when the user
347 /// types inside one of them (e.g. matching HTML/JSX tag
348 /// pairs). Wire wrapper only -- the multi-cursor
349 /// shadow-edit machinery the feature needs is its own
350 /// slice; see the matrix row's strong-reason defer.
351 pub fn linked_editing_range(
352 &self,
353 params: lsp_types::LinkedEditingRangeParams,
354 token: CancellationToken,
355 ) -> Pending<Option<lsp_types::LinkedEditingRanges>> {
356 self.request_with_cancel("textDocument/linkedEditingRange", params, token)
357 }
358
359 /// 4.5.h: `textDocument/inlineValue`. Returns the live
360 /// values to render at each line during a debug session
361 /// (e.g. `n = 5`, `result = "ok"`). Wire wrapper only --
362 /// without a debug-adapter integration (DAP) the trigger
363 /// surface that would render these is absent; see the
364 /// matrix row's strong-reason defer.
365 pub fn inline_value(
366 &self,
367 params: lsp_types::InlineValueParams,
368 token: CancellationToken,
369 ) -> Pending<Option<Vec<lsp_types::InlineValue>>> {
370 self.request_with_cancel("textDocument/inlineValue", params, token)
371 }
372
373 // 4.5.i: `textDocument/inlineCompletion` (LSP 3.18 /
374 // pre-spec). **Strong-reason defer at the wire layer**:
375 // lsp-types 0.97 doesn't export `InlineCompletionParams`
376 // / `InlineCompletionResponse` (the request types live
377 // behind a `proposed` feature flag the workspace doesn't
378 // opt into). The server-cap field
379 // `inline_completion_provider` is present but no client-
380 // facing types are accessible. Revisit when lsp-types
381 // promotes inlineCompletion out of `proposed` -- the
382 // Insert-mode autopilot trigger + ghost-text overlay
383 // will land in the same slice.
384
385 /// `textDocument/completion` (DESIGN.md §5.4 / Phase 4.2.g).
386 /// Returns either an array of items or an `isIncomplete` list
387 /// the editor must re-query as the user types more. The
388 /// completion pipeline (`lattice-completion`) registers a
389 /// `gen:lsp-completion` source backed by this call.
390 pub fn completion(
391 &self,
392 params: CompletionParams,
393 token: CancellationToken,
394 ) -> Pending<Option<CompletionResponse>> {
395 self.request_with_cancel("textDocument/completion", params, token)
396 }
397
398 /// `textDocument/formatting` (DESIGN.md §5.4 / Phase 4.3).
399 /// Whole-buffer formatter; the response is a `Vec<TextEdit>`
400 /// the editor applies as a single undo unit. Single-server
401 /// strategy per the architecture doc -- highest-priority
402 /// server with `documentFormattingProvider` advertised wins.
403 pub fn formatting(
404 &self,
405 params: DocumentFormattingParams,
406 token: CancellationToken,
407 ) -> Pending<Option<Vec<TextEdit>>> {
408 self.request_with_cancel("textDocument/formatting", params, token)
409 }
410
411 /// `textDocument/rangeFormatting` (Phase 4.3). Same shape as
412 /// `formatting` but bounded to the supplied range -- bound to
413 /// the `=` operator on motions / objects / Visual selection.
414 pub fn range_formatting(
415 &self,
416 params: DocumentRangeFormattingParams,
417 token: CancellationToken,
418 ) -> Pending<Option<Vec<TextEdit>>> {
419 self.request_with_cancel("textDocument/rangeFormatting", params, token)
420 }
421
422 /// `textDocument/signatureHelp` (Phase 4.3). Trigger-character
423 /// driven (`,`, `(`) when the server advertises the trigger.
424 /// Response carries the active signature + parameter; renderer
425 /// integration overlays a popup similar to hover.
426 pub fn signature_help(
427 &self,
428 params: SignatureHelpParams,
429 token: CancellationToken,
430 ) -> Pending<Option<SignatureHelp>> {
431 self.request_with_cancel("textDocument/signatureHelp", params, token)
432 }
433
434 /// `textDocument/prepareRename` (Phase 4.3). Validates the
435 /// cursor is on a renameable identifier and returns the
436 /// placeholder + range. `None` means "the symbol can't be
437 /// renamed here" -- the editor echoes and bails. Optional
438 /// in the spec (servers may skip prepareRename and accept
439 /// rename directly), so callers should treat `None` from
440 /// `prepare_rename` as "fall through to rename".
441 pub fn prepare_rename(
442 &self,
443 params: TextDocumentPositionParams,
444 token: CancellationToken,
445 ) -> Pending<Option<PrepareRenameResponse>> {
446 self.request_with_cancel("textDocument/prepareRename", params, token)
447 }
448
449 /// `textDocument/rename` (Phase 4.3). Renames the symbol
450 /// under cursor across the workspace. Response is a
451 /// `WorkspaceEdit` with per-file `Vec<TextEdit>`s; the
452 /// editor applies all edits as a single undoable unit.
453 pub fn rename(
454 &self,
455 params: RenameParams,
456 token: CancellationToken,
457 ) -> Pending<Option<WorkspaceEdit>> {
458 self.request_with_cancel("textDocument/rename", params, token)
459 }
460
461 /// `textDocument/willSave` (Phase 4.3 -- notification).
462 /// Fired before the editor commits the buffer to disk.
463 /// Servers use this to clean up state, finalise indexing,
464 /// or prepare didSave-driven validation.
465 pub fn will_save(
466 &self,
467 params: lsp_types::WillSaveTextDocumentParams,
468 ) -> crate::error::LspResult<()> {
469 self.notify("textDocument/willSave", params)
470 }
471
472 /// `textDocument/willSaveWaitUntil` (Phase 4.3). Same
473 /// trigger as `will_save` but request-shaped: server
474 /// returns a `Vec<TextEdit>` to apply pre-save.
475 /// format-on-save flows through here when the server
476 /// advertises `will_save_wait_until` on its
477 /// `TextDocumentSyncOptions.save`.
478 pub fn will_save_wait_until(
479 &self,
480 params: lsp_types::WillSaveTextDocumentParams,
481 token: CancellationToken,
482 ) -> Pending<Option<Vec<TextEdit>>> {
483 self.request_with_cancel("textDocument/willSaveWaitUntil", params, token)
484 }
485
486 /// `textDocument/didSave` (Phase 4.3 -- notification).
487 /// Fired after a successful disk write. Carries the
488 /// post-save text iff the server's
489 /// `TextDocumentSaveRegistrationOptions.include_text` is
490 /// true.
491 pub fn did_save(
492 &self,
493 params: lsp_types::DidSaveTextDocumentParams,
494 ) -> crate::error::LspResult<()> {
495 self.notify("textDocument/didSave", params)
496 }
497
498 /// 4.4.k: `workspace/didChangeConfiguration` (notification).
499 /// Fan-out fires when any `lsp.*` typed option changes
500 /// (via `OptionChanged` cascade). The notification's
501 /// `settings` carries the full `lsp` subtree from the
502 /// merged config TOML; most servers ignore the inline
503 /// payload and pull fresh values via
504 /// `workspace/configuration` (the host's drain serves
505 /// from the same tree), but servers that read inline get
506 /// the values too. Notification-only -- no response, no
507 /// cancellation token.
508 pub fn did_change_configuration(
509 &self,
510 params: lsp_types::DidChangeConfigurationParams,
511 ) -> crate::error::LspResult<()> {
512 self.notify("workspace/didChangeConfiguration", params)
513 }
514
515 /// 4.4.l: `workspace/didChangeWatchedFiles` (notification).
516 /// Fan-out fires when the host's file-watcher observes an
517 /// fs event whose path matches a glob from this server's
518 /// `client/registerCapability`-issued
519 /// `DidChangeWatchedFilesRegistrationOptions`. The host
520 /// batches per-tick into one notification per server (the
521 /// LSP spec allows multiple `FileEvent`s in one payload);
522 /// servers receive the events in arrival order.
523 /// Notification-only -- no response, no cancellation.
524 pub fn did_change_watched_files(
525 &self,
526 params: lsp_types::DidChangeWatchedFilesParams,
527 ) -> crate::error::LspResult<()> {
528 self.notify("workspace/didChangeWatchedFiles", params)
529 }
530
531 /// 4.4.m: `workspace/willCreateFiles` (request).
532 /// Pre-create hook -- server MAY return a `WorkspaceEdit`
533 /// the client applies BEFORE the actual file is created on
534 /// disk (e.g. add an import to a sibling module). Callers
535 /// must gate on `Capabilities::supports_will_create_files` +
536 /// filter the URIs against the registration's
537 /// `FileOperationFilter`s before issuing. The host pump
538 /// (when wired) blocks the create path on the response;
539 /// timeouts skip the edits and proceed.
540 pub fn will_create_files(
541 &self,
542 params: lsp_types::CreateFilesParams,
543 cancel: lattice_protocol::CancellationToken,
544 ) -> crate::pending::Pending<Option<lsp_types::WorkspaceEdit>> {
545 self.request_with_cancel("workspace/willCreateFiles", params, cancel)
546 }
547
548 /// 4.4.m: `workspace/didCreateFiles` (notification).
549 /// Post-create fan-out. The wire wrapper is straight-line;
550 /// trigger discipline (when to fire) lives in the host
551 /// save / create paths.
552 pub fn did_create_files(
553 &self,
554 params: lsp_types::CreateFilesParams,
555 ) -> crate::error::LspResult<()> {
556 self.notify("workspace/didCreateFiles", params)
557 }
558
559 /// 4.4.m: `workspace/willRenameFiles` (request). Same
560 /// response shape as `willCreateFiles`. Triggered when the
561 /// user renames a file in-place (`:saveas` follow-up that
562 /// removes the original); server returns edits to keep
563 /// imports / references in sync with the new path.
564 pub fn will_rename_files(
565 &self,
566 params: lsp_types::RenameFilesParams,
567 cancel: lattice_protocol::CancellationToken,
568 ) -> crate::pending::Pending<Option<lsp_types::WorkspaceEdit>> {
569 self.request_with_cancel("workspace/willRenameFiles", params, cancel)
570 }
571
572 /// 4.4.m: `workspace/didRenameFiles` (notification).
573 pub fn did_rename_files(
574 &self,
575 params: lsp_types::RenameFilesParams,
576 ) -> crate::error::LspResult<()> {
577 self.notify("workspace/didRenameFiles", params)
578 }
579
580 /// 4.4.m: `workspace/willDeleteFiles` (request). Server
581 /// returns edits to clean up references before the delete.
582 pub fn will_delete_files(
583 &self,
584 params: lsp_types::DeleteFilesParams,
585 cancel: lattice_protocol::CancellationToken,
586 ) -> crate::pending::Pending<Option<lsp_types::WorkspaceEdit>> {
587 self.request_with_cancel("workspace/willDeleteFiles", params, cancel)
588 }
589
590 /// 4.4.m: `workspace/didDeleteFiles` (notification).
591 pub fn did_delete_files(
592 &self,
593 params: lsp_types::DeleteFilesParams,
594 ) -> crate::error::LspResult<()> {
595 self.notify("workspace/didDeleteFiles", params)
596 }
597
598 /// `textDocument/codeAction` (Phase 4.3). Returns the list
599 /// of quick fixes / refactors / source actions available
600 /// for the supplied range. Each item carries either an
601 /// inline `edit` (apply directly), a `command` (route
602 /// through `executeCommand`), or both. Items with neither
603 /// need `codeAction/resolve` to fill in the missing
604 /// `edit`.
605 pub fn code_action(
606 &self,
607 params: lsp_types::CodeActionParams,
608 token: CancellationToken,
609 ) -> Pending<Option<lsp_types::CodeActionResponse>> {
610 self.request_with_cancel("textDocument/codeAction", params, token)
611 }
612
613 /// `codeAction/resolve` (Phase 4.3). Lazy-resolve a
614 /// codeAction that arrived without `edit`. Servers that
615 /// advertise `codeActionProvider.resolveProvider` may
616 /// return action stubs (label + kind only) and fill in
617 /// `edit` here, on demand. Cheaper than computing every
618 /// edit upfront.
619 pub fn code_action_resolve(
620 &self,
621 action: lsp_types::CodeAction,
622 token: CancellationToken,
623 ) -> Pending<lsp_types::CodeAction> {
624 self.request_with_cancel("codeAction/resolve", action, token)
625 }
626
627 /// `workspace/executeCommand` (Phase 4.3). Run a server-
628 /// registered command identified by string id. Used by
629 /// codeAction items that carry a `command` rather than an
630 /// inline `edit`. Response shape varies per command --
631 /// servers usually return null + side-effect via
632 /// `workspace/applyEdit`.
633 pub fn execute_command(
634 &self,
635 params: lsp_types::ExecuteCommandParams,
636 token: CancellationToken,
637 ) -> Pending<Option<serde_json::Value>> {
638 self.request_with_cancel("workspace/executeCommand", params, token)
639 }
640
641 /// `textDocument/onTypeFormatting` (Phase 4.3). Trigger-
642 /// character driven formatting that adjusts surrounding
643 /// whitespace / indentation as the user types (commonly
644 /// fires on `;`, `}`, `\n` for C-family). Returns the same
645 /// `Vec<TextEdit>` shape as the other formatting flavours.
646 pub fn on_type_formatting(
647 &self,
648 params: lsp_types::DocumentOnTypeFormattingParams,
649 token: CancellationToken,
650 ) -> Pending<Option<Vec<TextEdit>>> {
651 self.request_with_cancel("textDocument/onTypeFormatting", params, token)
652 }
653
654 /// 4.4.e: `textDocument/documentHighlight`. Returns the
655 /// references to the symbol at the cursor inside the
656 /// current document; each entry carries an optional `kind`
657 /// (`Text` / `Read` / `Write`) so the overlay can paint
658 /// reads / writes differently. Response is `Vec<DocumentHighlight>`
659 /// or null when the cursor isn't on a known symbol.
660 pub fn document_highlight(
661 &self,
662 params: lsp_types::DocumentHighlightParams,
663 token: CancellationToken,
664 ) -> Pending<Option<Vec<lsp_types::DocumentHighlight>>> {
665 self.request_with_cancel("textDocument/documentHighlight", params, token)
666 }
667
668 /// 4.4.e: `textDocument/selectionRange`. Given a slice of
669 /// positions (almost always one cursor position), the server
670 /// returns the structural ranges that surround each
671 /// position, walking outward (token → expression → statement
672 /// → block → function → module). The operator-side
673 /// `expand-region` / `shrink-region` consumes the linked list.
674 pub fn selection_range(
675 &self,
676 params: lsp_types::SelectionRangeParams,
677 token: CancellationToken,
678 ) -> Pending<Option<Vec<lsp_types::SelectionRange>>> {
679 self.request_with_cancel("textDocument/selectionRange", params, token)
680 }
681
682 /// 4.4.f: `textDocument/foldingRange`. Returns line-based
683 /// fold extents (with optional character columns + kind tag
684 /// like `comment` / `imports` / `region`). The host's
685 /// per-tick pump refreshes the cache when the document
686 /// version bumps; `:set foldmethod=lsp` reads from the
687 /// cache.
688 pub fn folding_range(
689 &self,
690 params: lsp_types::FoldingRangeParams,
691 token: CancellationToken,
692 ) -> Pending<Option<Vec<lsp_types::FoldingRange>>> {
693 self.request_with_cancel("textDocument/foldingRange", params, token)
694 }
695
696 /// 4.4.g: `textDocument/inlayHint`. Returns inline
697 /// virtual-text annotations (type hints, parameter
698 /// names, etc.) over the requested range. Each hint
699 /// carries its position, a label (single string or
700 /// composite `Vec<InlayHintLabelPart>`), and optional
701 /// kind / padding / tooltip fields. The host caches the
702 /// response per buffer-version and the renderer splices
703 /// each hint into the line span list.
704 pub fn inlay_hint(
705 &self,
706 params: lsp_types::InlayHintParams,
707 token: CancellationToken,
708 ) -> Pending<Option<Vec<lsp_types::InlayHint>>> {
709 self.request_with_cancel("textDocument/inlayHint", params, token)
710 }
711
712 /// 4.4.g follow-up: `inlayHint/resolve`. Lazy-resolves a
713 /// single inlay hint -- the server populates the
714 /// `tooltip` and `text_edits` fields it skipped on the
715 /// initial batched response. Servers gate this behind
716 /// the `InlayHintOptions.resolve_provider` capability;
717 /// callers must check [`crate::Capabilities::supports_inlay_hint_resolve`]
718 /// before issuing. The wrapper ships ahead of the
719 /// interaction UX (no gesture is wired to fire it today
720 /// -- see lsp-features.md for the deferral rationale);
721 /// this lets future work plug a trigger into a stable
722 /// surface.
723 pub fn inlay_hint_resolve(
724 &self,
725 hint: lsp_types::InlayHint,
726 token: CancellationToken,
727 ) -> Pending<lsp_types::InlayHint> {
728 self.request_with_cancel("inlayHint/resolve", hint, token)
729 }
730
731 /// 4.4.h: `textDocument/semanticTokens/full`. Returns the
732 /// whole-buffer semantic token list in the LSP
733 /// relative-position varint encoding (5 u32s per token:
734 /// deltaLine, deltaStart, length, tokenType, modifiers
735 /// bitfield). The host decodes against the server's
736 /// `SemanticTokensLegend` (cached at attach time via
737 /// [`crate::Capabilities::semantic_token_types`] /
738 /// `semantic_token_modifiers`). Wrapped response variant
739 /// `Tokens` carries `data: Vec<u32>` plus an optional
740 /// `result_id` for the 4.4.i delta path.
741 pub fn semantic_tokens_full(
742 &self,
743 params: lsp_types::SemanticTokensParams,
744 token: CancellationToken,
745 ) -> Pending<Option<lsp_types::SemanticTokensResult>> {
746 self.request_with_cancel("textDocument/semanticTokens/full", params, token)
747 }
748
749 /// 4.4.i: `textDocument/semanticTokens/full/delta`. Sends
750 /// the previous response's `result_id`; server either
751 /// returns a new full token list (`Tokens` variant) when
752 /// it can't compute a delta cheaply, or a list of edit
753 /// operations (`TokensDelta`) the host applies to the
754 /// cached raw token vec. Edit shape:
755 /// `SemanticTokensEdit { start, delete_count, data }`
756 /// where `start` is the index into the previous flat
757 /// token vec, `delete_count` is how many `SemanticToken`
758 /// entries to remove, and `data` is what to splice in.
759 /// The host re-decodes the spliced vec into absolute
760 /// positions.
761 pub fn semantic_tokens_full_delta(
762 &self,
763 params: lsp_types::SemanticTokensDeltaParams,
764 token: CancellationToken,
765 ) -> Pending<Option<lsp_types::SemanticTokensFullDeltaResult>> {
766 self.request_with_cancel("textDocument/semanticTokens/full/delta", params, token)
767 }
768
769 /// 4.4.i: `textDocument/semanticTokens/range`. Viewport-
770 /// bounded request -- the host can issue this for very
771 /// large files to skip decoding tokens outside the
772 /// visible window. Returns a plain `SemanticTokens`
773 /// (no `Delta` variant for the range flavour).
774 /// Exposed as a typed wrapper today; the v1 pump uses
775 /// full/delta. Viewport-aware fetching can switch over
776 /// in a follow-up without re-touching the wire path.
777 pub fn semantic_tokens_range(
778 &self,
779 params: lsp_types::SemanticTokensRangeParams,
780 token: CancellationToken,
781 ) -> Pending<Option<lsp_types::SemanticTokensRangeResult>> {
782 self.request_with_cancel("textDocument/semanticTokens/range", params, token)
783 }
784
785 /// 4.4.j: `textDocument/diagnostic`. Pull-based
786 /// diagnostics (LSP 3.17). The server returns either a
787 /// `Full` report (entire diagnostics list for the URI,
788 /// plus optional `result_id` for the next delta) or an
789 /// `Unchanged` report ("no diagnostics moved since the
790 /// previous `result_id`"). The host caches the
791 /// `result_id` per buffer-version and threads it back in
792 /// `DocumentDiagnosticParams.previous_result_id` so the
793 /// server can answer `Unchanged` cheaply. Used when a
794 /// server prefers pull over push, or alongside push for
795 /// servers that support both.
796 pub fn document_diagnostic(
797 &self,
798 params: lsp_types::DocumentDiagnosticParams,
799 token: CancellationToken,
800 ) -> Pending<lsp_types::DocumentDiagnosticReportResult> {
801 self.request_with_cancel("textDocument/diagnostic", params, token)
802 }
803
804 /// 4.4.j: `workspace/diagnostic`. Workspace-wide pull --
805 /// returns reports for every URI the server tracks
806 /// diagnostics for, even ones the client hasn't opened.
807 /// Wrapper ships ahead of the host pump (strong-reason
808 /// deferred; the per-document pump already covers every
809 /// open buffer's diagnostics, and the closed-file
810 /// workspace pull is niche -- see lsp-features.md). The
811 /// callable exists so future workspace-view rework has a
812 /// stable surface.
813 pub fn workspace_diagnostic(
814 &self,
815 params: lsp_types::WorkspaceDiagnosticParams,
816 token: CancellationToken,
817 ) -> Pending<lsp_types::WorkspaceDiagnosticReportResult> {
818 self.request_with_cancel("workspace/diagnostic", params, token)
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 #![allow(clippy::unwrap_used, clippy::panic)]
825 use super::*;
826
827 use lsp_types::{
828 Position as LspPosition, TextDocumentIdentifier, TextDocumentPositionParams, Uri,
829 };
830 use std::str::FromStr;
831
832 fn fake_uri() -> Uri {
833 Uri::from_str("file:///tmp/test.rs").unwrap()
834 }
835
836 fn position_params(line: u32, character: u32) -> TextDocumentPositionParams {
837 TextDocumentPositionParams {
838 text_document: TextDocumentIdentifier { uri: fake_uri() },
839 position: LspPosition { line, character },
840 }
841 }
842
843 /// Compile-time presence check: every wrapper has the expected
844 /// signature `(Params, CancellationToken) -> Pending<Option<R>>`.
845 /// The function bodies never run -- this asserts the API surface.
846 /// `drop` (rather than `let _`) sidesteps clippy's
847 /// `let_underscore_future` lint -- we genuinely don't want to
848 /// poll these futures, the compile-time bounds check is the
849 /// whole point.
850 #[allow(dead_code)]
851 fn _api_surface_compiles(handle: &ServerHandle, token: CancellationToken) {
852 let pos = position_params(0, 0);
853 drop::<Pending<Option<Hover>>>(handle.hover(
854 HoverParams {
855 text_document_position_params: pos.clone(),
856 work_done_progress_params: Default::default(),
857 },
858 token.clone(),
859 ));
860 drop::<Pending<Option<GotoDefinitionResponse>>>(handle.goto_definition(
861 GotoDefinitionParams {
862 text_document_position_params: pos.clone(),
863 work_done_progress_params: Default::default(),
864 partial_result_params: Default::default(),
865 },
866 token.clone(),
867 ));
868 drop::<Pending<Option<GotoDeclarationResponse>>>(handle.goto_declaration(
869 GotoDeclarationParams {
870 text_document_position_params: pos.clone(),
871 work_done_progress_params: Default::default(),
872 partial_result_params: Default::default(),
873 },
874 token.clone(),
875 ));
876 drop::<Pending<Option<GotoTypeDefinitionResponse>>>(handle.goto_type_definition(
877 GotoTypeDefinitionParams {
878 text_document_position_params: pos.clone(),
879 work_done_progress_params: Default::default(),
880 partial_result_params: Default::default(),
881 },
882 token.clone(),
883 ));
884 drop::<Pending<Option<GotoImplementationResponse>>>(handle.goto_implementation(
885 GotoImplementationParams {
886 text_document_position_params: pos.clone(),
887 work_done_progress_params: Default::default(),
888 partial_result_params: Default::default(),
889 },
890 token.clone(),
891 ));
892 drop::<Pending<Option<Vec<Location>>>>(handle.references(
893 ReferenceParams {
894 text_document_position: pos,
895 work_done_progress_params: Default::default(),
896 partial_result_params: Default::default(),
897 context: lsp_types::ReferenceContext {
898 include_declaration: true,
899 },
900 },
901 token.clone(),
902 ));
903 drop::<Pending<Option<DocumentSymbolResponse>>>(handle.document_symbol(
904 DocumentSymbolParams {
905 text_document: TextDocumentIdentifier { uri: fake_uri() },
906 work_done_progress_params: Default::default(),
907 partial_result_params: Default::default(),
908 },
909 token.clone(),
910 ));
911 drop::<Pending<Option<lsp_types::WorkspaceSymbolResponse>>>(handle.workspace_symbol(
912 WorkspaceSymbolParams {
913 query: "foo".into(),
914 work_done_progress_params: Default::default(),
915 partial_result_params: Default::default(),
916 },
917 token.clone(),
918 ));
919 drop::<Pending<Option<CompletionResponse>>>(handle.completion(
920 CompletionParams {
921 text_document_position: position_params(0, 0),
922 work_done_progress_params: Default::default(),
923 partial_result_params: Default::default(),
924 context: None,
925 },
926 token,
927 ));
928 }
929}