Skip to main content

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}