1use std::num::NonZeroU32;
5
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use super::enclosing::{ContextualDiagnostic, ContextualLocation, EnrichmentSummary};
11use super::kind_filter::{self, KindFilter};
12use crate::bridge::notifications::ReportedSeverity;
13use crate::config::text_newtype::impl_text_newtype;
14use crate::config::{BoundedText, InvalidBoundedText};
15use crate::error::spelled_enum;
16use crate::redaction::{Redactions, ServerText};
17
18pub(super) fn lsp_kind_to_u32<T: Into<u32>>(kind: T) -> u32 {
23 kind.into()
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
28pub struct Position2D {
29 pub line: u32,
31 pub character: u32,
33}
34
35pub const MAX_POSITION_VALUE: u32 = 1_000_000;
37
38pub const MAX_RANGE_LINES: u32 = 10_000;
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
43pub enum InvalidPosition {
44 #[error("Line and character positions must be >= 1")]
46 ZeroBased,
47 #[error("Position values must be <= {MAX_POSITION_VALUE}")]
49 TooLarge,
50}
51
52pub const MAX_NEW_NAME_LENGTH: usize = 1_000;
56
57spelled_enum! {
58 TextField {
61 NewName => "new_name",
63 Trigger => "trigger",
65 SymbolName => "symbol_name",
67 Container => "container",
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
86#[error("`{field}` {reason}")]
87pub struct InvalidTextField {
88 pub field: TextField,
90 pub reason: InvalidBoundedText,
92}
93
94impl InvalidTextField {
95 pub(crate) fn of(field: TextField) -> impl Fn(InvalidBoundedText) -> Self {
97 move |reason| Self { field, reason }
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
116#[serde(try_from = "String")]
117pub struct NewName(BoundedText<MAX_NEW_NAME_LENGTH>);
118
119impl NewName {
120 pub fn new(raw: impl Into<String>) -> Result<Self, InvalidTextField> {
127 BoundedText::new(raw)
128 .map(Self)
129 .map_err(InvalidTextField::of(TextField::NewName))
130 }
131
132 #[must_use]
134 pub fn into_string(self) -> String {
135 String::from(self)
136 }
137}
138
139impl_text_newtype!(NewName, InvalidTextField);
140
141pub const MAX_TRIGGER_CHARACTER_BYTES: usize = 8;
148
149#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
169#[serde(try_from = "String")]
170pub struct CompletionTrigger(String);
171
172impl CompletionTrigger {
173 pub fn new(raw: impl Into<String>) -> Result<Self, InvalidTextField> {
180 let raw = raw.into();
181 let invalid = InvalidTextField::of(TextField::Trigger);
182 if raw.is_empty() {
183 return Err(invalid(InvalidBoundedText::Blank));
184 }
185 if raw.len() > MAX_TRIGGER_CHARACTER_BYTES {
186 return Err(invalid(InvalidBoundedText::TooLong {
187 len: raw.len(),
188 max: MAX_TRIGGER_CHARACTER_BYTES,
189 }));
190 }
191 Ok(Self(raw))
192 }
193
194 #[must_use]
196 pub fn into_string(self) -> String {
197 self.0
198 }
199}
200
201impl_text_newtype!(CompletionTrigger, InvalidTextField);
202
203pub const MAX_TAB_SIZE: u32 = 32;
205
206const DEFAULT_TAB_SIZE: u32 = 4;
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
211#[error("tab_size must be between 1 and {MAX_TAB_SIZE}, got {0}")]
212pub struct InvalidTabSize(pub u32);
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
232#[serde(try_from = "u32", into = "u32")]
233pub struct TabSize(NonZeroU32);
234
235impl TabSize {
236 #[must_use]
238 pub const fn get(self) -> u32 {
239 self.0.get()
240 }
241}
242
243impl Default for TabSize {
244 fn default() -> Self {
245 const DEFAULT: TabSize = match NonZeroU32::new(DEFAULT_TAB_SIZE) {
246 Some(size) => TabSize(size),
247 None => panic!("the default tab size must be non-zero"),
248 };
249 DEFAULT
250 }
251}
252
253impl TryFrom<u32> for TabSize {
254 type Error = InvalidTabSize;
255
256 fn try_from(value: u32) -> Result<Self, Self::Error> {
257 match NonZeroU32::new(value) {
258 Some(size) if value <= MAX_TAB_SIZE => Ok(Self(size)),
259 _ => Err(InvalidTabSize(value)),
260 }
261 }
262}
263
264impl From<TabSize> for u32 {
265 fn from(size: TabSize) -> Self {
266 size.get()
267 }
268}
269
270impl JsonSchema for TabSize {
271 fn schema_name() -> std::borrow::Cow<'static, str> {
272 "TabSize".into()
273 }
274
275 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
276 schemars::json_schema!({
277 "type": "integer",
278 "minimum": 1,
279 "maximum": MAX_TAB_SIZE,
280 "default": DEFAULT_TAB_SIZE,
281 })
282 }
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
287pub enum InvalidRange {
288 #[error("invalid range start: {0}")]
290 Start(InvalidPosition),
291 #[error("invalid range end: {0}")]
293 End(InvalidPosition),
294 #[error("Start position must be before or equal to end position")]
296 Reversed,
297 #[error("Range size must be <= {MAX_RANGE_LINES} lines")]
299 TooManyLines,
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
322pub struct Position {
323 line: NonZeroU32,
324 character: NonZeroU32,
325}
326
327impl Position {
328 pub const fn from_client(line: u32, character: u32) -> Result<Self, InvalidPosition> {
335 let (Some(line), Some(character)) = (NonZeroU32::new(line), NonZeroU32::new(character))
336 else {
337 return Err(InvalidPosition::ZeroBased);
338 };
339 if line.get() > MAX_POSITION_VALUE || character.get() > MAX_POSITION_VALUE {
340 return Err(InvalidPosition::TooLarge);
341 }
342 Ok(Self { line, character })
343 }
344
345 #[must_use]
347 pub const fn line(self) -> NonZeroU32 {
348 self.line
349 }
350
351 #[must_use]
353 pub const fn character(self) -> NonZeroU32 {
354 self.character
355 }
356
357 #[must_use]
359 pub const fn lsp_line(self) -> u32 {
360 self.line.get().saturating_sub(1)
361 }
362
363 #[must_use]
365 pub const fn lsp_character(self) -> u32 {
366 self.character.get().saturating_sub(1)
367 }
368
369 pub(crate) fn from_server_output(position: &Position2D) -> Self {
375 let clamp = |value: u32| NonZeroU32::new(value).unwrap_or(NonZeroU32::MIN);
376 Self {
377 line: clamp(position.line),
378 character: clamp(position.character),
379 }
380 }
381
382 #[cfg(test)]
384 pub(crate) fn at(line: u32, character: u32) -> Self {
385 Self::from_client(line, character).expect("fixture position is valid")
386 }
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Eq)]
402pub struct PositionRange {
403 start: Position,
404 end: Position,
405}
406
407impl PositionRange {
408 pub fn new(start: Position, end: Position) -> Result<Self, InvalidRange> {
414 let ordered = (start.line, start.character) <= (end.line, end.character);
415 ordered
416 .ok_or(InvalidRange::Reversed)
417 .map(|()| Self { start, end })
418 }
419
420 pub fn from_client(
426 (start_line, start_character): (u32, u32),
427 (end_line, end_character): (u32, u32),
428 ) -> Result<Self, InvalidRange> {
429 let start =
430 Position::from_client(start_line, start_character).map_err(InvalidRange::Start)?;
431 let end = Position::from_client(end_line, end_character).map_err(InvalidRange::End)?;
432 Self::new(start, end)
433 }
434
435 #[must_use]
437 pub const fn start(self) -> Position {
438 self.start
439 }
440
441 #[must_use]
443 pub const fn end(self) -> Position {
444 self.end
445 }
446}
447
448#[derive(Debug, Clone, Copy, PartialEq, Eq)]
451pub struct BoundedRange(PositionRange);
452
453impl BoundedRange {
454 #[must_use]
456 pub const fn range(self) -> PositionRange {
457 self.0
458 }
459}
460
461impl TryFrom<PositionRange> for BoundedRange {
462 type Error = InvalidRange;
463
464 fn try_from(range: PositionRange) -> Result<Self, Self::Error> {
465 let span = range.end.line.get().saturating_sub(range.start.line.get());
466 (span <= MAX_RANGE_LINES)
467 .ok_or(InvalidRange::TooManyLines)
468 .map(|()| Self(range))
469 }
470}
471
472#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
474pub struct Range {
475 pub start: Position2D,
477 pub end: Position2D,
479}
480
481#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
483pub struct Location {
484 pub uri: String,
486 pub range: Range,
488 #[serde(default, skip_serializing_if = "is_false")]
504 pub out_of_workspace: bool,
505}
506
507#[expect(
513 clippy::trivially_copy_pass_by_ref,
514 reason = "serde's `skip_serializing_if` requires `fn(&T) -> bool`"
515)]
516const fn is_false(value: &bool) -> bool {
517 !*value
518}
519
520#[derive(
523 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
524)]
525#[serde(rename_all = "snake_case")]
526pub enum PositionDegradation {
527 Response,
531 Request,
534}
535
536#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
538pub struct HoverResult {
539 pub contents: String,
541 pub range: Option<Range>,
543 #[serde(default, skip_serializing_if = "Option::is_none")]
547 pub positions_degraded: Option<PositionDegradation>,
548}
549
550#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
552pub struct Diagnostic {
553 pub range: Range,
555 pub severity: ReportedSeverity,
557 pub message: String,
559 pub code: Option<String>,
561}
562
563#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
565pub struct DiagnosticsResult {
566 pub diagnostics: Vec<Diagnostic>,
568 #[serde(default, skip_serializing_if = "Option::is_none")]
572 #[schemars(with = "PositionDegradation")]
573 pub positions_degraded: Option<PositionDegradation>,
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
579pub struct DocumentDiagnosticsResult {
580 pub diagnostics: Vec<ContextualDiagnostic>,
582 #[serde(default, skip_serializing_if = "Option::is_none")]
586 #[schemars(with = "PositionDegradation")]
587 pub positions_degraded: Option<PositionDegradation>,
588 #[serde(default, skip_serializing_if = "Option::is_none")]
592 #[schemars(with = "EnrichmentSummary")]
593 pub enrichment: Option<EnrichmentSummary>,
594}
595
596#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
598pub struct TextEdit {
599 pub range: Range,
601 pub new_text: String,
603}
604
605#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
607pub struct DocumentChanges {
608 pub uri: String,
610 pub edits: Vec<TextEdit>,
612}
613
614#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
616pub struct RenameResult {
617 pub changes: Vec<DocumentChanges>,
619 #[serde(default, skip_serializing_if = "DroppedEdits::is_empty")]
623 pub dropped: DroppedEdits,
624 #[serde(default, skip_serializing_if = "Option::is_none")]
628 pub positions_degraded: Option<PositionDegradation>,
629}
630
631#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
640pub struct DroppedEdits {
641 #[serde(default, skip_serializing_if = "is_zero")]
643 pub out_of_workspace: usize,
644 #[serde(default, skip_serializing_if = "is_zero")]
647 pub unsupported_file_operation: usize,
648 #[serde(default, skip_serializing_if = "is_zero")]
651 pub unsupported_snippet_edit: usize,
652 #[serde(default, skip_serializing_if = "is_zero")]
656 pub exceeds_item_cap: usize,
657 #[serde(default, skip_serializing_if = "is_zero")]
661 pub shadowed_by_changes: usize,
662}
663
664impl DroppedEdits {
665 #[must_use]
667 pub fn is_empty(&self) -> bool {
668 *self == Self::default()
669 }
670}
671
672#[expect(
673 clippy::trivially_copy_pass_by_ref,
674 reason = "serde's `skip_serializing_if` requires `fn(&T) -> bool`"
675)]
676const fn is_zero(count: &usize) -> bool {
677 *count == 0
678}
679
680#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
682pub struct Completion {
683 pub label: String,
685 pub kind: Option<u32>,
687 pub detail: Option<String>,
689 pub documentation: Option<String>,
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
695pub struct CompletionsResult {
696 pub items: Vec<Completion>,
698 #[serde(default, skip_serializing_if = "Option::is_none")]
702 pub positions_degraded: Option<PositionDegradation>,
703}
704
705#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
707pub struct Symbol {
708 pub name: String,
710 pub kind: u32,
712 pub range: Range,
714 pub selection_range: Range,
716 #[serde(skip_serializing_if = "Option::is_none")]
718 pub children: Option<Vec<Self>>,
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
723pub struct DocumentSymbolsResult {
724 pub symbols: Vec<Symbol>,
726 #[serde(default, skip_serializing_if = "Option::is_none")]
730 #[schemars(with = "PositionDegradation")]
731 pub positions_degraded: Option<PositionDegradation>,
732}
733
734#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
736pub struct FormatDocumentResult {
737 pub edits: Vec<TextEdit>,
739 #[serde(default, skip_serializing_if = "Option::is_none")]
743 pub positions_degraded: Option<PositionDegradation>,
744}
745
746#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
748pub struct WorkspaceSymbol {
749 pub name: String,
751 pub kind: u32,
753 pub location: Location,
755 #[serde(skip_serializing_if = "Option::is_none")]
757 pub container_name: Option<String>,
758}
759
760#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
762pub struct WorkspaceSymbolResult {
763 pub symbols: Vec<WorkspaceSymbol>,
765 #[serde(default, skip_serializing_if = "is_false")]
771 pub truncated: bool,
772 #[serde(default, skip_serializing_if = "Option::is_none")]
776 pub positions_degraded: Option<PositionDegradation>,
777 #[serde(default, skip_serializing_if = "Option::is_none")]
781 #[schemars(with = "IndexCoverage")]
782 pub index_coverage: Option<IndexCoverage>,
783}
784
785#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
787pub struct CodeAction {
788 pub title: String,
790 #[serde(skip_serializing_if = "Option::is_none")]
792 pub kind: Option<String>,
793 #[serde(skip_serializing_if = "Vec::is_empty", default)]
795 pub diagnostics: Vec<Diagnostic>,
796 #[serde(skip_serializing_if = "Option::is_none")]
798 pub edit: Option<WorkspaceEditDescription>,
799 #[serde(skip_serializing_if = "Option::is_none")]
801 pub command: Option<CommandDescription>,
802 #[serde(default)]
804 pub is_preferred: bool,
805}
806
807#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
809pub struct WorkspaceEditDescription {
810 pub changes: Vec<DocumentChanges>,
812 #[serde(default, skip_serializing_if = "DroppedEdits::is_empty")]
814 pub dropped: DroppedEdits,
815}
816
817#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
819pub struct CommandDescription {
820 pub title: String,
822 pub command: String,
824 #[serde(skip_serializing_if = "Vec::is_empty", default)]
826 pub arguments: Vec<serde_json::Value>,
827}
828
829#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
831pub struct CodeActionsResult {
832 pub actions: Vec<CodeAction>,
834 #[serde(default, skip_serializing_if = "is_false")]
839 pub truncated: bool,
840 #[serde(default, skip_serializing_if = "Option::is_none")]
844 pub positions_degraded: Option<PositionDegradation>,
845}
846
847#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
849pub struct CallHierarchyPrepareResult {
850 pub items: Vec<HierarchyItem>,
852 #[serde(default, skip_serializing_if = "is_false")]
856 pub truncated: bool,
857 #[serde(default, skip_serializing_if = "Option::is_none")]
861 pub positions_degraded: Option<PositionDegradation>,
862}
863
864#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
866pub struct IncomingCall {
867 pub from: HierarchyItem,
869 pub from_ranges: Vec<Range>,
871}
872
873#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
875pub struct IncomingCallsResult {
876 pub calls: Vec<IncomingCall>,
878 #[serde(default, skip_serializing_if = "is_false")]
882 pub truncated: bool,
883 #[serde(default, skip_serializing_if = "Option::is_none")]
887 pub positions_degraded: Option<PositionDegradation>,
888}
889
890#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
892pub struct OutgoingCall {
893 pub to: HierarchyItem,
895 pub from_ranges: Vec<Range>,
897}
898
899#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
901pub struct OutgoingCallsResult {
902 pub calls: Vec<OutgoingCall>,
904 #[serde(default, skip_serializing_if = "is_false")]
908 pub truncated: bool,
909 #[serde(default, skip_serializing_if = "Option::is_none")]
913 pub positions_degraded: Option<PositionDegradation>,
914}
915
916#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
918pub struct ServerLogsResult {
919 pub logs: Vec<crate::bridge::notifications::LogEntry>,
921}
922
923#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
925pub struct ServerMessagesResult {
926 pub messages: Vec<crate::bridge::notifications::ServerMessage>,
928}
929
930#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
932pub struct SignatureParameter {
933 #[serde(skip_serializing_if = "Option::is_none")]
938 pub label: Option<String>,
939 #[serde(skip_serializing_if = "Option::is_none")]
941 pub documentation: Option<String>,
942}
943
944#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
946pub struct SignatureInfo {
947 pub label: String,
949 #[serde(skip_serializing_if = "Option::is_none")]
951 pub documentation: Option<String>,
952 pub parameters: Vec<SignatureParameter>,
954}
955
956#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
958pub struct SignatureHelpResult {
959 pub signatures: Vec<SignatureInfo>,
961 #[serde(skip_serializing_if = "Option::is_none")]
963 pub active_signature: Option<u32>,
964 #[serde(skip_serializing_if = "Option::is_none")]
966 pub active_parameter: Option<u32>,
967 #[serde(default, skip_serializing_if = "Option::is_none")]
971 pub positions_degraded: Option<PositionDegradation>,
972}
973
974#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
977pub struct LocationsResult {
978 pub locations: Vec<ContextualLocation>,
980 #[serde(default, skip_serializing_if = "is_false")]
985 pub truncated: bool,
986 #[serde(default, skip_serializing_if = "Option::is_none")]
990 #[schemars(with = "PositionDegradation")]
991 pub positions_degraded: Option<PositionDegradation>,
992 #[serde(default, skip_serializing_if = "Option::is_none")]
997 #[schemars(with = "EnrichmentSummary")]
998 pub enrichment: Option<EnrichmentSummary>,
999}
1000
1001#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1006pub struct IndexCoverage {
1007 pub synced_documents: u32,
1009 #[serde(default, skip_serializing_if = "is_false")]
1011 pub server_restarted: bool,
1012}
1013
1014#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1018pub struct ReferencesResult {
1019 #[serde(flatten)]
1021 pub result: LocationsResult,
1022 #[serde(default, skip_serializing_if = "Option::is_none")]
1026 #[schemars(with = "IndexCoverage")]
1027 pub index_coverage: Option<IndexCoverage>,
1028}
1029
1030#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1032pub struct InlayHintEntry {
1033 pub position: Position2D,
1035 pub label: String,
1037 #[serde(skip_serializing_if = "Option::is_none")]
1040 pub kind: Option<u32>,
1041 #[serde(skip_serializing_if = "Option::is_none")]
1043 pub padding_left: Option<bool>,
1044 #[serde(skip_serializing_if = "Option::is_none")]
1046 pub padding_right: Option<bool>,
1047 #[serde(skip_serializing_if = "Option::is_none")]
1049 pub tooltip: Option<String>,
1050}
1051
1052#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1054pub struct InlayHintsResult {
1055 pub hints: Vec<InlayHintEntry>,
1057 #[serde(default, skip_serializing_if = "is_false")]
1061 pub truncated: bool,
1062 #[serde(default, skip_serializing_if = "Option::is_none")]
1066 pub positions_degraded: Option<PositionDegradation>,
1067}
1068
1069#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1096pub struct HierarchyItem {
1097 pub name: String,
1099 pub kind: u32,
1101 #[serde(default, skip_serializing_if = "Option::is_none")]
1103 pub detail: Option<String>,
1104 pub uri: String,
1106 pub range: Range,
1108 #[serde(rename = "selectionRange")]
1113 pub selection_range: Range,
1114 #[serde(default, skip_serializing_if = "Option::is_none")]
1116 pub data: Option<serde_json::Value>,
1117 #[serde(default, skip_serializing_if = "is_false")]
1122 pub out_of_workspace: bool,
1123}
1124
1125#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
1127pub enum InvalidHierarchyItem {
1128 #[error("invalid hierarchy item range: {0}")]
1130 Range(InvalidRange),
1131 #[error("invalid hierarchy item selectionRange: {0}")]
1133 SelectionRange(InvalidRange),
1134}
1135
1136#[derive(Debug, Clone)]
1164pub struct CheckedHierarchyItem {
1165 pub(super) name: String,
1166 pub(super) kind: u32,
1167 pub(super) detail: Option<String>,
1168 pub(super) uri: String,
1169 pub(super) range: PositionRange,
1170 pub(super) selection_range: PositionRange,
1171 pub(super) data: Option<serde_json::Value>,
1172}
1173
1174impl CheckedHierarchyItem {
1175 pub fn from_client(item: HierarchyItem) -> Result<Self, InvalidHierarchyItem> {
1182 let range = checked_range(&item.range).map_err(InvalidHierarchyItem::Range)?;
1183 let selection_range =
1184 checked_range(&item.selection_range).map_err(InvalidHierarchyItem::SelectionRange)?;
1185 Ok(Self {
1186 name: item.name,
1187 kind: item.kind,
1188 detail: item.detail,
1189 uri: item.uri,
1190 range,
1191 selection_range,
1192 data: item.data,
1193 })
1194 }
1195
1196 #[must_use]
1198 pub fn uri(&self) -> &str {
1199 &self.uri
1200 }
1201}
1202
1203fn checked_range(range: &Range) -> Result<PositionRange, InvalidRange> {
1204 PositionRange::from_client(
1205 (range.start.line, range.start.character),
1206 (range.end.line, range.end.character),
1207 )
1208}
1209
1210#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1212pub struct TypeHierarchyResult {
1213 pub items: Vec<HierarchyItem>,
1216 #[serde(default, skip_serializing_if = "is_false")]
1220 pub truncated: bool,
1221 #[serde(default, skip_serializing_if = "Option::is_none")]
1225 #[schemars(with = "PositionDegradation")]
1226 pub positions_degraded: Option<PositionDegradation>,
1227}
1228
1229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1244#[serde(tag = "status", rename_all = "snake_case")]
1245pub enum PrepareRenameOutcome {
1246 Renameable {
1248 range: Range,
1250 #[serde(default, skip_serializing_if = "Option::is_none")]
1252 placeholder: Option<String>,
1253 },
1254 DefaultBehavior,
1258 NotRenameable {
1261 #[serde(default, skip_serializing_if = "Option::is_none")]
1263 server_message: Option<String>,
1264 },
1265}
1266
1267#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1269pub struct PrepareRenameResult {
1270 #[serde(flatten)]
1272 pub outcome: PrepareRenameOutcome,
1273 #[serde(default, skip_serializing_if = "Option::is_none")]
1277 #[schemars(with = "PositionDegradation")]
1278 pub positions_degraded: Option<PositionDegradation>,
1279}
1280
1281#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1283#[serde(rename_all = "snake_case")]
1284pub enum DocumentHighlightKind {
1285 Text,
1287 Read,
1289 Write,
1291}
1292
1293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1295pub struct DocumentHighlightEntry {
1296 pub range: Range,
1298 pub kind: DocumentHighlightKind,
1300}
1301
1302pub const MAX_SELECTION_CHAIN: usize = 32;
1305
1306#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1321pub struct SelectionRangesResult {
1322 pub ranges: Vec<Range>,
1326 #[serde(default, skip_serializing_if = "is_false")]
1329 pub truncated: bool,
1330 #[serde(default, skip_serializing_if = "Option::is_none")]
1332 #[schemars(with = "PositionDegradation")]
1333 pub positions_degraded: Option<PositionDegradation>,
1334}
1335
1336pub const MAX_COLLAPSED_TEXT_BYTES: usize = 256;
1339
1340#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1342#[serde(rename_all = "snake_case")]
1343pub enum FoldingKind {
1344 Comment,
1346 Imports,
1348 Region,
1350 Unspecified,
1352}
1353
1354#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1359pub enum FoldingKindFilter {
1360 #[default]
1362 All,
1363 Comment,
1365 Imports,
1367 Region,
1369}
1370
1371impl JsonSchema for FoldingKind {
1373 fn schema_name() -> std::borrow::Cow<'static, str> {
1374 "FoldingKind".into()
1375 }
1376
1377 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1378 schemars::json_schema!({
1379 "type": "string",
1380 "enum": ["comment", "imports", "region", "unspecified"],
1381 })
1382 }
1383}
1384
1385impl JsonSchema for FoldingKindFilter {
1386 fn schema_name() -> std::borrow::Cow<'static, str> {
1387 Self::SCHEMA_NAME.into()
1388 }
1389
1390 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1391 Self::schema()
1392 }
1393}
1394
1395kind_filter::closed_kind_filter!(FoldingKindFilter, "FoldingKindFilter");
1396
1397impl FoldingKindFilter {
1398 pub const ALL: [Self; 4] = [Self::All, Self::Comment, Self::Imports, Self::Region];
1400
1401 #[must_use]
1403 pub const fn as_str(self) -> &'static str {
1404 match self {
1405 Self::All => "all",
1406 Self::Comment => "comment",
1407 Self::Imports => "imports",
1408 Self::Region => "region",
1409 }
1410 }
1411
1412 #[must_use]
1424 pub const fn admits(self, kind: FoldingKind) -> bool {
1425 matches!(
1426 (self, kind),
1427 (Self::All, _)
1428 | (Self::Comment, FoldingKind::Comment)
1429 | (Self::Imports, FoldingKind::Imports)
1430 | (Self::Region, FoldingKind::Region)
1431 )
1432 }
1433}
1434
1435#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1437pub struct FoldingRegion {
1438 pub start_line: u32,
1440 pub end_line: u32,
1442 #[serde(default, skip_serializing_if = "Option::is_none")]
1444 pub start_character: Option<u32>,
1445 #[serde(default, skip_serializing_if = "Option::is_none")]
1447 pub end_character: Option<u32>,
1448 pub kind: FoldingKind,
1450 #[serde(default, skip_serializing_if = "Option::is_none")]
1452 pub collapsed_text: Option<String>,
1453}
1454
1455#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1457pub struct FoldingRangesResult {
1458 pub regions: Vec<FoldingRegion>,
1460 #[serde(default, skip_serializing_if = "is_false")]
1463 pub truncated: bool,
1464 #[serde(default, skip_serializing_if = "Option::is_none")]
1466 #[schemars(with = "PositionDegradation")]
1467 pub positions_degraded: Option<PositionDegradation>,
1468}
1469
1470#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
1472pub struct DocumentHighlightsResult {
1473 pub highlights: Vec<DocumentHighlightEntry>,
1475 #[serde(default, skip_serializing_if = "is_false")]
1479 pub truncated: bool,
1480 #[serde(default, skip_serializing_if = "Option::is_none")]
1484 #[schemars(with = "PositionDegradation")]
1485 pub positions_degraded: Option<PositionDegradation>,
1486}
1487
1488impl ServerText for Location {
1489 fn redact_server_text(&mut self, redactions: &Redactions) {
1490 let Self {
1491 uri,
1492 range: _,
1493 out_of_workspace: _,
1494 } = self;
1495 redactions.note_payload(uri);
1496 }
1497}
1498
1499impl ServerText for HoverResult {
1500 fn redact_server_text(&mut self, redactions: &Redactions) {
1501 let Self {
1502 contents,
1503 range: _,
1504 positions_degraded: _,
1505 } = self;
1506 redactions.redact_in_place(contents);
1507 }
1508}
1509
1510impl ServerText for LocationsResult {
1511 fn redact_server_text(&mut self, redactions: &Redactions) {
1512 let Self {
1513 locations,
1514 truncated: _,
1515 positions_degraded: _,
1516 enrichment: _,
1517 } = self;
1518 locations.redact_server_text(redactions);
1519 }
1520}
1521
1522impl ServerText for ReferencesResult {
1523 fn redact_server_text(&mut self, redactions: &Redactions) {
1524 let Self {
1525 result,
1526 index_coverage: _,
1527 } = self;
1528 result.redact_server_text(redactions);
1529 }
1530}
1531
1532impl ServerText for Diagnostic {
1533 fn redact_server_text(&mut self, redactions: &Redactions) {
1534 let Self {
1535 range: _,
1536 severity: _,
1537 message,
1538 code,
1539 } = self;
1540 redactions.redact_in_place(message);
1541 if let Some(code) = code {
1542 redactions.redact_in_place(code);
1543 }
1544 }
1545}
1546
1547impl ServerText for DiagnosticsResult {
1549 fn redact_server_text(&mut self, _redactions: &Redactions) {
1550 let Self {
1551 diagnostics: _,
1552 positions_degraded: _,
1553 } = self;
1554 }
1555}
1556
1557impl ServerText for DocumentDiagnosticsResult {
1558 fn redact_server_text(&mut self, _redactions: &Redactions) {
1559 let Self {
1560 diagnostics: _,
1561 positions_degraded: _,
1562 enrichment: _,
1563 } = self;
1564 }
1565}
1566
1567impl ServerText for TextEdit {
1568 fn redact_server_text(&mut self, redactions: &Redactions) {
1569 let Self { range: _, new_text } = self;
1570 redactions.note_payload(new_text);
1571 }
1572}
1573
1574impl ServerText for DocumentChanges {
1575 fn redact_server_text(&mut self, redactions: &Redactions) {
1576 let Self { uri, edits } = self;
1577 redactions.note_payload(uri);
1578 edits.redact_server_text(redactions);
1579 }
1580}
1581
1582impl ServerText for RenameResult {
1583 fn redact_server_text(&mut self, redactions: &Redactions) {
1584 let Self {
1585 changes,
1586 dropped: _,
1587 positions_degraded: _,
1588 } = self;
1589 changes.redact_server_text(redactions);
1590 }
1591}
1592
1593impl ServerText for Completion {
1594 fn redact_server_text(&mut self, redactions: &Redactions) {
1595 let Self {
1596 label,
1597 kind: _,
1598 detail,
1599 documentation,
1600 } = self;
1601 redactions.note_payload(label);
1602 for prose in [detail, documentation].into_iter().flatten() {
1603 redactions.redact_in_place(prose);
1604 }
1605 }
1606}
1607
1608impl ServerText for CompletionsResult {
1609 fn redact_server_text(&mut self, redactions: &Redactions) {
1610 let Self {
1611 items,
1612 positions_degraded: _,
1613 } = self;
1614 items.redact_server_text(redactions);
1615 }
1616}
1617
1618impl ServerText for Symbol {
1619 fn redact_server_text(&mut self, redactions: &Redactions) {
1620 let Self {
1621 name,
1622 kind: _,
1623 range: _,
1624 selection_range: _,
1625 children,
1626 } = self;
1627 redactions.note_payload(name);
1628 if let Some(children) = children {
1629 children.redact_server_text(redactions);
1630 }
1631 }
1632}
1633
1634impl ServerText for DocumentSymbolsResult {
1635 fn redact_server_text(&mut self, redactions: &Redactions) {
1636 let Self {
1637 symbols,
1638 positions_degraded: _,
1639 } = self;
1640 symbols.redact_server_text(redactions);
1641 }
1642}
1643
1644impl ServerText for FormatDocumentResult {
1645 fn redact_server_text(&mut self, redactions: &Redactions) {
1646 let Self {
1647 edits,
1648 positions_degraded: _,
1649 } = self;
1650 edits.redact_server_text(redactions);
1651 }
1652}
1653
1654impl ServerText for WorkspaceSymbol {
1655 fn redact_server_text(&mut self, redactions: &Redactions) {
1656 let Self {
1657 name,
1658 kind: _,
1659 location,
1660 container_name,
1661 } = self;
1662 redactions.note_payload(name);
1663 if let Some(container) = container_name {
1664 redactions.note_payload(container);
1665 }
1666 location.redact_server_text(redactions);
1667 }
1668}
1669
1670impl ServerText for WorkspaceSymbolResult {
1671 fn redact_server_text(&mut self, redactions: &Redactions) {
1672 let Self {
1673 symbols,
1674 truncated: _,
1675 positions_degraded: _,
1676 index_coverage: _,
1677 } = self;
1678 symbols.redact_server_text(redactions);
1679 }
1680}
1681
1682impl ServerText for CodeAction {
1683 fn redact_server_text(&mut self, redactions: &Redactions) {
1684 let Self {
1685 title,
1686 kind: _,
1687 diagnostics,
1688 edit,
1689 command,
1690 is_preferred: _,
1691 } = self;
1692 redactions.redact_in_place(title);
1693 diagnostics.redact_server_text(redactions);
1694 edit.redact_server_text(redactions);
1695 command.redact_server_text(redactions);
1696 }
1697}
1698
1699impl ServerText for WorkspaceEditDescription {
1700 fn redact_server_text(&mut self, redactions: &Redactions) {
1701 let Self {
1702 changes,
1703 dropped: _,
1704 } = self;
1705 changes.redact_server_text(redactions);
1706 }
1707}
1708
1709impl ServerText for CommandDescription {
1710 fn redact_server_text(&mut self, redactions: &Redactions) {
1711 let Self {
1712 title,
1713 command,
1714 arguments,
1715 } = self;
1716 redactions.redact_in_place(title);
1717 redactions.note_payload(command);
1718 for argument in arguments {
1719 redactions.note_payload_json(argument);
1720 }
1721 }
1722}
1723
1724impl ServerText for CodeActionsResult {
1725 fn redact_server_text(&mut self, redactions: &Redactions) {
1726 let Self {
1727 actions,
1728 truncated: _,
1729 positions_degraded: _,
1730 } = self;
1731 actions.redact_server_text(redactions);
1732 }
1733}
1734
1735impl ServerText for CallHierarchyPrepareResult {
1736 fn redact_server_text(&mut self, redactions: &Redactions) {
1737 let Self {
1738 items,
1739 truncated: _,
1740 positions_degraded: _,
1741 } = self;
1742 items.redact_server_text(redactions);
1743 }
1744}
1745
1746impl ServerText for IncomingCall {
1747 fn redact_server_text(&mut self, redactions: &Redactions) {
1748 let Self {
1749 from,
1750 from_ranges: _,
1751 } = self;
1752 from.redact_server_text(redactions);
1753 }
1754}
1755
1756impl ServerText for IncomingCallsResult {
1757 fn redact_server_text(&mut self, redactions: &Redactions) {
1758 let Self {
1759 calls,
1760 truncated: _,
1761 positions_degraded: _,
1762 } = self;
1763 calls.redact_server_text(redactions);
1764 }
1765}
1766
1767impl ServerText for OutgoingCall {
1768 fn redact_server_text(&mut self, redactions: &Redactions) {
1769 let Self { to, from_ranges: _ } = self;
1770 to.redact_server_text(redactions);
1771 }
1772}
1773
1774impl ServerText for OutgoingCallsResult {
1775 fn redact_server_text(&mut self, redactions: &Redactions) {
1776 let Self {
1777 calls,
1778 truncated: _,
1779 positions_degraded: _,
1780 } = self;
1781 calls.redact_server_text(redactions);
1782 }
1783}
1784
1785impl ServerText for ServerLogsResult {
1787 fn redact_server_text(&mut self, _redactions: &Redactions) {
1788 let Self { logs: _ } = self;
1789 }
1790}
1791
1792impl ServerText for ServerMessagesResult {
1793 fn redact_server_text(&mut self, _redactions: &Redactions) {
1794 let Self { messages: _ } = self;
1795 }
1796}
1797
1798impl ServerText for SignatureParameter {
1799 fn redact_server_text(&mut self, redactions: &Redactions) {
1800 let Self {
1801 label,
1802 documentation,
1803 } = self;
1804 for prose in [label, documentation].into_iter().flatten() {
1805 redactions.redact_in_place(prose);
1806 }
1807 }
1808}
1809
1810impl ServerText for SignatureInfo {
1811 fn redact_server_text(&mut self, redactions: &Redactions) {
1812 let Self {
1813 label,
1814 documentation,
1815 parameters,
1816 } = self;
1817 redactions.redact_in_place(label);
1818 if let Some(documentation) = documentation {
1819 redactions.redact_in_place(documentation);
1820 }
1821 parameters.redact_server_text(redactions);
1822 }
1823}
1824
1825impl ServerText for SignatureHelpResult {
1826 fn redact_server_text(&mut self, redactions: &Redactions) {
1827 let Self {
1828 signatures,
1829 active_signature: _,
1830 active_parameter: _,
1831 positions_degraded: _,
1832 } = self;
1833 signatures.redact_server_text(redactions);
1834 }
1835}
1836
1837impl ServerText for InlayHintEntry {
1838 fn redact_server_text(&mut self, redactions: &Redactions) {
1839 let Self {
1840 position: _,
1841 label,
1842 kind: _,
1843 padding_left: _,
1844 padding_right: _,
1845 tooltip,
1846 } = self;
1847 redactions.redact_in_place(label);
1848 if let Some(tooltip) = tooltip {
1849 redactions.redact_in_place(tooltip);
1850 }
1851 }
1852}
1853
1854impl ServerText for InlayHintsResult {
1855 fn redact_server_text(&mut self, redactions: &Redactions) {
1856 let Self {
1857 hints,
1858 truncated: _,
1859 positions_degraded: _,
1860 } = self;
1861 hints.redact_server_text(redactions);
1862 }
1863}
1864
1865impl ServerText for HierarchyItem {
1866 fn redact_server_text(&mut self, redactions: &Redactions) {
1867 let Self {
1868 name,
1869 kind: _,
1870 detail,
1871 uri,
1872 range: _,
1873 selection_range: _,
1874 data,
1875 out_of_workspace: _,
1876 } = self;
1877 redactions.note_payload(name);
1878 if let Some(detail) = detail {
1879 redactions.note_payload(detail);
1880 }
1881 redactions.note_payload(uri);
1882 if let Some(data) = data {
1883 redactions.note_payload_json(data);
1884 }
1885 }
1886}
1887
1888impl ServerText for TypeHierarchyResult {
1889 fn redact_server_text(&mut self, redactions: &Redactions) {
1890 let Self {
1891 items,
1892 truncated: _,
1893 positions_degraded: _,
1894 } = self;
1895 items.redact_server_text(redactions);
1896 }
1897}
1898
1899impl ServerText for PrepareRenameOutcome {
1900 fn redact_server_text(&mut self, redactions: &Redactions) {
1901 match self {
1902 Self::Renameable {
1903 range: _,
1904 placeholder,
1905 } => {
1906 if let Some(placeholder) = placeholder {
1907 redactions.note_payload(placeholder);
1908 }
1909 }
1910 Self::DefaultBehavior => {}
1911 Self::NotRenameable { server_message } => {
1912 if let Some(message) = server_message {
1913 redactions.redact_in_place(message);
1914 }
1915 }
1916 }
1917 }
1918}
1919
1920impl ServerText for PrepareRenameResult {
1921 fn redact_server_text(&mut self, redactions: &Redactions) {
1922 let Self {
1923 outcome,
1924 positions_degraded: _,
1925 } = self;
1926 outcome.redact_server_text(redactions);
1927 }
1928}
1929
1930impl ServerText for DocumentHighlightsResult {
1931 fn redact_server_text(&mut self, _redactions: &Redactions) {
1932 let Self {
1933 highlights: _,
1934 truncated: _,
1935 positions_degraded: _,
1936 } = self;
1937 }
1938}
1939
1940impl ServerText for FoldingRangesResult {
1941 fn redact_server_text(&mut self, redactions: &Redactions) {
1942 let Self {
1943 regions,
1944 truncated: _,
1945 positions_degraded: _,
1946 } = self;
1947 for region in regions {
1948 if let Some(text) = &mut region.collapsed_text {
1949 redactions.redact_in_place(text);
1950 *text =
1951 crate::util::truncate_string(std::mem::take(text), MAX_COLLAPSED_TEXT_BYTES);
1952 }
1953 }
1954 }
1955}
1956
1957impl ServerText for SelectionRangesResult {
1958 fn redact_server_text(&mut self, _redactions: &Redactions) {
1959 let Self {
1960 ranges: _,
1961 truncated: _,
1962 positions_degraded: _,
1963 } = self;
1964 }
1965}
1966
1967#[cfg(test)]
1968mod tests {
1969 use std::assert_matches;
1970
1971 fn hierarchy_wire(range: [u32; 4], selection: [u32; 4]) -> HierarchyItem {
1972 let at = |line, character| serde_json::json!({"line": line, "character": character});
1973 serde_json::from_value(serde_json::json!({
1974 "name": "x", "kind": 5, "uri": "file:///a.rs",
1975 "range": {"start": at(range[0], range[1]), "end": at(range[2], range[3])},
1976 "selectionRange": {"start": at(selection[0], selection[1]), "end": at(selection[2], selection[3])},
1977 }))
1978 .unwrap()
1979 }
1980
1981 #[test]
1982 fn checked_hierarchy_item_accepts_valid_ranges() {
1983 let item = CheckedHierarchyItem::from_client(hierarchy_wire([1, 1, 2, 1], [1, 7, 1, 9]));
1984 assert_eq!(item.unwrap().uri(), "file:///a.rs");
1985 }
1986
1987 #[test]
1988 fn checked_hierarchy_item_rejects_bad_range_and_names_it() {
1989 for bad in [
1990 [0, 1, 1, 1],
1991 [1, 0, 1, 1],
1992 [1, 1, 1, 1_000_001],
1993 [1, 1, u32::MAX, 1],
1994 [2, 1, 1, 1],
1995 ] {
1996 let err =
1997 CheckedHierarchyItem::from_client(hierarchy_wire(bad, [1, 1, 1, 1])).unwrap_err();
1998 assert_matches!(err, InvalidHierarchyItem::Range(_), "{bad:?}");
1999 let err =
2000 CheckedHierarchyItem::from_client(hierarchy_wire([1, 1, 9, 1], bad)).unwrap_err();
2001 assert_matches!(err, InvalidHierarchyItem::SelectionRange(_), "{bad:?}");
2002 }
2003 }
2004
2005 #[test]
2006 fn checked_hierarchy_item_rejects_selection_column_above_max() {
2007 let err = CheckedHierarchyItem::from_client(hierarchy_wire(
2008 [1, 1, 2, 1],
2009 [1, 1_000_001, 1, 1_000_001],
2010 ))
2011 .unwrap_err();
2012 assert_eq!(
2013 err,
2014 InvalidHierarchyItem::SelectionRange(InvalidRange::Start(InvalidPosition::TooLarge))
2015 );
2016 }
2017
2018 use super::*;
2019
2020 #[test]
2021 fn test_text_fields_report_the_field_and_the_rule() {
2022 let blank = NewName::new(" ").unwrap_err();
2023 assert_eq!(blank.to_string(), "`new_name` cannot be empty");
2024
2025 let long = NewName::new("x".repeat(MAX_NEW_NAME_LENGTH + 1)).unwrap_err();
2026 assert_eq!(
2027 long.reason,
2028 InvalidBoundedText::TooLong {
2029 len: MAX_NEW_NAME_LENGTH + 1,
2030 max: MAX_NEW_NAME_LENGTH,
2031 }
2032 );
2033 assert_eq!(
2034 NewName::new(" keeps spaces ").unwrap().as_str(),
2035 " keeps spaces "
2036 );
2037
2038 let empty = CompletionTrigger::new("").unwrap_err();
2039 assert_eq!(empty.field, TextField::Trigger);
2040 assert_eq!(empty.reason, InvalidBoundedText::Blank);
2041 assert_eq!(CompletionTrigger::new(" ").unwrap().as_str(), " ");
2042 assert!(CompletionTrigger::new("x".repeat(MAX_TRIGGER_CHARACTER_BYTES)).is_ok());
2043 assert_eq!(
2044 CompletionTrigger::new("x".repeat(MAX_TRIGGER_CHARACTER_BYTES + 1))
2045 .unwrap_err()
2046 .reason,
2047 InvalidBoundedText::TooLong {
2048 len: MAX_TRIGGER_CHARACTER_BYTES + 1,
2049 max: MAX_TRIGGER_CHARACTER_BYTES,
2050 }
2051 );
2052 }
2053
2054 const SECRET: &str = "SuperSecretValue123";
2055
2056 fn range() -> Range {
2057 Range {
2058 start: Position2D {
2059 line: 1,
2060 character: 1,
2061 },
2062 end: Position2D {
2063 line: 1,
2064 character: 2,
2065 },
2066 }
2067 }
2068
2069 #[test]
2070 fn test_server_text_redacts_prose_and_leaves_edit_payload() {
2071 let set = Redactions::new([("API_TOKEN".to_owned(), SECRET.to_owned())]);
2072 let mut actions = vec![CodeAction {
2073 title: format!("fix {SECRET}"),
2074 kind: None,
2075 diagnostics: vec![Diagnostic {
2076 range: range(),
2077 severity: ReportedSeverity::Error,
2078 message: format!("bad {SECRET}"),
2079 code: Some(format!("E-{SECRET}")),
2080 }],
2081 edit: Some(WorkspaceEditDescription {
2082 changes: vec![DocumentChanges {
2083 uri: format!("file:///{SECRET}.rs"),
2084 edits: vec![TextEdit {
2085 range: range(),
2086 new_text: format!("let k = \"{SECRET}\";"),
2087 }],
2088 }],
2089 dropped: DroppedEdits::default(),
2090 }),
2091 command: None,
2092 is_preferred: false,
2093 }];
2094
2095 actions.redact_server_text(&set);
2096
2097 let action = &actions[0];
2098 assert_eq!(action.title, "fix [redacted:API_TOKEN]");
2099 assert_eq!(action.diagnostics[0].message, "bad [redacted:API_TOKEN]");
2100 assert_eq!(
2101 action.diagnostics[0].code.as_deref(),
2102 Some("E-[redacted:API_TOKEN]")
2103 );
2104 let changes = &action.edit.as_ref().unwrap().changes[0];
2105 assert!(changes.uri.contains(SECRET));
2106 assert!(changes.edits[0].new_text.contains(SECRET));
2107 }
2108
2109 #[test]
2110 fn test_server_text_completion_keeps_label_and_redacts_detail_and_documentation() {
2111 let set = Redactions::new([("API_TOKEN".to_owned(), SECRET.to_owned())]);
2112 let mut completion = Completion {
2113 label: format!("use_{SECRET}"),
2114 kind: Some(3),
2115 detail: Some(format!("fn {SECRET}()")),
2116 documentation: Some(format!("docs {SECRET}")),
2117 };
2118
2119 completion.redact_server_text(&set);
2120
2121 assert_eq!(completion.label, format!("use_{SECRET}"));
2122 assert_eq!(
2123 completion.detail.as_deref(),
2124 Some("fn [redacted:API_TOKEN]()")
2125 );
2126 assert_eq!(
2127 completion.documentation.as_deref(),
2128 Some("docs [redacted:API_TOKEN]")
2129 );
2130 }
2131
2132 #[test]
2133 fn test_server_text_redacts_signature_label_documentation_and_parameters() {
2134 let set = Redactions::new([("API_TOKEN".to_owned(), SECRET.to_owned())]);
2135 let mut result = SignatureHelpResult {
2136 signatures: vec![SignatureInfo {
2137 label: format!("f({SECRET})"),
2138 documentation: Some(format!("doc {SECRET}")),
2139 parameters: vec![SignatureParameter {
2140 label: Some(format!("p {SECRET}")),
2141 documentation: Some(format!("pd {SECRET}")),
2142 }],
2143 }],
2144 active_signature: Some(0),
2145 active_parameter: Some(0),
2146 positions_degraded: None,
2147 };
2148
2149 result.redact_server_text(&set);
2150
2151 let json = serde_json::to_string(&result).unwrap();
2152 assert!(!json.contains(SECRET), "{json}");
2153 assert_eq!(json.matches("[redacted:API_TOKEN]").count(), 4, "{json}");
2154 }
2155
2156 #[test]
2157 fn test_server_text_redacts_inlay_label_and_tooltip() {
2158 let set = Redactions::new([("API_TOKEN".to_owned(), SECRET.to_owned())]);
2159 let mut hint = InlayHintEntry {
2160 position: Position2D {
2161 line: 1,
2162 character: 1,
2163 },
2164 label: format!(": {SECRET}"),
2165 kind: None,
2166 padding_left: None,
2167 padding_right: None,
2168 tooltip: Some(format!("tip {SECRET}")),
2169 };
2170
2171 hint.redact_server_text(&set);
2172
2173 assert_eq!(hint.label, ": [redacted:API_TOKEN]");
2174 assert_eq!(hint.tooltip.as_deref(), Some("tip [redacted:API_TOKEN]"));
2175 }
2176
2177 #[test]
2178 fn test_server_text_command_redacts_title_and_keeps_command_and_arguments() {
2179 let set = Redactions::new([("API_TOKEN".to_owned(), SECRET.to_owned())]);
2180 let mut command = CommandDescription {
2181 title: format!("run {SECRET}"),
2182 command: format!("cmd.{SECRET}"),
2183 arguments: vec![serde_json::json!({ "key": SECRET })],
2184 };
2185
2186 command.redact_server_text(&set);
2187
2188 assert_eq!(command.title, "run [redacted:API_TOKEN]");
2189 assert_eq!(command.command, format!("cmd.{SECRET}"));
2190 assert_eq!(
2191 command.arguments,
2192 vec![serde_json::json!({ "key": SECRET })]
2193 );
2194 }
2195
2196 #[test]
2197 fn test_server_text_leaves_symbol_and_hierarchy_identifiers() {
2198 let set = Redactions::new([("API_TOKEN".to_owned(), SECRET.to_owned())]);
2199 let mut symbols = DocumentSymbolsResult {
2200 symbols: vec![Symbol {
2201 name: format!("sym_{SECRET}"),
2202 kind: 12,
2203 range: range(),
2204 selection_range: range(),
2205 children: Some(vec![Symbol {
2206 name: format!("child_{SECRET}"),
2207 kind: 12,
2208 range: range(),
2209 selection_range: range(),
2210 children: None,
2211 }]),
2212 }],
2213 positions_degraded: None,
2214 };
2215 let mut item = HierarchyItem {
2216 name: format!("call_{SECRET}"),
2217 kind: 12,
2218 detail: Some(format!("detail {SECRET}")),
2219 uri: format!("file:///{SECRET}.rs"),
2220 range: range(),
2221 selection_range: range(),
2222 data: Some(serde_json::json!({ "id": SECRET })),
2223 out_of_workspace: false,
2224 };
2225 let mut type_item = HierarchyItem {
2226 name: format!("type_{SECRET}"),
2227 kind: 5,
2228 detail: None,
2229 uri: format!("file:///{SECRET}.rs"),
2230 range: range(),
2231 selection_range: range(),
2232 data: None,
2233 out_of_workspace: false,
2234 };
2235 let before = (
2236 serde_json::to_string(&symbols).unwrap(),
2237 serde_json::to_string(&item).unwrap(),
2238 serde_json::to_string(&type_item).unwrap(),
2239 );
2240
2241 symbols.redact_server_text(&set);
2242 item.redact_server_text(&set);
2243 type_item.redact_server_text(&set);
2244
2245 let after = (
2246 serde_json::to_string(&symbols).unwrap(),
2247 serde_json::to_string(&item).unwrap(),
2248 serde_json::to_string(&type_item).unwrap(),
2249 );
2250 assert_eq!(before, after);
2251 }
2252
2253 #[test]
2254 fn test_server_text_with_no_secrets_rewrites_nothing() {
2255 let mut hover = HoverResult {
2256 contents: format!("doc {SECRET}"),
2257 range: None,
2258 positions_degraded: None,
2259 };
2260
2261 hover.redact_server_text(&Redactions::default());
2262
2263 assert_eq!(hover.contents, format!("doc {SECRET}"));
2264 }
2265
2266 #[test]
2267 fn test_server_text_does_not_touch_already_redacted_diagnostics() {
2268 let set = Redactions::new([("API_TOKEN".to_owned(), SECRET.to_owned())]);
2269 let mut result = DocumentDiagnosticsResult {
2270 diagnostics: vec![ContextualDiagnostic::from(Diagnostic {
2271 range: range(),
2272 severity: ReportedSeverity::Error,
2273 message: format!("echo {SECRET}"),
2274 code: None,
2275 })],
2276 positions_degraded: None,
2277 enrichment: None,
2278 };
2279
2280 result.redact_server_text(&set);
2281
2282 assert_eq!(result.diagnostics[0].message, format!("echo {SECRET}"));
2283 }
2284
2285 #[test]
2286 fn test_server_text_redacts_hover_and_prepare_rename_message() {
2287 let set = Redactions::new([("API_TOKEN".to_owned(), SECRET.to_owned())]);
2288 let mut hover = HoverResult {
2289 contents: format!("doc {SECRET}"),
2290 range: None,
2291 positions_degraded: None,
2292 };
2293 let mut outcome = PrepareRenameOutcome::NotRenameable {
2294 server_message: Some(format!("no {SECRET}")),
2295 };
2296
2297 hover.redact_server_text(&set);
2298 outcome.redact_server_text(&set);
2299
2300 assert_eq!(hover.contents, "doc [redacted:API_TOKEN]");
2301 assert_eq!(
2302 outcome,
2303 PrepareRenameOutcome::NotRenameable {
2304 server_message: Some("no [redacted:API_TOKEN]".to_owned())
2305 }
2306 );
2307 }
2308
2309 #[test]
2310 fn test_position_degradation_request_outranks_response() {
2311 assert!(PositionDegradation::Request > PositionDegradation::Response);
2312 assert_eq!(
2313 Some(PositionDegradation::Response).max(Some(PositionDegradation::Request)),
2314 Some(PositionDegradation::Request)
2315 );
2316 assert_eq!(
2317 None.max(Some(PositionDegradation::Response)),
2318 Some(PositionDegradation::Response)
2319 );
2320 }
2321
2322 #[test]
2323 fn test_position_degradation_serializes_as_snake_case_string() {
2324 assert_eq!(
2325 serde_json::to_value(PositionDegradation::Request).unwrap(),
2326 "request"
2327 );
2328 assert_eq!(
2329 serde_json::to_value(PositionDegradation::Response).unwrap(),
2330 "response"
2331 );
2332 }
2333
2334 #[test]
2335 fn test_positions_degraded_key_omitted_when_none() {
2336 let result = super::DocumentSymbolsResult {
2337 symbols: Vec::new(),
2338 positions_degraded: None,
2339 };
2340 let value = serde_json::to_value(&result).unwrap();
2341 assert!(value.get("positions_degraded").is_none());
2342
2343 let result = super::DocumentSymbolsResult {
2344 positions_degraded: Some(PositionDegradation::Response),
2345 ..result
2346 };
2347 let value = serde_json::to_value(&result).unwrap();
2348 assert_eq!(value["positions_degraded"], "response");
2349 }
2350
2351 #[test]
2356 fn test_lsp_kind_to_u32_preserves_custom_values_above_u8_range() {
2357 let kind = lsp_types::InlayHintKind::Custom(300);
2358 assert_eq!(lsp_kind_to_u32(kind), 300u32);
2359 }
2360
2361 #[test]
2362 fn test_position_from_client_bounds() {
2363 assert_eq!(Position::from_client(0, 1), Err(InvalidPosition::ZeroBased));
2364 assert_eq!(Position::from_client(1, 0), Err(InvalidPosition::ZeroBased));
2365 assert_eq!(
2366 Position::from_client(MAX_POSITION_VALUE + 1, 1),
2367 Err(InvalidPosition::TooLarge)
2368 );
2369 assert_eq!(
2370 Position::from_client(1, MAX_POSITION_VALUE + 1),
2371 Err(InvalidPosition::TooLarge)
2372 );
2373 let max = Position::from_client(MAX_POSITION_VALUE, MAX_POSITION_VALUE).unwrap();
2374 assert_eq!((max.lsp_line(), max.lsp_character()), (999_999, 999_999));
2375 }
2376
2377 #[test]
2378 fn test_position_from_output_position_is_not_capped_and_clamps_zero() {
2379 let big = Position::from_server_output(&Position2D {
2380 line: MAX_POSITION_VALUE + 7,
2381 character: 2_000_000,
2382 });
2383 assert_eq!(big.line().get(), MAX_POSITION_VALUE + 7);
2384 let clamped = Position::from_server_output(&Position2D {
2385 line: 0,
2386 character: 0,
2387 });
2388 assert_eq!((clamped.line().get(), clamped.character().get()), (1, 1));
2389 }
2390
2391 #[test]
2392 fn test_position_range_orders_and_bounds() {
2393 let at = Position::at;
2394 assert!(PositionRange::new(at(1, 5), at(1, 5)).is_ok());
2395 assert_eq!(
2396 PositionRange::new(at(2, 1), at(1, 9)),
2397 Err(InvalidRange::Reversed)
2398 );
2399 assert_eq!(
2400 PositionRange::new(at(1, 5), at(1, 4)),
2401 Err(InvalidRange::Reversed)
2402 );
2403 assert_eq!(
2404 PositionRange::from_client((0, 1), (1, 1)),
2405 Err(InvalidRange::Start(InvalidPosition::ZeroBased))
2406 );
2407 assert_eq!(
2408 PositionRange::from_client((1, 1), (1, 0)),
2409 Err(InvalidRange::End(InvalidPosition::ZeroBased))
2410 );
2411
2412 let within = PositionRange::new(at(1, 1), at(1 + MAX_RANGE_LINES, 1)).unwrap();
2413 assert!(BoundedRange::try_from(within).is_ok());
2414 let beyond = PositionRange::new(at(1, 1), at(2 + MAX_RANGE_LINES, 1)).unwrap();
2415 assert_eq!(
2416 BoundedRange::try_from(beyond),
2417 Err(InvalidRange::TooManyLines)
2418 );
2419 }
2420
2421 #[test]
2422 fn test_folding_kind_schema_lists_exactly_the_serde_names() {
2423 let kinds = |k: FoldingKind| match k {
2424 FoldingKind::Comment
2425 | FoldingKind::Imports
2426 | FoldingKind::Region
2427 | FoldingKind::Unspecified => k,
2428 };
2429 let variants = [
2430 FoldingKind::Comment,
2431 FoldingKind::Imports,
2432 FoldingKind::Region,
2433 FoldingKind::Unspecified,
2434 ]
2435 .map(kinds);
2436 let mut generator = schemars::SchemaGenerator::default();
2437 let schema = FoldingKind::json_schema(&mut generator);
2438 let listed: Vec<String> = schema
2439 .get("enum")
2440 .unwrap()
2441 .as_array()
2442 .unwrap()
2443 .iter()
2444 .map(|name| name.as_str().unwrap().to_owned())
2445 .collect();
2446 let serialized: Vec<String> = variants
2447 .iter()
2448 .map(|k| {
2449 serde_json::to_value(k)
2450 .unwrap()
2451 .as_str()
2452 .unwrap()
2453 .to_owned()
2454 })
2455 .collect();
2456 assert_eq!(listed, serialized);
2457 }
2458
2459 #[test]
2460 fn test_folding_kind_filter_schema_lists_exactly_the_serde_names() {
2461 let filters = |f: FoldingKindFilter| match f {
2462 FoldingKindFilter::All
2463 | FoldingKindFilter::Comment
2464 | FoldingKindFilter::Imports
2465 | FoldingKindFilter::Region => f,
2466 };
2467 let variants = FoldingKindFilter::ALL.map(filters);
2468 let schema = FoldingKindFilter::schema();
2469 let listed: Vec<FoldingKindFilter> = schema
2470 .get("enum")
2471 .unwrap()
2472 .as_array()
2473 .unwrap()
2474 .iter()
2475 .map(|name| FoldingKindFilter::parse(name.as_str().unwrap()).unwrap())
2476 .collect();
2477 assert_eq!(listed, variants);
2478 assert_eq!(
2479 FoldingKindFilter::parse("IMPORTS"),
2480 Some(FoldingKindFilter::Imports)
2481 );
2482 assert_eq!(FoldingKindFilter::parse("unspecified"), None);
2483 }
2484
2485 #[test]
2486 fn test_folding_collapsed_text_is_redacted_before_it_is_cut() {
2487 use crate::redaction::{Redactions, ServerText as _};
2488
2489 let secret = "bravo-secret-222";
2490 let redactions = Redactions::new([("B_TOKEN".to_owned(), secret.to_owned())]);
2491 let straddling = format!("{}{secret}tail", "x".repeat(MAX_COLLAPSED_TEXT_BYTES - 8));
2492 let region = |text: String| FoldingRegion {
2493 start_line: 1,
2494 end_line: 2,
2495 start_character: None,
2496 end_character: None,
2497 kind: FoldingKind::Region,
2498 collapsed_text: Some(text),
2499 };
2500 let mut result = FoldingRangesResult {
2501 regions: vec![region(straddling), region("short".to_string())],
2502 truncated: false,
2503 positions_degraded: None,
2504 };
2505
2506 result.redact_server_text(&redactions);
2507
2508 let text = result.regions[0].collapsed_text.as_deref().unwrap();
2509 assert!(!text.contains("bravo"), "{text}");
2510 assert!(text.ends_with("... (truncated)"), "{text}");
2511 assert!(text.len() <= MAX_COLLAPSED_TEXT_BYTES + "... (truncated)".len());
2512 assert_eq!(result.regions[1].collapsed_text.as_deref(), Some("short"));
2513 }
2514}