lattice_lsp/cache.rs
1//! Per-buffer LSP response caches + outcome enums.
2//!
3//! Phase 5.2: extracted from `lattice-ui-tui::app` to their proper
4//! home in `lattice-lsp`. Every type here is renderer-agnostic
5//! pure data describing the *result* of an LSP request -- the
6//! shape the App's per-tick drain expects after the spawned task
7//! delivers its response.
8//!
9//! Cache types are keyed on `(BufferId, document_version)` so the
10//! pump can invalidate-by-version when the buffer mutates.
11//! Outcome enums carry the same key plus the actual payload (or
12//! a sentinel variant for `Empty` / `NoProvider` / etc.) so the
13//! drain can route to the appropriate echo / apply path.
14//!
15//! `lattice-ui-tui::app` re-exports every type from this module
16//! so existing `crate::app::HoverOutcome` etc. references in the
17//! host crate's source continue to resolve unchanged.
18
19use std::sync::Arc;
20
21use lattice_core::{BufferId, Fold};
22use lattice_protocol::position::Position;
23
24use crate::Uri;
25
26// ---- Selection range ---------------------------------------------------
27
28/// 4.4.e: cached `textDocument/selectionRange` chain anchored
29/// at a specific buffer + cursor position. Flat `Vec<Range>`
30/// (innermost first) instead of the LSP linked-list shape so
31/// the operator can index into it in O(1). Captured once on
32/// the first `:lsp-expand-region` after a fresh cursor; reused
33/// across subsequent expand/shrink steps until the cursor
34/// moves outside `ranges[0]` (the innermost) or the buffer
35/// changes.
36#[derive(Debug, Clone)]
37pub struct LspSelectionChain {
38 pub buffer_id: BufferId,
39 pub anchor_cursor: Position,
40 pub ranges: Vec<lsp_types::Range>,
41}
42
43/// 4.4.e: outcome of an in-flight `textDocument/selectionRange`
44/// request. The drain consumes one of these per response and
45/// either seats the chain into `App::lsp_selection_chain` or
46/// surfaces an error echo. `pending_step` carries whether the
47/// triggering invocation was `:lsp-expand-region` or the first
48/// step of `:lsp-shrink-region` (the latter is rare -- shrink
49/// without an existing chain is a user error -- but the drain
50/// handles it uniformly).
51#[derive(Debug, Clone)]
52pub enum SelectionRangeOutcome {
53 /// Server returned a non-empty chain; `ranges[0]` is the
54 /// innermost. The drain stores this and applies the first
55 /// step at `index = 0` (then bumps for expand).
56 Items {
57 anchor_cursor: Position,
58 anchor_buffer: BufferId,
59 ranges: Vec<lsp_types::Range>,
60 pending_step: SelectionRangeStep,
61 },
62 NoProvider,
63 Empty,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum SelectionRangeStep {
68 Expand,
69 Shrink,
70}
71
72// ---- Document highlight ------------------------------------------------
73
74/// 4.4.e: cached `textDocument/documentHighlight` result
75/// anchored at a specific buffer + cursor position. The
76/// renderer reads `highlights` to paint a soft overlay; the
77/// pump compares `cursor` to the live cursor to decide whether
78/// to invalidate and re-request.
79#[derive(Debug, Clone)]
80pub struct DocumentHighlightCache {
81 pub buffer_id: BufferId,
82 pub cursor: Position,
83 pub highlights: Vec<lsp_types::DocumentHighlight>,
84}
85
86/// 4.4.e: in-flight `documentHighlight` request outcome.
87#[derive(Debug, Clone)]
88pub enum DocumentHighlightOutcome {
89 Items {
90 buffer_id: BufferId,
91 cursor: Position,
92 highlights: Vec<lsp_types::DocumentHighlight>,
93 },
94 /// Server returned null (cursor not on a known symbol);
95 /// the pump clears the cache so we don't paint stale
96 /// highlights.
97 Empty { buffer_id: BufferId },
98}
99
100// ---- Folding range -----------------------------------------------------
101
102/// 4.4.f: cached `textDocument/foldingRange` response for one
103/// buffer. `document_version` is the buffer's
104/// [`lattice_core::Document`] version at the time the request
105/// was issued; the pump compares it against the live version
106/// to decide whether to refresh.
107#[derive(Debug, Clone)]
108pub struct LspFoldsCache {
109 pub document_version: u64,
110 pub folds: Vec<Fold>,
111}
112
113/// 4.4.f: in-flight `foldingRange` request outcome.
114#[derive(Debug, Clone)]
115pub enum FoldingRangeOutcome {
116 Items {
117 buffer_id: BufferId,
118 document_version: u64,
119 folds: Vec<Fold>,
120 },
121 Empty {
122 buffer_id: BufferId,
123 document_version: u64,
124 },
125}
126
127// ---- Inlay hint --------------------------------------------------------
128
129/// 4.4.g: cached `textDocument/inlayHint` response for one
130/// buffer. Keyed on `(BufferId, document_version)`; pump
131/// invalidates when the version changes. `hints` are sorted
132/// by position so the renderer can stop scanning once it
133/// walks past the current line.
134///
135/// 4.4.g viewport polish: `requested_first_line` /
136/// `requested_last_line` (inclusive, 0-based LSP line indices)
137/// record the line range that produced this cache entry. The
138/// pump refetches when the visible viewport scrolls outside
139/// this window plus an overscan margin -- so small scrolls
140/// stay cached, larger ones trigger a new request bounded to
141/// the new viewport. Small files where viewport + overscan
142/// covers the whole buffer cache exactly once and never
143/// refetch on scroll.
144#[derive(Debug, Clone)]
145pub struct LspInlayHintCache {
146 pub document_version: u64,
147 pub hints: Vec<lsp_types::InlayHint>,
148 pub requested_first_line: u32,
149 pub requested_last_line: u32,
150}
151
152/// 4.4.g: in-flight `inlayHint` request outcome. The
153/// `requested_*_line` pair rides through so the drain can seat
154/// the cache with the range that actually produced these
155/// hints (matters for the viewport pump -- subsequent scrolls
156/// reuse the cache only while the viewport sits inside that
157/// range).
158#[derive(Debug, Clone)]
159pub enum InlayHintOutcome {
160 Items {
161 buffer_id: BufferId,
162 document_version: u64,
163 hints: Vec<lsp_types::InlayHint>,
164 requested_first_line: u32,
165 requested_last_line: u32,
166 },
167 Empty {
168 buffer_id: BufferId,
169 document_version: u64,
170 requested_first_line: u32,
171 requested_last_line: u32,
172 },
173}
174
175// ---- Document link -----------------------------------------------------
176
177/// 4.5.c: per-buffer cache of `textDocument/documentLink`
178/// responses. Filled by the pump on document-version change;
179/// `gx` walks the entries looking for the first link whose
180/// range covers the cursor and follows it. Cache invalidates
181/// when the version changes.
182#[derive(Debug, Clone)]
183pub struct LspDocumentLinksCache {
184 pub document_version: u64,
185 pub links: Vec<lsp_types::DocumentLink>,
186}
187
188/// 4.5.c: outcome of an in-flight `textDocument/documentLink`
189/// request. `Empty` for server responses that returned no
190/// links (still updates the cache so we don't keep
191/// re-issuing for the same version).
192#[derive(Debug, Clone)]
193pub enum DocumentLinksOutcome {
194 Items {
195 buffer_id: BufferId,
196 document_version: u64,
197 links: Vec<lsp_types::DocumentLink>,
198 },
199 Empty {
200 buffer_id: BufferId,
201 document_version: u64,
202 },
203}
204
205// ---- Code lens ---------------------------------------------------------
206
207/// 4.5.d: per-buffer cache of `textDocument/codeLens` results.
208/// Filled by the pump on doc-version change or after
209/// `workspace/codeLens/refresh` evicts the entry. The
210/// `:lsp-code-lens` picker reads the cache; accept routes the
211/// chosen lens's `command` through `workspace/executeCommand`.
212#[derive(Debug, Clone)]
213pub struct LspCodeLensCache {
214 pub document_version: u64,
215 pub lenses: Vec<lsp_types::CodeLens>,
216 /// Which server produced the cached batch. Code-lens
217 /// `command` payloads are server-specific; routing the
218 /// `executeCommand` to the originating server keeps the
219 /// dispatch unambiguous when multiple servers attach.
220 pub server_id: Arc<str>,
221}
222
223/// 4.5.d: outcome of an in-flight `textDocument/codeLens`
224/// request. Carries the server id so the eviction-on-refresh
225/// path can match by server.
226#[derive(Debug, Clone)]
227pub enum CodeLensOutcome {
228 Items {
229 buffer_id: BufferId,
230 document_version: u64,
231 server_id: Arc<str>,
232 lenses: Vec<lsp_types::CodeLens>,
233 },
234 Empty {
235 buffer_id: BufferId,
236 document_version: u64,
237 },
238}
239
240// ---- Document color ----------------------------------------------------
241
242/// 4.5.e: per-buffer cache of color literals + their resolved
243/// values. Filled by the per-tick pump on document-version
244/// change; consumed by `:lsp-color-presentation`. Renderer
245/// swatch overlay queued -- today the cache only feeds the
246/// picker.
247#[derive(Debug, Clone)]
248pub struct LspDocumentColorCache {
249 pub document_version: u64,
250 pub colors: Vec<lsp_types::ColorInformation>,
251 /// Server that produced the cache. Routing `colorPresentation`
252 /// back to the same server keeps the alternatives consistent
253 /// (a CSS server's `rgb()` formats aren't useful for a Rust
254 /// `rgb!` macro).
255 pub server_id: Arc<str>,
256}
257
258/// 4.5.e: outcome of an in-flight `documentColor` request.
259#[derive(Debug, Clone)]
260pub enum DocumentColorOutcome {
261 Items {
262 buffer_id: BufferId,
263 document_version: u64,
264 server_id: Arc<str>,
265 colors: Vec<lsp_types::ColorInformation>,
266 },
267 Empty {
268 buffer_id: BufferId,
269 document_version: u64,
270 },
271}
272
273// ---- Semantic tokens ---------------------------------------------------
274
275/// 4.4.h: one decoded LSP semantic token, expanded from the
276/// server's relative-position varint encoding into absolute
277/// positions. `token_type` is the canonical name from the
278/// server's legend (e.g. `"keyword"`, `"function"`) so the
279/// renderer can pick a style without looking up the index.
280/// `length` is in utf-16 code units per the LSP spec.
281#[derive(Debug, Clone)]
282pub struct DecodedSemanticToken {
283 pub line: u32,
284 pub start_char: u32,
285 pub length: u32,
286 pub token_type: String,
287 /// Names of every modifier bit set on this token (legend
288 /// resolved). Empty when the server set no modifier bits.
289 pub modifiers: Vec<String>,
290}
291
292/// 4.4.h: cached decoded `textDocument/semanticTokens/full`
293/// response for one buffer. Same shape as the other LSP
294/// per-buffer caches: keyed on `(BufferId, document_version)`,
295/// invalidated by the pump when the version changes.
296///
297/// `result_id` is the server-issued tag the host sends back
298/// on the next `full/delta` request so the server knows which
299/// baseline to diff against (4.4.i).
300///
301/// `raw_data` (4.4.i) keeps the un-decoded `Vec<SemanticToken>`
302/// alongside the decoded view so delta edits can splice into
303/// it before the host re-decodes for the renderer.
304#[derive(Debug, Clone)]
305pub struct LspSemanticTokensCache {
306 pub document_version: u64,
307 pub result_id: Option<String>,
308 pub raw_data: Vec<lsp_types::SemanticToken>,
309 pub tokens: Vec<DecodedSemanticToken>,
310}
311
312/// 4.4.h: in-flight `semanticTokens/full` request outcome.
313/// 4.4.i extends with the `Delta` variant for the
314/// `full/delta` path.
315#[derive(Debug, Clone)]
316pub enum SemanticTokensOutcome {
317 Items {
318 buffer_id: BufferId,
319 document_version: u64,
320 result_id: Option<String>,
321 raw_data: Vec<lsp_types::SemanticToken>,
322 tokens: Vec<DecodedSemanticToken>,
323 },
324 /// 4.4.i: server returned a delta against `previous_result_id`.
325 /// The drain looks up the previous cache, splices the edits
326 /// into `raw_data`, and re-decodes using the legend captured
327 /// at request time.
328 Delta {
329 buffer_id: BufferId,
330 document_version: u64,
331 previous_result_id: String,
332 new_result_id: Option<String>,
333 edits: Vec<lsp_types::SemanticTokensEdit>,
334 token_types: Vec<lsp_types::SemanticTokenType>,
335 token_modifiers: Vec<lsp_types::SemanticTokenModifier>,
336 },
337 Empty {
338 buffer_id: BufferId,
339 document_version: u64,
340 },
341}
342
343/// 4.4.i: apply a server-issued `SemanticTokensEdit` script
344/// to `raw_data` in place. Each edit specifies a start index
345/// (into the previous token vec), a count to delete, and a
346/// replacement slice. Edits are applied in order; the server
347/// constructs them against the index space of the input vec.
348///
349/// Returns `Err(())` and leaves `raw_data` untouched when an
350/// edit references a range outside the current vec (defensive
351/// guard against server bugs; the host falls back to a fresh
352/// full request when this fires).
353pub fn apply_semantic_token_edits(
354 raw_data: &mut Vec<lsp_types::SemanticToken>,
355 edits: &[lsp_types::SemanticTokensEdit],
356) -> Result<(), ()> {
357 for edit in edits {
358 let start = edit.start as usize;
359 let delete_count = edit.delete_count as usize;
360 let end = start.checked_add(delete_count).ok_or(())?;
361 if end > raw_data.len() {
362 return Err(());
363 }
364 let replacement = edit.data.clone().unwrap_or_default();
365 raw_data.splice(start..end, replacement);
366 }
367 Ok(())
368}
369
370/// 4.4.h: decode the LSP semantic-tokens stream into
371/// absolute-position tokens. Format per LSP §3.17.6: each
372/// token's `delta_line` is relative to the previous token's
373/// line; `delta_start` is relative to the previous token's
374/// start when on the same line, otherwise relative to column
375/// 0. `token_types` / `token_modifiers` are the server's
376/// legend slices -- indexes outside the legend are dropped
377/// (defense-in-depth; real servers don't emit out-of-range).
378pub fn decode_semantic_tokens(
379 data: &[lsp_types::SemanticToken],
380 token_types: &[lsp_types::SemanticTokenType],
381 token_modifiers: &[lsp_types::SemanticTokenModifier],
382) -> Vec<DecodedSemanticToken> {
383 let mut out = Vec::with_capacity(data.len());
384 let mut cur_line: u32 = 0;
385 let mut cur_start: u32 = 0;
386 for tok in data {
387 if tok.delta_line == 0 {
388 cur_start += tok.delta_start;
389 } else {
390 cur_line += tok.delta_line;
391 cur_start = tok.delta_start;
392 }
393 let Some(token_type) = token_types.get(tok.token_type as usize) else {
394 continue;
395 };
396 let mut modifiers: Vec<String> = Vec::new();
397 for (i, m) in token_modifiers.iter().enumerate() {
398 if (tok.token_modifiers_bitset >> i) & 1 == 1 {
399 modifiers.push(m.as_str().to_string());
400 }
401 }
402 out.push(DecodedSemanticToken {
403 line: cur_line,
404 start_char: cur_start,
405 length: tok.length,
406 token_type: token_type.as_str().to_string(),
407 modifiers,
408 });
409 }
410 out
411}
412
413// ---- Pull diagnostics --------------------------------------------------
414
415/// 4.4.j: cached `textDocument/diagnostic` state per buffer.
416/// `result_id` is what the server issued on the previous
417/// response; threading it back in `previous_result_id` lets
418/// the server answer `Unchanged` when nothing moved.
419#[derive(Debug, Clone, Default)]
420pub struct LspPullDiagnosticsCache {
421 pub document_version: u64,
422 pub result_id: Option<String>,
423}
424
425/// 4.4.j: in-flight `textDocument/diagnostic` request outcome.
426/// `Full` means "here are the diagnostics" (apply to the
427/// layer); `Unchanged` means "nothing moved since the
428/// previous `result_id`" (no-op on the layer, just refresh
429/// the cache's version). `Empty` is the "no server / cancelled
430/// / error" path -- still seats a cache entry with the
431/// current version so the pump doesn't re-fire on the next
432/// tick without an actual edit.
433#[derive(Debug, Clone)]
434pub enum PullDiagnosticsOutcome {
435 Full {
436 buffer_id: BufferId,
437 server_id: Arc<str>,
438 uri: Uri,
439 document_version: u64,
440 result_id: Option<String>,
441 diagnostics: Vec<lsp_types::Diagnostic>,
442 },
443 Unchanged {
444 buffer_id: BufferId,
445 document_version: u64,
446 result_id: String,
447 },
448 Empty {
449 buffer_id: BufferId,
450 document_version: u64,
451 },
452}
453
454// ---- Hover -------------------------------------------------------------
455
456/// Result of a `K` (LSP hover) request, sent from the spawned
457/// task to the App's main thread. Carrying the no-result
458/// variants explicitly (instead of just dropping the channel
459/// send) lets the drain echo a clear message so the user
460/// always gets feedback on `K`.
461#[derive(Debug, Clone)]
462pub enum HoverOutcome {
463 /// Markdown body to feed into the popup. First non-empty
464 /// wins across attached servers.
465 Body(String),
466 /// Walked every attached server; each returned an empty /
467 /// missing hover.
468 NoBody { servers_tried: usize },
469 /// The buffer's URI maps to no attached servers.
470 NoServers,
471}
472
473// ---- References --------------------------------------------------------
474
475/// Result of a `gr` (LSP references) request. Carries the
476/// symbol-under-cursor verbatim so the rendered help buffer's
477/// title reads `References for "foo"` and the user has
478/// confirmation of what they searched for.
479#[derive(Debug, Clone)]
480pub enum ReferencesOutcome {
481 /// Merged + deduped reference list across attached
482 /// servers. May be empty (Found(symbol, [])) when servers
483 /// know about the symbol but it has no other call sites.
484 Found {
485 symbol: String,
486 locations: Vec<lsp_types::Location>,
487 },
488 /// The buffer's URI maps to no attached servers.
489 NoServers,
490}
491
492// ---- Completion (LSP-specific) -----------------------------------------
493
494/// Drain payload for `completionItem/resolve` (Phase
495/// 4.2.g.3). CSM.8b.5: `meta_index` is now the index of the
496/// fired candidate within state.raw's LSP-row sequence; the
497/// drain decodes that row's payload, applies the resolved
498/// fields, re-encodes in place. Multiple resolves in flight
499/// (selection change → cancel prior → fire new) cancel via
500/// the supplied token.
501#[derive(Debug, Clone)]
502pub struct CompletionResolveOutcome {
503 pub meta_index: usize,
504 pub resolved: lsp_types::CompletionItem,
505}
506
507// OR.7: `AsyncCompletionOutcome` moved to
508// `lattice_completion::AsyncCompletionOutcome`. It never was LSP's — it
509// is the drain payload for the whole async fan-out, and living here
510// under an LSP name is part of why the WASM completion seam sat
511// undriven. See `lattice-completion/src/source.rs`.
512
513/// One row of an LSP completion picker. Carries the item
514/// label, kind glyph, optional detail blurb, and the insert
515/// text. `replace_range` is the byte range in the active line
516/// to splice the insert text over.
517#[derive(Debug, Clone)]
518pub struct CompletionItemRow {
519 pub label: String,
520 pub kind_glyph: &'static str,
521 pub detail: Option<String>,
522 /// Text to insert (raw -- no snippet expansion yet).
523 pub insert_text: String,
524 /// Replace range as `(start_byte, end_byte)` on the active
525 /// line.
526 pub replace_range: (u32, u32),
527 /// Line the replace range is on (LSP 0-based). Always the
528 /// cursor's line; carried explicitly to keep the accept
529 /// path independent of cursor mutations.
530 pub line: u32,
531}
532
533/// Outcome of a `textDocument/completion` request.
534#[derive(Debug, Clone)]
535pub enum CompletionOutcome {
536 Items(Vec<CompletionItemRow>),
537 NoServers,
538}
539
540// ---- Code action -------------------------------------------------------
541
542/// One row of a code-action picker. Carries the action title,
543/// kind glyph, and the original `CodeAction` payload (or its
544/// raw `Command`-only form). Action items survive on the App
545/// across the request → picker accept gap so the resolve /
546/// apply path can read them by index.
547#[derive(Debug, Clone)]
548pub struct CodeActionRow {
549 pub title: String,
550 pub kind_glyph: &'static str,
551 pub action: lsp_types::CodeActionOrCommand,
552}
553
554/// Outcome of a `:code-actions` request. Drained per frame.
555#[derive(Debug, Clone)]
556pub enum CodeActionOutcome {
557 /// Fresh code-action result list. Drain opens a picker.
558 Items(Vec<CodeActionRow>),
559 /// Resolved action (post-`codeAction/resolve`). Drain
560 /// applies directly -- the picker is already gone.
561 Resolved(lsp_types::CodeAction),
562 NoProvider,
563}
564
565// ---- Rename ------------------------------------------------------------
566
567/// Outcome of a `:rename` request. The success arm
568/// pre-flattens the WorkspaceEdit into a per-file
569/// `Vec<TextEdit>` map so the App-side apply path doesn't have
570/// to walk lsp-types' enum shapes. `NoProvider` echoes when no
571/// attached server advertises `renameProvider`; `NotRenameable`
572/// when prepareRename refused; `Empty` when the rename
573/// succeeded but the server returned no edits.
574#[derive(Debug, Clone)]
575pub enum RenameOutcome {
576 Edits {
577 /// Per-file edits keyed by URI string. Each Vec is
578 /// already in the order the server returned (the apply
579 /// path reverse-sorts before applying, same as
580 /// formatting).
581 per_file: Vec<(lsp_types::Uri, Vec<lsp_types::TextEdit>)>,
582 new_name: String,
583 },
584 NoProvider,
585 NotRenameable {
586 reason: String,
587 },
588 Empty,
589}
590
591// ---- Signature help ----------------------------------------------------
592
593/// Outcome of a `textDocument/signatureHelp` request. The
594/// response carries multiple signatures (one per overload)
595/// plus the active signature/parameter indices. We collapse
596/// to the active overload + parameter highlight for the popup
597/// body.
598#[derive(Debug, Clone)]
599pub enum SignatureHelpOutcome {
600 /// Pre-rendered markdown body for the popup. Empty body
601 /// means "no signature info" (server returned None or an
602 /// empty `signatures` array).
603 Body(String),
604 /// No server attached / no provider advertised.
605 NoServers,
606}
607
608// ---- Format ------------------------------------------------------------
609
610/// Outcome of a `:format` / `:format-range` request. Drained
611/// per frame; the App applies the edits as one undo unit or
612/// echoes the appropriate failure / no-op state.
613#[derive(Debug, Clone)]
614pub enum FormatOutcome {
615 /// Server returned a (possibly empty) edit list. Empty ==
616 /// no changes needed; non-empty == apply.
617 Edits(Vec<lsp_types::TextEdit>),
618 /// No attached server advertises the relevant formatting
619 /// provider (`is_range` distinguishes whole-buffer from
620 /// range-format providers since they're separate caps).
621 NoProvider { is_range: bool },
622}
623
624// ---- Symbols -----------------------------------------------------------
625
626/// One row of a document-symbol / workspace-symbol picker.
627/// Carries the symbol's name, kind, depth (for in-document
628/// hierarchy indent), and the location to jump to. Built
629/// host-side from the LSP `DocumentSymbolResponse` /
630/// `Vec<SymbolInformation>` so the picker doesn't depend on
631/// lsp-types.
632#[derive(Debug, Clone)]
633pub struct SymbolRow {
634 pub name: String,
635 pub kind_glyph: &'static str,
636 pub container: Option<String>,
637 /// Indent depth (0 = top-level). Document-symbol responses
638 /// nest; workspace-symbol responses are flat (depth = 0).
639 pub depth: u32,
640 pub path: std::path::PathBuf,
641 /// LSP 0-based line.
642 pub line: u32,
643 /// utf-8 byte column.
644 pub col: u32,
645}
646
647/// Outcome of a document-symbol / workspace-symbol request --
648/// drained per frame and either opens a picker or echoes.
649#[derive(Debug, Clone)]
650pub enum SymbolsOutcome {
651 Found { title: String, rows: Vec<SymbolRow> },
652 NoServers,
653}
654
655// ---- Nav kind ----------------------------------------------------------
656
657/// Which navigation request flavour produced an in-flight nav
658/// response (Phase 4.2.c). All four share the same dispatch
659/// shape (per-server `Vec<Location>` merge + dedup + jump-or-
660/// list) -- the kind only changes the LSP method called and
661/// the user-facing "no X found" echo.
662#[derive(Debug, Clone, Copy, PartialEq, Eq)]
663pub enum LspNavKind {
664 Definition,
665 Declaration,
666 TypeDefinition,
667 Implementation,
668}
669
670impl LspNavKind {
671 /// Verb used in echoes ("no definitions found",
672 /// "3 implementations; jumping to first", etc.).
673 pub fn noun_plural(self) -> &'static str {
674 match self {
675 Self::Definition => "definitions",
676 Self::Declaration => "declarations",
677 Self::TypeDefinition => "type definitions",
678 Self::Implementation => "implementations",
679 }
680 }
681
682 /// Single-word noun used in error contexts ("definition
683 /// target uri is not a file", etc.).
684 pub fn noun_singular(self) -> &'static str {
685 match self {
686 Self::Definition => "definition",
687 Self::Declaration => "declaration",
688 Self::TypeDefinition => "type definition",
689 Self::Implementation => "implementation",
690 }
691 }
692}