mcpls_core/bridge/translator/dto.rs
1//! Public MCP-facing result/data-transfer types returned by the tool-call
2//! handlers in the sibling domain modules.
3
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7/// Convert an LSP integer-valued enum (`SymbolKind`, `CompletionItemKind`,
8/// `InlayHintKind`, ...) to its wire-format `u32`.
9///
10/// Infallible and needs no fallback value, unlike a `serde_json` roundtrip.
11pub(super) fn lsp_kind_to_u32<T: Into<u32>>(kind: T) -> u32 {
12 kind.into()
13}
14
15/// Position in a document (1-based for MCP).
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
17pub struct Position2D {
18 /// Line number (1-based).
19 pub line: u32,
20 /// Character offset (1-based).
21 pub character: u32,
22}
23
24/// A 1-based MCP position taken as input by `Translator::handle_*` methods.
25///
26/// Kept distinct from [`Position2D`] (which carries an *output* position back
27/// to the caller) so passing a position into a handler always goes through a
28/// named-field struct literal (`Position { line, character }`) instead of two
29/// adjacent bare `u32` arguments -- a call site that swaps `line` and
30/// `character` no longer compiles instead of silently sending a wrong
31/// position to the LSP server (#322).
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct Position {
34 /// Line number (1-based).
35 pub line: u32,
36 /// Character offset (1-based).
37 pub character: u32,
38}
39
40/// Range in a document (1-based for MCP).
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
42pub struct Range {
43 /// Start position.
44 pub start: Position2D,
45 /// End position.
46 pub end: Position2D,
47}
48
49/// Location in a document.
50#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
51pub struct Location {
52 /// URI of the document.
53 pub uri: String,
54 /// Range within the document.
55 pub range: Range,
56 /// Whether this location is not provably inside any configured
57 /// workspace root (e.g. the standard library or a crates.io dependency).
58 ///
59 /// Advisory only, not a security/safety guarantee: read-only navigation
60 /// results are never filtered by workspace containment (see
61 /// `bridge::uri_in_workspace_roots`'s docs for why), so callers that
62 /// want to apply their own policy toward out-of-workspace locations can
63 /// check this flag. The underlying check is purely lexical -- it does
64 /// not resolve symlinks -- so a location reached through a symlinked
65 /// workspace root (e.g. macOS's `/var` -> `/private/var`, or a package
66 /// manager's symlinked dependency store) can read `true` even though it
67 /// is genuinely inside the workspace. Also always `true` when no
68 /// workspace roots are configured, consistent with
69 /// `bridge::uri_in_workspace_roots`'s fail-closed convention: without a
70 /// configured root, nothing can be vouched for as inside the workspace.
71 /// Omitted (defaults to `false`) when serialized.
72 #[serde(default, skip_serializing_if = "is_false")]
73 pub out_of_workspace: bool,
74}
75
76/// `skip_serializing_if` predicate for a `bool` field that should be omitted
77/// from the serialized output when `false`.
78///
79/// Takes `&bool` rather than `bool` because serde's `skip_serializing_if`
80/// always calls the predicate with a field reference.
81#[allow(clippy::trivially_copy_pass_by_ref)]
82const fn is_false(value: &bool) -> bool {
83 !*value
84}
85
86/// Result of a hover request.
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct HoverResult {
89 /// Hover contents as markdown string.
90 pub contents: String,
91 /// Optional range the hover applies to.
92 pub range: Option<Range>,
93 /// Whether a position in this response could not be resolved for
94 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
95 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
96 /// each cause this -- meaning `range`'s `character` values may be
97 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
98 /// `false`) when serialized.
99 #[serde(default, skip_serializing_if = "is_false")]
100 pub positions_degraded: bool,
101}
102
103/// Result of a definition request.
104#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
105pub struct DefinitionResult {
106 /// Locations of the definition.
107 pub locations: Vec<Location>,
108 /// Whether `locations` was capped below the LSP server's full response
109 /// (see `MAX_NORMALIZED_LOCATIONS`, #474) -- if `true`, more locations
110 /// exist than are returned here. Omitted (defaults to `false`) when
111 /// serialized.
112 #[serde(default, skip_serializing_if = "is_false")]
113 pub truncated: bool,
114 /// Whether a position in this response could not be resolved for
115 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
116 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
117 /// each cause this -- meaning some `locations` entries' `character` values may be
118 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
119 /// `false`) when serialized.
120 #[serde(default, skip_serializing_if = "is_false")]
121 pub positions_degraded: bool,
122}
123
124/// Result of a references request.
125#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
126pub struct ReferencesResult {
127 /// Locations of all references.
128 pub locations: Vec<Location>,
129 /// Whether `locations` was capped below the LSP server's full response
130 /// (see `MAX_NORMALIZED_LOCATIONS`, #474) -- if `true`, more references
131 /// exist than are returned here. Omitted (defaults to `false`) when
132 /// serialized.
133 #[serde(default, skip_serializing_if = "is_false")]
134 pub truncated: bool,
135 /// Whether a position in this response could not be resolved for
136 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
137 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
138 /// each cause this -- meaning some `locations` entries' `character` values may be
139 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
140 /// `false`) when serialized.
141 #[serde(default, skip_serializing_if = "is_false")]
142 pub positions_degraded: bool,
143}
144
145/// Diagnostic severity.
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
147#[serde(rename_all = "lowercase")]
148pub enum DiagnosticSeverity {
149 /// Error diagnostic.
150 Error,
151 /// Warning diagnostic.
152 Warning,
153 /// Informational diagnostic.
154 Information,
155 /// Hint diagnostic.
156 Hint,
157}
158
159/// A single diagnostic.
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
161pub struct Diagnostic {
162 /// Range where the diagnostic applies.
163 pub range: Range,
164 /// Severity of the diagnostic.
165 pub severity: DiagnosticSeverity,
166 /// Diagnostic message.
167 pub message: String,
168 /// Optional diagnostic code.
169 pub code: Option<String>,
170}
171
172/// Result of a diagnostics request.
173#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
174pub struct DiagnosticsResult {
175 /// List of diagnostics for the document.
176 pub diagnostics: Vec<Diagnostic>,
177 /// Whether a position in this response could not be resolved for
178 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
179 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
180 /// each cause this -- meaning some `diagnostics` ranges' `character` values may be
181 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
182 /// `false`) when serialized.
183 #[serde(default, skip_serializing_if = "is_false")]
184 pub positions_degraded: bool,
185}
186
187/// A text edit operation.
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct TextEdit {
190 /// Range to replace.
191 pub range: Range,
192 /// New text.
193 pub new_text: String,
194}
195
196/// Changes to a document.
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct DocumentChanges {
199 /// URI of the document.
200 pub uri: String,
201 /// List of edits to apply.
202 pub edits: Vec<TextEdit>,
203}
204
205/// Result of a rename request.
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct RenameResult {
208 /// Changes to apply across documents.
209 pub changes: Vec<DocumentChanges>,
210 /// Entries withheld from `changes` -- see [`DroppedEdits`]. A non-empty
211 /// value means the rename is incomplete even if `changes` is non-empty,
212 /// and callers must not treat this result as the full rename otherwise.
213 #[serde(default, skip_serializing_if = "DroppedEdits::is_empty")]
214 pub dropped: DroppedEdits,
215 /// Whether a position in this response could not be resolved for
216 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
217 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
218 /// each cause this -- meaning some `changes` edit ranges' `character` values may be
219 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
220 /// `false`) when serialized.
221 #[serde(default, skip_serializing_if = "is_false")]
222 pub positions_degraded: bool,
223}
224
225/// Counts of `WorkspaceEdit` entries withheld during conversion to MCP DTOs,
226/// broken down by reason (#475).
227///
228/// `convert_workspace_edit` silently discarded such entries with only a
229/// `tracing` log line, so a client applying a [`RenameResult`] or
230/// `WorkspaceEditDescription` straight to disk could not tell "nothing to
231/// rename" apart from "some of the rename was withheld" -- this makes that
232/// distinction visible in the result itself.
233#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
234pub struct DroppedEdits {
235 /// Entries referencing a URI outside every configured workspace root.
236 #[serde(default, skip_serializing_if = "is_zero")]
237 pub out_of_workspace: usize,
238 /// `CreateFile`/`RenameFile`/`DeleteFile` document changes, which mcpls
239 /// does not translate into MCP DTOs.
240 #[serde(default, skip_serializing_if = "is_zero")]
241 pub unsupported_file_operation: usize,
242 /// `SnippetTextEdit` entries, which mcpls does not translate since it
243 /// advertises no `snippetEditSupport`.
244 #[serde(default, skip_serializing_if = "is_zero")]
245 pub unsupported_snippet_edit: usize,
246}
247
248impl DroppedEdits {
249 /// Whether no entries were withheld.
250 #[must_use]
251 pub fn is_empty(&self) -> bool {
252 *self == Self::default()
253 }
254}
255
256// Signature required by `#[serde(skip_serializing_if = "is_zero")]` on a `usize` field.
257#[allow(clippy::trivially_copy_pass_by_ref)]
258const fn is_zero(count: &usize) -> bool {
259 *count == 0
260}
261
262/// A completion item.
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct Completion {
265 /// Label of the completion.
266 pub label: String,
267 /// LSP numeric completion-item kind (e.g. 3 for Function).
268 pub kind: Option<u32>,
269 /// Detail information.
270 pub detail: Option<String>,
271 /// Documentation.
272 pub documentation: Option<String>,
273}
274
275/// Result of a completions request.
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct CompletionsResult {
278 /// List of completion items.
279 pub items: Vec<Completion>,
280}
281
282/// A document symbol.
283#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
284pub struct Symbol {
285 /// Name of the symbol.
286 pub name: String,
287 /// LSP numeric symbol kind (e.g. 12 for Function).
288 pub kind: u32,
289 /// Range of the symbol.
290 pub range: Range,
291 /// Selection range (identifier location).
292 pub selection_range: Range,
293 /// Child symbols.
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub children: Option<Vec<Self>>,
296}
297
298/// Result of a document symbols request.
299#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
300pub struct DocumentSymbolsResult {
301 /// List of symbols in the document.
302 pub symbols: Vec<Symbol>,
303 /// Whether a position in this response could not be resolved for
304 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
305 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
306 /// each cause this -- meaning some `symbols` ranges' `character` values may be
307 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
308 /// `false`) when serialized.
309 #[serde(default, skip_serializing_if = "is_false")]
310 pub positions_degraded: bool,
311}
312
313/// Result of a format document request.
314#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct FormatDocumentResult {
316 /// List of edits to format the document.
317 pub edits: Vec<TextEdit>,
318 /// Whether a position in this response could not be resolved for
319 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
320 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
321 /// each cause this -- meaning some `edits` ranges' `character` values may be
322 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
323 /// `false`) when serialized.
324 #[serde(default, skip_serializing_if = "is_false")]
325 pub positions_degraded: bool,
326}
327
328/// A workspace symbol.
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct WorkspaceSymbol {
331 /// Name of the symbol.
332 pub name: String,
333 /// LSP numeric symbol kind (e.g. 12 for Function).
334 pub kind: u32,
335 /// Location of the symbol.
336 pub location: Location,
337 /// Optional container name (parent scope).
338 #[serde(skip_serializing_if = "Option::is_none")]
339 pub container_name: Option<String>,
340}
341
342/// Result of workspace symbol search.
343#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct WorkspaceSymbolResult {
345 /// List of symbols found.
346 pub symbols: Vec<WorkspaceSymbol>,
347 /// Whether more symbols matched than are returned in `symbols` -- set
348 /// whenever any are dropped, whether by the caller's own smaller
349 /// `limit` or by the server-side maximum it's clamped to (see
350 /// `MAX_NORMALIZED_LOCATIONS`, #474); this does not distinguish which of
351 /// the two caused it. Omitted (defaults to `false`) when serialized.
352 #[serde(default, skip_serializing_if = "is_false")]
353 pub truncated: bool,
354 /// Whether a position in this response could not be resolved for
355 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
356 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
357 /// each cause this -- meaning some `symbols` locations' `character` values may be
358 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
359 /// `false`) when serialized.
360 #[serde(default, skip_serializing_if = "is_false")]
361 pub positions_degraded: bool,
362}
363
364/// A single code action.
365#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct CodeAction {
367 /// Title of the code action.
368 pub title: String,
369 /// Kind of code action (quickfix, refactor, etc.).
370 #[serde(skip_serializing_if = "Option::is_none")]
371 pub kind: Option<String>,
372 /// Diagnostics that this action resolves.
373 #[serde(skip_serializing_if = "Vec::is_empty", default)]
374 pub diagnostics: Vec<Diagnostic>,
375 /// Workspace edit to apply.
376 #[serde(skip_serializing_if = "Option::is_none")]
377 pub edit: Option<WorkspaceEditDescription>,
378 /// Command to execute.
379 #[serde(skip_serializing_if = "Option::is_none")]
380 pub command: Option<CommandDescription>,
381 /// Whether this is the preferred action.
382 #[serde(default)]
383 pub is_preferred: bool,
384}
385
386/// Description of a workspace edit.
387#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct WorkspaceEditDescription {
389 /// Changes to apply to documents.
390 pub changes: Vec<DocumentChanges>,
391 /// Entries withheld from `changes` -- see [`DroppedEdits`].
392 #[serde(default, skip_serializing_if = "DroppedEdits::is_empty")]
393 pub dropped: DroppedEdits,
394}
395
396/// Description of a command.
397#[derive(Debug, Clone, Serialize, Deserialize)]
398pub struct CommandDescription {
399 /// Title of the command.
400 pub title: String,
401 /// Command identifier.
402 pub command: String,
403 /// Command arguments.
404 #[serde(skip_serializing_if = "Vec::is_empty", default)]
405 pub arguments: Vec<serde_json::Value>,
406}
407
408/// Result of code actions request.
409#[derive(Debug, Clone, Serialize, Deserialize)]
410pub struct CodeActionsResult {
411 /// Available code actions.
412 pub actions: Vec<CodeAction>,
413 /// Whether a position in this response could not be resolved for
414 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
415 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
416 /// each cause this -- meaning some `actions` diagnostic/edit ranges'
417 /// `character` values may be wrong for a non-UTF-16 LSP server (#497).
418 /// Omitted (defaults to `false`) when serialized.
419 #[serde(default, skip_serializing_if = "is_false")]
420 pub positions_degraded: bool,
421}
422
423/// A call hierarchy item.
424#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct CallHierarchyItemResult {
426 /// Name of the symbol.
427 pub name: String,
428 /// LSP numeric symbol kind (e.g. 12 for Function).
429 pub kind: u32,
430 /// More detail for this item.
431 #[serde(skip_serializing_if = "Option::is_none")]
432 pub detail: Option<String>,
433 /// URI of the document.
434 pub uri: String,
435 /// Range of the symbol.
436 pub range: Range,
437 /// Selection range (identifier location).
438 ///
439 /// Serialized as `selectionRange` (camelCase) so that the value returned by
440 /// `prepare_call_hierarchy` round-trips correctly when the MCP client passes
441 /// it back to `get_incoming_calls` / `get_outgoing_calls`, which deserialize
442 /// it as `lsp_types::CallHierarchyItem` (camelCase).
443 #[serde(rename = "selectionRange")]
444 pub selection_range: Range,
445 /// Opaque data to pass to incoming/outgoing calls.
446 #[serde(skip_serializing_if = "Option::is_none")]
447 pub data: Option<serde_json::Value>,
448 /// Whether this item is not provably inside any configured workspace
449 /// root -- see [`Location::out_of_workspace`] for the exact semantics
450 /// and caveats (advisory only, lexical, symlink-unaware).
451 #[serde(default, skip_serializing_if = "is_false")]
452 pub out_of_workspace: bool,
453}
454
455/// Result of call hierarchy prepare request.
456#[derive(Debug, Clone, Serialize, Deserialize)]
457pub struct CallHierarchyPrepareResult {
458 /// List of callable items at the position.
459 pub items: Vec<CallHierarchyItemResult>,
460 /// Whether a position in this response could not be resolved for
461 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
462 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
463 /// each cause this -- meaning some `items` ranges' `character` values may be
464 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
465 /// `false`) when serialized.
466 #[serde(default, skip_serializing_if = "is_false")]
467 pub positions_degraded: bool,
468}
469
470/// An incoming call (caller of the current item).
471#[derive(Debug, Clone, Serialize, Deserialize)]
472pub struct IncomingCall {
473 /// The item that calls the current item.
474 pub from: CallHierarchyItemResult,
475 /// Ranges where the call occurs.
476 pub from_ranges: Vec<Range>,
477}
478
479/// Result of incoming calls request.
480#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct IncomingCallsResult {
482 /// List of incoming calls.
483 pub calls: Vec<IncomingCall>,
484 /// Whether a position in this response could not be resolved for
485 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
486 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
487 /// each cause this -- meaning some `calls` ranges' `character` values may be
488 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
489 /// `false`) when serialized.
490 #[serde(default, skip_serializing_if = "is_false")]
491 pub positions_degraded: bool,
492}
493
494/// An outgoing call (callee from the current item).
495#[derive(Debug, Clone, Serialize, Deserialize)]
496pub struct OutgoingCall {
497 /// The item being called.
498 pub to: CallHierarchyItemResult,
499 /// Ranges where the call occurs.
500 pub from_ranges: Vec<Range>,
501}
502
503/// Result of outgoing calls request.
504#[derive(Debug, Clone, Serialize, Deserialize)]
505pub struct OutgoingCallsResult {
506 /// List of outgoing calls.
507 pub calls: Vec<OutgoingCall>,
508 /// Whether a position in this response could not be resolved for
509 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
510 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
511 /// each cause this -- meaning some `calls` ranges' `character` values may be
512 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
513 /// `false`) when serialized.
514 #[serde(default, skip_serializing_if = "is_false")]
515 pub positions_degraded: bool,
516}
517
518/// Result of server logs request.
519#[derive(Debug, Clone, Serialize, Deserialize)]
520pub struct ServerLogsResult {
521 /// List of log entries.
522 pub logs: Vec<crate::bridge::notifications::LogEntry>,
523}
524
525/// Result of server messages request.
526#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct ServerMessagesResult {
528 /// List of server messages.
529 pub messages: Vec<crate::bridge::notifications::ServerMessage>,
530}
531
532/// A single parameter in a signature.
533#[derive(Debug, Clone, Serialize, Deserialize)]
534pub struct SignatureParameter {
535 /// Label of the parameter.
536 pub label: String,
537 /// Optional documentation for the parameter.
538 #[serde(skip_serializing_if = "Option::is_none")]
539 pub documentation: Option<String>,
540}
541
542/// A single signature overload.
543#[derive(Debug, Clone, Serialize, Deserialize)]
544pub struct SignatureInfo {
545 /// Full label of the signature.
546 pub label: String,
547 /// Optional documentation for the signature.
548 #[serde(skip_serializing_if = "Option::is_none")]
549 pub documentation: Option<String>,
550 /// Parameters of the signature.
551 pub parameters: Vec<SignatureParameter>,
552}
553
554/// Result of a signature help request.
555#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct SignatureHelpResult {
557 /// Available signatures.
558 pub signatures: Vec<SignatureInfo>,
559 /// Index of the active signature.
560 #[serde(skip_serializing_if = "Option::is_none")]
561 pub active_signature: Option<u32>,
562 /// Index of the active parameter within the active signature.
563 #[serde(skip_serializing_if = "Option::is_none")]
564 pub active_parameter: Option<u32>,
565}
566
567/// Result of a go-to-implementation or go-to-type-definition request.
568#[derive(Debug, Clone, Serialize, Deserialize)]
569pub struct LocationsResult {
570 /// Locations found.
571 pub locations: Vec<Location>,
572 /// Whether `locations` was capped below the LSP server's full response
573 /// (see `MAX_NORMALIZED_LOCATIONS`, #474) -- if `true`, more locations
574 /// exist than are returned here. Omitted (defaults to `false`) when
575 /// serialized.
576 #[serde(default, skip_serializing_if = "is_false")]
577 pub truncated: bool,
578 /// Whether a position in this response could not be resolved for
579 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
580 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
581 /// each cause this -- meaning some `locations` entries' `character` values may be
582 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
583 /// `false`) when serialized.
584 #[serde(default, skip_serializing_if = "is_false")]
585 pub positions_degraded: bool,
586}
587
588/// A single inlay hint entry.
589#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct InlayHintEntry {
591 /// Position of the hint (1-based MCP).
592 pub position: Position2D,
593 /// Label text for the hint.
594 pub label: String,
595 /// LSP numeric inlay-hint kind (1 = Type, 2 = Parameter, or a
596 /// server-defined custom value).
597 #[serde(skip_serializing_if = "Option::is_none")]
598 pub kind: Option<u32>,
599 /// Whether to add a space before the hint.
600 #[serde(skip_serializing_if = "Option::is_none")]
601 pub padding_left: Option<bool>,
602 /// Whether to add a space after the hint.
603 #[serde(skip_serializing_if = "Option::is_none")]
604 pub padding_right: Option<bool>,
605 /// Tooltip text.
606 #[serde(skip_serializing_if = "Option::is_none")]
607 pub tooltip: Option<String>,
608}
609
610/// Result of an inlay hints request.
611#[derive(Debug, Clone, Serialize, Deserialize)]
612pub struct InlayHintsResult {
613 /// List of inlay hints.
614 pub hints: Vec<InlayHintEntry>,
615 /// Whether a position in this response could not be resolved for
616 /// encoding conversion -- disk-read budget exhaustion, an unresolvable
617 /// server-supplied path, a line past EOF, or invalid UTF-8 content can
618 /// each cause this -- meaning some `hints` positions' `character` values may be
619 /// wrong for a non-UTF-16 LSP server (#497). Omitted (defaults to
620 /// `false`) when serialized.
621 #[serde(default, skip_serializing_if = "is_false")]
622 pub positions_degraded: bool,
623}
624
625#[cfg(test)]
626#[allow(clippy::unwrap_used, clippy::expect_used)]
627mod tests {
628 use super::lsp_kind_to_u32;
629
630 /// #467 regression: the old `Option<u8>` narrowing silently dropped any
631 /// `InlayHintKind::Custom(n)` with `n > 255` to `None`, indistinguishable
632 /// from "server sent no kind". `lsp_kind_to_u32` must preserve the full
633 /// `u32` value losslessly.
634 #[test]
635 fn test_lsp_kind_to_u32_preserves_custom_values_above_u8_range() {
636 let kind = lsp_types::InlayHintKind::Custom(300);
637 assert_eq!(lsp_kind_to_u32(kind), 300u32);
638 }
639}