]> git.proxmox.com Git - rustc.git/blob - src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
New upstream version 1.74.1+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / rust-analyzer / src / lsp / to_proto.rs
1 //! Conversion of rust-analyzer specific types to lsp_types equivalents.
2 use std::{
3 iter::once,
4 path,
5 sync::atomic::{AtomicU32, Ordering},
6 };
7
8 use ide::{
9 Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem,
10 CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
11 Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel,
12 InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup,
13 NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp,
14 SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
15 };
16 use ide_db::rust_doc::format_docs;
17 use itertools::Itertools;
18 use serde_json::to_value;
19 use vfs::AbsPath;
20
21 use crate::{
22 cargo_target_spec::CargoTargetSpec,
23 config::{CallInfoConfig, Config},
24 global_state::GlobalStateSnapshot,
25 line_index::{LineEndings, LineIndex, PositionEncoding},
26 lsp::{
27 semantic_tokens::{self, standard_fallback_type},
28 utils::invalid_params_error,
29 LspError,
30 },
31 lsp_ext::{self, SnippetTextEdit},
32 };
33
34 pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
35 let line_col = line_index.index.line_col(offset);
36 match line_index.encoding {
37 PositionEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col),
38 PositionEncoding::Wide(enc) => {
39 let line_col = line_index.index.to_wide(enc, line_col).unwrap();
40 lsp_types::Position::new(line_col.line, line_col.col)
41 }
42 }
43 }
44
45 pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range {
46 let start = position(line_index, range.start());
47 let end = position(line_index, range.end());
48 lsp_types::Range::new(start, end)
49 }
50
51 pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
52 match symbol_kind {
53 SymbolKind::Function => lsp_types::SymbolKind::FUNCTION,
54 SymbolKind::Struct => lsp_types::SymbolKind::STRUCT,
55 SymbolKind::Enum => lsp_types::SymbolKind::ENUM,
56 SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER,
57 SymbolKind::Trait | SymbolKind::TraitAlias => lsp_types::SymbolKind::INTERFACE,
58 SymbolKind::Macro
59 | SymbolKind::BuiltinAttr
60 | SymbolKind::Attribute
61 | SymbolKind::Derive
62 | SymbolKind::DeriveHelper => lsp_types::SymbolKind::FUNCTION,
63 SymbolKind::Module | SymbolKind::ToolModule => lsp_types::SymbolKind::MODULE,
64 SymbolKind::TypeAlias | SymbolKind::TypeParam | SymbolKind::SelfType => {
65 lsp_types::SymbolKind::TYPE_PARAMETER
66 }
67 SymbolKind::Field => lsp_types::SymbolKind::FIELD,
68 SymbolKind::Static => lsp_types::SymbolKind::CONSTANT,
69 SymbolKind::Const => lsp_types::SymbolKind::CONSTANT,
70 SymbolKind::ConstParam => lsp_types::SymbolKind::CONSTANT,
71 SymbolKind::Impl => lsp_types::SymbolKind::OBJECT,
72 SymbolKind::Local
73 | SymbolKind::SelfParam
74 | SymbolKind::LifetimeParam
75 | SymbolKind::ValueParam
76 | SymbolKind::Label => lsp_types::SymbolKind::VARIABLE,
77 SymbolKind::Union => lsp_types::SymbolKind::STRUCT,
78 }
79 }
80
81 pub(crate) fn structure_node_kind(kind: StructureNodeKind) -> lsp_types::SymbolKind {
82 match kind {
83 StructureNodeKind::SymbolKind(symbol) => symbol_kind(symbol),
84 StructureNodeKind::Region => lsp_types::SymbolKind::NAMESPACE,
85 }
86 }
87
88 pub(crate) fn document_highlight_kind(
89 category: ReferenceCategory,
90 ) -> Option<lsp_types::DocumentHighlightKind> {
91 match category {
92 ReferenceCategory::Read => Some(lsp_types::DocumentHighlightKind::READ),
93 ReferenceCategory::Write => Some(lsp_types::DocumentHighlightKind::WRITE),
94 ReferenceCategory::Import => None,
95 }
96 }
97
98 pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {
99 match severity {
100 Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
101 Severity::Warning => lsp_types::DiagnosticSeverity::WARNING,
102 Severity::WeakWarning => lsp_types::DiagnosticSeverity::HINT,
103 // unreachable
104 Severity::Allow => lsp_types::DiagnosticSeverity::INFORMATION,
105 }
106 }
107
108 pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Documentation {
109 let value = format_docs(&documentation);
110 let markup_content = lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value };
111 lsp_types::Documentation::MarkupContent(markup_content)
112 }
113
114 pub(crate) fn completion_item_kind(
115 completion_item_kind: CompletionItemKind,
116 ) -> lsp_types::CompletionItemKind {
117 match completion_item_kind {
118 CompletionItemKind::Binding => lsp_types::CompletionItemKind::VARIABLE,
119 CompletionItemKind::BuiltinType => lsp_types::CompletionItemKind::STRUCT,
120 CompletionItemKind::InferredType => lsp_types::CompletionItemKind::SNIPPET,
121 CompletionItemKind::Keyword => lsp_types::CompletionItemKind::KEYWORD,
122 CompletionItemKind::Method => lsp_types::CompletionItemKind::METHOD,
123 CompletionItemKind::Snippet => lsp_types::CompletionItemKind::SNIPPET,
124 CompletionItemKind::UnresolvedReference => lsp_types::CompletionItemKind::REFERENCE,
125 CompletionItemKind::SymbolKind(symbol) => match symbol {
126 SymbolKind::Attribute => lsp_types::CompletionItemKind::FUNCTION,
127 SymbolKind::Const => lsp_types::CompletionItemKind::CONSTANT,
128 SymbolKind::ConstParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
129 SymbolKind::Derive => lsp_types::CompletionItemKind::FUNCTION,
130 SymbolKind::DeriveHelper => lsp_types::CompletionItemKind::FUNCTION,
131 SymbolKind::Enum => lsp_types::CompletionItemKind::ENUM,
132 SymbolKind::Field => lsp_types::CompletionItemKind::FIELD,
133 SymbolKind::Function => lsp_types::CompletionItemKind::FUNCTION,
134 SymbolKind::Impl => lsp_types::CompletionItemKind::TEXT,
135 SymbolKind::Label => lsp_types::CompletionItemKind::VARIABLE,
136 SymbolKind::LifetimeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
137 SymbolKind::Local => lsp_types::CompletionItemKind::VARIABLE,
138 SymbolKind::Macro => lsp_types::CompletionItemKind::FUNCTION,
139 SymbolKind::Module => lsp_types::CompletionItemKind::MODULE,
140 SymbolKind::SelfParam => lsp_types::CompletionItemKind::VALUE,
141 SymbolKind::SelfType => lsp_types::CompletionItemKind::TYPE_PARAMETER,
142 SymbolKind::Static => lsp_types::CompletionItemKind::VALUE,
143 SymbolKind::Struct => lsp_types::CompletionItemKind::STRUCT,
144 SymbolKind::Trait => lsp_types::CompletionItemKind::INTERFACE,
145 SymbolKind::TraitAlias => lsp_types::CompletionItemKind::INTERFACE,
146 SymbolKind::TypeAlias => lsp_types::CompletionItemKind::STRUCT,
147 SymbolKind::TypeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
148 SymbolKind::Union => lsp_types::CompletionItemKind::STRUCT,
149 SymbolKind::ValueParam => lsp_types::CompletionItemKind::VALUE,
150 SymbolKind::Variant => lsp_types::CompletionItemKind::ENUM_MEMBER,
151 SymbolKind::BuiltinAttr => lsp_types::CompletionItemKind::FUNCTION,
152 SymbolKind::ToolModule => lsp_types::CompletionItemKind::MODULE,
153 },
154 }
155 }
156
157 pub(crate) fn text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::TextEdit {
158 let range = range(line_index, indel.delete);
159 let new_text = match line_index.endings {
160 LineEndings::Unix => indel.insert,
161 LineEndings::Dos => indel.insert.replace('\n', "\r\n"),
162 };
163 lsp_types::TextEdit { range, new_text }
164 }
165
166 pub(crate) fn completion_text_edit(
167 line_index: &LineIndex,
168 insert_replace_support: Option<lsp_types::Position>,
169 indel: Indel,
170 ) -> lsp_types::CompletionTextEdit {
171 let text_edit = text_edit(line_index, indel);
172 match insert_replace_support {
173 Some(cursor_pos) => lsp_types::InsertReplaceEdit {
174 new_text: text_edit.new_text,
175 insert: lsp_types::Range { start: text_edit.range.start, end: cursor_pos },
176 replace: text_edit.range,
177 }
178 .into(),
179 None => text_edit.into(),
180 }
181 }
182
183 pub(crate) fn snippet_text_edit(
184 line_index: &LineIndex,
185 is_snippet: bool,
186 indel: Indel,
187 ) -> lsp_ext::SnippetTextEdit {
188 let text_edit = text_edit(line_index, indel);
189 let insert_text_format =
190 if is_snippet { Some(lsp_types::InsertTextFormat::SNIPPET) } else { None };
191 lsp_ext::SnippetTextEdit {
192 range: text_edit.range,
193 new_text: text_edit.new_text,
194 insert_text_format,
195 annotation_id: None,
196 }
197 }
198
199 pub(crate) fn text_edit_vec(
200 line_index: &LineIndex,
201 text_edit: TextEdit,
202 ) -> Vec<lsp_types::TextEdit> {
203 text_edit.into_iter().map(|indel| self::text_edit(line_index, indel)).collect()
204 }
205
206 pub(crate) fn snippet_text_edit_vec(
207 line_index: &LineIndex,
208 is_snippet: bool,
209 text_edit: TextEdit,
210 ) -> Vec<lsp_ext::SnippetTextEdit> {
211 text_edit
212 .into_iter()
213 .map(|indel| self::snippet_text_edit(line_index, is_snippet, indel))
214 .collect()
215 }
216
217 pub(crate) fn completion_items(
218 config: &Config,
219 line_index: &LineIndex,
220 tdpp: lsp_types::TextDocumentPositionParams,
221 items: Vec<CompletionItem>,
222 ) -> Vec<lsp_types::CompletionItem> {
223 let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default();
224 let mut res = Vec::with_capacity(items.len());
225 for item in items {
226 completion_item(&mut res, config, line_index, &tdpp, max_relevance, item);
227 }
228
229 if let Some(limit) = config.completion().limit {
230 res.sort_by(|item1, item2| item1.sort_text.cmp(&item2.sort_text));
231 res.truncate(limit);
232 }
233
234 res
235 }
236
237 fn completion_item(
238 acc: &mut Vec<lsp_types::CompletionItem>,
239 config: &Config,
240 line_index: &LineIndex,
241 tdpp: &lsp_types::TextDocumentPositionParams,
242 max_relevance: u32,
243 item: CompletionItem,
244 ) {
245 let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
246 let ref_match = item.ref_match();
247 let lookup = item.lookup().to_string();
248
249 let mut additional_text_edits = Vec::new();
250
251 // LSP does not allow arbitrary edits in completion, so we have to do a
252 // non-trivial mapping here.
253 let text_edit = {
254 let mut text_edit = None;
255 let source_range = item.source_range;
256 for indel in item.text_edit {
257 if indel.delete.contains_range(source_range) {
258 // Extract this indel as the main edit
259 text_edit = Some(if indel.delete == source_range {
260 self::completion_text_edit(line_index, insert_replace_support, indel.clone())
261 } else {
262 assert!(source_range.end() == indel.delete.end());
263 let range1 = TextRange::new(indel.delete.start(), source_range.start());
264 let range2 = source_range;
265 let indel1 = Indel::delete(range1);
266 let indel2 = Indel::replace(range2, indel.insert.clone());
267 additional_text_edits.push(self::text_edit(line_index, indel1));
268 self::completion_text_edit(line_index, insert_replace_support, indel2)
269 })
270 } else {
271 assert!(source_range.intersect(indel.delete).is_none());
272 let text_edit = self::text_edit(line_index, indel.clone());
273 additional_text_edits.push(text_edit);
274 }
275 }
276 text_edit.unwrap()
277 };
278
279 let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET);
280 let tags = item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]);
281 let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints {
282 Some(command::trigger_parameter_hints())
283 } else {
284 None
285 };
286
287 let mut lsp_item = lsp_types::CompletionItem {
288 label: item.label.to_string(),
289 detail: item.detail,
290 filter_text: Some(lookup),
291 kind: Some(completion_item_kind(item.kind)),
292 text_edit: Some(text_edit),
293 additional_text_edits: Some(additional_text_edits),
294 documentation: item.documentation.map(documentation),
295 deprecated: Some(item.deprecated),
296 tags,
297 command,
298 insert_text_format,
299 ..Default::default()
300 };
301
302 if config.completion_label_details_support() {
303 lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails {
304 detail: None,
305 description: lsp_item.detail.clone(),
306 });
307 }
308
309 set_score(&mut lsp_item, max_relevance, item.relevance);
310
311 if config.completion().enable_imports_on_the_fly {
312 if !item.import_to_add.is_empty() {
313 let imports: Vec<_> = item
314 .import_to_add
315 .into_iter()
316 .filter_map(|(import_path, import_name)| {
317 Some(lsp_ext::CompletionImport {
318 full_import_path: import_path,
319 imported_name: import_name,
320 })
321 })
322 .collect();
323 if !imports.is_empty() {
324 let data = lsp_ext::CompletionResolveData { position: tdpp.clone(), imports };
325 lsp_item.data = Some(to_value(data).unwrap());
326 }
327 }
328 }
329
330 if let Some((label, indel, relevance)) = ref_match {
331 let mut lsp_item_with_ref = lsp_types::CompletionItem { label, ..lsp_item.clone() };
332 lsp_item_with_ref
333 .additional_text_edits
334 .get_or_insert_with(Default::default)
335 .push(self::text_edit(line_index, indel));
336 set_score(&mut lsp_item_with_ref, max_relevance, relevance);
337 acc.push(lsp_item_with_ref);
338 };
339
340 acc.push(lsp_item);
341
342 fn set_score(
343 res: &mut lsp_types::CompletionItem,
344 max_relevance: u32,
345 relevance: CompletionRelevance,
346 ) {
347 if relevance.is_relevant() && relevance.score() == max_relevance {
348 res.preselect = Some(true);
349 }
350 // The relevance needs to be inverted to come up with a sort score
351 // because the client will sort ascending.
352 let sort_score = relevance.score() ^ 0xFF_FF_FF_FF;
353 // Zero pad the string to ensure values can be properly sorted
354 // by the client. Hex format is used because it is easier to
355 // visually compare very large values, which the sort text
356 // tends to be since it is the opposite of the score.
357 res.sort_text = Some(format!("{sort_score:08x}"));
358 }
359 }
360
361 pub(crate) fn signature_help(
362 call_info: SignatureHelp,
363 config: CallInfoConfig,
364 label_offsets: bool,
365 ) -> lsp_types::SignatureHelp {
366 let (label, parameters) = match (config.params_only, label_offsets) {
367 (concise, false) => {
368 let params = call_info
369 .parameter_labels()
370 .map(|label| lsp_types::ParameterInformation {
371 label: lsp_types::ParameterLabel::Simple(label.to_string()),
372 documentation: None,
373 })
374 .collect::<Vec<_>>();
375 let label =
376 if concise { call_info.parameter_labels().join(", ") } else { call_info.signature };
377 (label, params)
378 }
379 (false, true) => {
380 let params = call_info
381 .parameter_ranges()
382 .iter()
383 .map(|it| {
384 let start = call_info.signature[..it.start().into()].chars().count() as u32;
385 let end = call_info.signature[..it.end().into()].chars().count() as u32;
386 [start, end]
387 })
388 .map(|label_offsets| lsp_types::ParameterInformation {
389 label: lsp_types::ParameterLabel::LabelOffsets(label_offsets),
390 documentation: None,
391 })
392 .collect::<Vec<_>>();
393 (call_info.signature, params)
394 }
395 (true, true) => {
396 let mut params = Vec::new();
397 let mut label = String::new();
398 let mut first = true;
399 for param in call_info.parameter_labels() {
400 if !first {
401 label.push_str(", ");
402 }
403 first = false;
404 let start = label.chars().count() as u32;
405 label.push_str(param);
406 let end = label.chars().count() as u32;
407 params.push(lsp_types::ParameterInformation {
408 label: lsp_types::ParameterLabel::LabelOffsets([start, end]),
409 documentation: None,
410 });
411 }
412
413 (label, params)
414 }
415 };
416
417 let documentation = call_info.doc.filter(|_| config.docs).map(|doc| {
418 lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
419 kind: lsp_types::MarkupKind::Markdown,
420 value: format_docs(&doc),
421 })
422 });
423
424 let active_parameter = call_info.active_parameter.map(|it| it as u32);
425
426 let signature = lsp_types::SignatureInformation {
427 label,
428 documentation,
429 parameters: Some(parameters),
430 active_parameter,
431 };
432 lsp_types::SignatureHelp {
433 signatures: vec![signature],
434 active_signature: Some(0),
435 active_parameter,
436 }
437 }
438
439 pub(crate) fn inlay_hint(
440 snap: &GlobalStateSnapshot,
441 fields_to_resolve: &InlayFieldsToResolve,
442 line_index: &LineIndex,
443 file_id: FileId,
444 inlay_hint: InlayHint,
445 ) -> Cancellable<lsp_types::InlayHint> {
446 let needs_resolve = inlay_hint.needs_resolve;
447 let (label, tooltip, mut something_to_resolve) =
448 inlay_hint_label(snap, fields_to_resolve, needs_resolve, inlay_hint.label)?;
449 let text_edits = if needs_resolve && fields_to_resolve.resolve_text_edits {
450 something_to_resolve |= inlay_hint.text_edit.is_some();
451 None
452 } else {
453 inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it))
454 };
455 let data = if needs_resolve && something_to_resolve {
456 Some(to_value(lsp_ext::InlayHintResolveData { file_id: file_id.0 }).unwrap())
457 } else {
458 None
459 };
460
461 Ok(lsp_types::InlayHint {
462 position: match inlay_hint.position {
463 ide::InlayHintPosition::Before => position(line_index, inlay_hint.range.start()),
464 ide::InlayHintPosition::After => position(line_index, inlay_hint.range.end()),
465 },
466 padding_left: Some(inlay_hint.pad_left),
467 padding_right: Some(inlay_hint.pad_right),
468 kind: match inlay_hint.kind {
469 InlayKind::Parameter => Some(lsp_types::InlayHintKind::PARAMETER),
470 InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE),
471 _ => None,
472 },
473 text_edits,
474 data,
475 tooltip,
476 label,
477 })
478 }
479
480 fn inlay_hint_label(
481 snap: &GlobalStateSnapshot,
482 fields_to_resolve: &InlayFieldsToResolve,
483 needs_resolve: bool,
484 mut label: InlayHintLabel,
485 ) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>, bool)> {
486 let mut something_to_resolve = false;
487 let (label, tooltip) = match &*label.parts {
488 [InlayHintLabelPart { linked_location: None, .. }] => {
489 let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap();
490 let hint_tooltip = if needs_resolve && fields_to_resolve.resolve_hint_tooltip {
491 something_to_resolve |= tooltip.is_some();
492 None
493 } else {
494 match tooltip {
495 Some(ide::InlayTooltip::String(s)) => {
496 Some(lsp_types::InlayHintTooltip::String(s))
497 }
498 Some(ide::InlayTooltip::Markdown(s)) => {
499 Some(lsp_types::InlayHintTooltip::MarkupContent(lsp_types::MarkupContent {
500 kind: lsp_types::MarkupKind::Markdown,
501 value: s,
502 }))
503 }
504 None => None,
505 }
506 };
507 (lsp_types::InlayHintLabel::String(text), hint_tooltip)
508 }
509 _ => {
510 let parts = label
511 .parts
512 .into_iter()
513 .map(|part| {
514 let tooltip = if needs_resolve && fields_to_resolve.resolve_label_tooltip {
515 something_to_resolve |= part.tooltip.is_some();
516 None
517 } else {
518 match part.tooltip {
519 Some(ide::InlayTooltip::String(s)) => {
520 Some(lsp_types::InlayHintLabelPartTooltip::String(s))
521 }
522 Some(ide::InlayTooltip::Markdown(s)) => {
523 Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
524 lsp_types::MarkupContent {
525 kind: lsp_types::MarkupKind::Markdown,
526 value: s,
527 },
528 ))
529 }
530 None => None,
531 }
532 };
533 let location = if needs_resolve && fields_to_resolve.resolve_label_location {
534 something_to_resolve |= part.linked_location.is_some();
535 None
536 } else {
537 part.linked_location.map(|range| location(snap, range)).transpose()?
538 };
539 Ok(lsp_types::InlayHintLabelPart {
540 value: part.text,
541 tooltip,
542 location,
543 command: None,
544 })
545 })
546 .collect::<Cancellable<_>>()?;
547 (lsp_types::InlayHintLabel::LabelParts(parts), None)
548 }
549 };
550 Ok((label, tooltip, something_to_resolve))
551 }
552
553 static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);
554
555 pub(crate) fn semantic_tokens(
556 text: &str,
557 line_index: &LineIndex,
558 highlights: Vec<HlRange>,
559 semantics_tokens_augments_syntax_tokens: bool,
560 non_standard_tokens: bool,
561 ) -> lsp_types::SemanticTokens {
562 let id = TOKEN_RESULT_COUNTER.fetch_add(1, Ordering::SeqCst).to_string();
563 let mut builder = semantic_tokens::SemanticTokensBuilder::new(id);
564
565 for highlight_range in highlights {
566 if highlight_range.highlight.is_empty() {
567 continue;
568 }
569
570 if semantics_tokens_augments_syntax_tokens {
571 match highlight_range.highlight.tag {
572 HlTag::BoolLiteral
573 | HlTag::ByteLiteral
574 | HlTag::CharLiteral
575 | HlTag::Comment
576 | HlTag::Keyword
577 | HlTag::NumericLiteral
578 | HlTag::Operator(_)
579 | HlTag::Punctuation(_)
580 | HlTag::StringLiteral
581 | HlTag::None
582 if highlight_range.highlight.mods.is_empty() =>
583 {
584 continue
585 }
586 _ => (),
587 }
588 }
589
590 let (mut ty, mut mods) = semantic_token_type_and_modifiers(highlight_range.highlight);
591
592 if !non_standard_tokens {
593 ty = match standard_fallback_type(ty) {
594 Some(ty) => ty,
595 None => continue,
596 };
597 mods.standard_fallback();
598 }
599 let token_index = semantic_tokens::type_index(ty);
600 let modifier_bitset = mods.0;
601
602 for mut text_range in line_index.index.lines(highlight_range.range) {
603 if text[text_range].ends_with('\n') {
604 text_range =
605 TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n'));
606 }
607 let range = range(line_index, text_range);
608 builder.push(range, token_index, modifier_bitset);
609 }
610 }
611
612 builder.build()
613 }
614
615 pub(crate) fn semantic_token_delta(
616 previous: &lsp_types::SemanticTokens,
617 current: &lsp_types::SemanticTokens,
618 ) -> lsp_types::SemanticTokensDelta {
619 let result_id = current.result_id.clone();
620 let edits = semantic_tokens::diff_tokens(&previous.data, &current.data);
621 lsp_types::SemanticTokensDelta { result_id, edits }
622 }
623
624 fn semantic_token_type_and_modifiers(
625 highlight: Highlight,
626 ) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet) {
627 let mut mods = semantic_tokens::ModifierSet::default();
628 let type_ = match highlight.tag {
629 HlTag::Symbol(symbol) => match symbol {
630 SymbolKind::Attribute => semantic_tokens::DECORATOR,
631 SymbolKind::Derive => semantic_tokens::DERIVE,
632 SymbolKind::DeriveHelper => semantic_tokens::DERIVE_HELPER,
633 SymbolKind::Module => semantic_tokens::NAMESPACE,
634 SymbolKind::Impl => semantic_tokens::TYPE_ALIAS,
635 SymbolKind::Field => semantic_tokens::PROPERTY,
636 SymbolKind::TypeParam => semantic_tokens::TYPE_PARAMETER,
637 SymbolKind::ConstParam => semantic_tokens::CONST_PARAMETER,
638 SymbolKind::LifetimeParam => semantic_tokens::LIFETIME,
639 SymbolKind::Label => semantic_tokens::LABEL,
640 SymbolKind::ValueParam => semantic_tokens::PARAMETER,
641 SymbolKind::SelfParam => semantic_tokens::SELF_KEYWORD,
642 SymbolKind::SelfType => semantic_tokens::SELF_TYPE_KEYWORD,
643 SymbolKind::Local => semantic_tokens::VARIABLE,
644 SymbolKind::Function => {
645 if highlight.mods.contains(HlMod::Associated) {
646 semantic_tokens::METHOD
647 } else {
648 semantic_tokens::FUNCTION
649 }
650 }
651 SymbolKind::Const => {
652 mods |= semantic_tokens::CONSTANT;
653 mods |= semantic_tokens::STATIC;
654 semantic_tokens::VARIABLE
655 }
656 SymbolKind::Static => {
657 mods |= semantic_tokens::STATIC;
658 semantic_tokens::VARIABLE
659 }
660 SymbolKind::Struct => semantic_tokens::STRUCT,
661 SymbolKind::Enum => semantic_tokens::ENUM,
662 SymbolKind::Variant => semantic_tokens::ENUM_MEMBER,
663 SymbolKind::Union => semantic_tokens::UNION,
664 SymbolKind::TypeAlias => semantic_tokens::TYPE_ALIAS,
665 SymbolKind::Trait => semantic_tokens::INTERFACE,
666 SymbolKind::TraitAlias => semantic_tokens::INTERFACE,
667 SymbolKind::Macro => semantic_tokens::MACRO,
668 SymbolKind::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE,
669 SymbolKind::ToolModule => semantic_tokens::TOOL_MODULE,
670 },
671 HlTag::AttributeBracket => semantic_tokens::ATTRIBUTE_BRACKET,
672 HlTag::BoolLiteral => semantic_tokens::BOOLEAN,
673 HlTag::BuiltinType => semantic_tokens::BUILTIN_TYPE,
674 HlTag::ByteLiteral | HlTag::NumericLiteral => semantic_tokens::NUMBER,
675 HlTag::CharLiteral => semantic_tokens::CHAR,
676 HlTag::Comment => semantic_tokens::COMMENT,
677 HlTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
678 HlTag::InvalidEscapeSequence => semantic_tokens::INVALID_ESCAPE_SEQUENCE,
679 HlTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
680 HlTag::Keyword => semantic_tokens::KEYWORD,
681 HlTag::None => semantic_tokens::GENERIC,
682 HlTag::Operator(op) => match op {
683 HlOperator::Bitwise => semantic_tokens::BITWISE,
684 HlOperator::Arithmetic => semantic_tokens::ARITHMETIC,
685 HlOperator::Logical => semantic_tokens::LOGICAL,
686 HlOperator::Comparison => semantic_tokens::COMPARISON,
687 HlOperator::Other => semantic_tokens::OPERATOR,
688 },
689 HlTag::StringLiteral => semantic_tokens::STRING,
690 HlTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE,
691 HlTag::Punctuation(punct) => match punct {
692 HlPunct::Bracket => semantic_tokens::BRACKET,
693 HlPunct::Brace => semantic_tokens::BRACE,
694 HlPunct::Parenthesis => semantic_tokens::PARENTHESIS,
695 HlPunct::Angle => semantic_tokens::ANGLE,
696 HlPunct::Comma => semantic_tokens::COMMA,
697 HlPunct::Dot => semantic_tokens::DOT,
698 HlPunct::Colon => semantic_tokens::COLON,
699 HlPunct::Semi => semantic_tokens::SEMICOLON,
700 HlPunct::Other => semantic_tokens::PUNCTUATION,
701 HlPunct::MacroBang => semantic_tokens::MACRO_BANG,
702 },
703 };
704
705 for modifier in highlight.mods.iter() {
706 let modifier = match modifier {
707 HlMod::Associated => continue,
708 HlMod::Async => semantic_tokens::ASYNC,
709 HlMod::Attribute => semantic_tokens::ATTRIBUTE_MODIFIER,
710 HlMod::Callable => semantic_tokens::CALLABLE,
711 HlMod::Consuming => semantic_tokens::CONSUMING,
712 HlMod::ControlFlow => semantic_tokens::CONTROL_FLOW,
713 HlMod::CrateRoot => semantic_tokens::CRATE_ROOT,
714 HlMod::DefaultLibrary => semantic_tokens::DEFAULT_LIBRARY,
715 HlMod::Definition => semantic_tokens::DECLARATION,
716 HlMod::Documentation => semantic_tokens::DOCUMENTATION,
717 HlMod::Injected => semantic_tokens::INJECTED,
718 HlMod::IntraDocLink => semantic_tokens::INTRA_DOC_LINK,
719 HlMod::Library => semantic_tokens::LIBRARY,
720 HlMod::Macro => semantic_tokens::MACRO_MODIFIER,
721 HlMod::Mutable => semantic_tokens::MUTABLE,
722 HlMod::Public => semantic_tokens::PUBLIC,
723 HlMod::Reference => semantic_tokens::REFERENCE,
724 HlMod::Static => semantic_tokens::STATIC,
725 HlMod::Trait => semantic_tokens::TRAIT_MODIFIER,
726 HlMod::Unsafe => semantic_tokens::UNSAFE,
727 };
728 mods |= modifier;
729 }
730
731 (type_, mods)
732 }
733
734 pub(crate) fn folding_range(
735 text: &str,
736 line_index: &LineIndex,
737 line_folding_only: bool,
738 fold: Fold,
739 ) -> lsp_types::FoldingRange {
740 let kind = match fold.kind {
741 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
742 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
743 FoldKind::Region => Some(lsp_types::FoldingRangeKind::Region),
744 FoldKind::Mods
745 | FoldKind::Block
746 | FoldKind::ArgList
747 | FoldKind::Consts
748 | FoldKind::Statics
749 | FoldKind::WhereClause
750 | FoldKind::ReturnType
751 | FoldKind::Array
752 | FoldKind::MatchArm => None,
753 };
754
755 let range = range(line_index, fold.range);
756
757 if line_folding_only {
758 // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
759 // even if it contains text not in the folding range. To prevent that we exclude
760 // range.end.line from the folding region if there is more text after range.end
761 // on the same line.
762 let has_more_text_on_end_line = text[TextRange::new(fold.range.end(), TextSize::of(text))]
763 .chars()
764 .take_while(|it| *it != '\n')
765 .any(|it| !it.is_whitespace());
766
767 let end_line = if has_more_text_on_end_line {
768 range.end.line.saturating_sub(1)
769 } else {
770 range.end.line
771 };
772
773 lsp_types::FoldingRange {
774 start_line: range.start.line,
775 start_character: None,
776 end_line,
777 end_character: None,
778 kind,
779 collapsed_text: None,
780 }
781 } else {
782 lsp_types::FoldingRange {
783 start_line: range.start.line,
784 start_character: Some(range.start.character),
785 end_line: range.end.line,
786 end_character: Some(range.end.character),
787 kind,
788 collapsed_text: None,
789 }
790 }
791 }
792
793 pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url {
794 snap.file_id_to_url(file_id)
795 }
796
797 /// Returns a `Url` object from a given path, will lowercase drive letters if present.
798 /// This will only happen when processing windows paths.
799 ///
800 /// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
801 pub(crate) fn url_from_abs_path(path: &AbsPath) -> lsp_types::Url {
802 let url = lsp_types::Url::from_file_path(path).unwrap();
803 match path.as_ref().components().next() {
804 Some(path::Component::Prefix(prefix))
805 if matches!(prefix.kind(), path::Prefix::Disk(_) | path::Prefix::VerbatimDisk(_)) =>
806 {
807 // Need to lowercase driver letter
808 }
809 _ => return url,
810 }
811
812 let driver_letter_range = {
813 let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() {
814 Some(it) => it,
815 None => return url,
816 };
817 let start = scheme.len() + ':'.len_utf8();
818 start..(start + drive_letter.len())
819 };
820
821 // Note: lowercasing the `path` itself doesn't help, the `Url::parse`
822 // machinery *also* canonicalizes the drive letter. So, just massage the
823 // string in place.
824 let mut url: String = url.into();
825 url[driver_letter_range].make_ascii_lowercase();
826 lsp_types::Url::parse(&url).unwrap()
827 }
828
829 pub(crate) fn optional_versioned_text_document_identifier(
830 snap: &GlobalStateSnapshot,
831 file_id: FileId,
832 ) -> lsp_types::OptionalVersionedTextDocumentIdentifier {
833 let url = url(snap, file_id);
834 let version = snap.url_file_version(&url);
835 lsp_types::OptionalVersionedTextDocumentIdentifier { uri: url, version }
836 }
837
838 pub(crate) fn location(
839 snap: &GlobalStateSnapshot,
840 frange: FileRange,
841 ) -> Cancellable<lsp_types::Location> {
842 let url = url(snap, frange.file_id);
843 let line_index = snap.file_line_index(frange.file_id)?;
844 let range = range(&line_index, frange.range);
845 let loc = lsp_types::Location::new(url, range);
846 Ok(loc)
847 }
848
849 /// Prefer using `location_link`, if the client has the cap.
850 pub(crate) fn location_from_nav(
851 snap: &GlobalStateSnapshot,
852 nav: NavigationTarget,
853 ) -> Cancellable<lsp_types::Location> {
854 let url = url(snap, nav.file_id);
855 let line_index = snap.file_line_index(nav.file_id)?;
856 let range = range(&line_index, nav.full_range);
857 let loc = lsp_types::Location::new(url, range);
858 Ok(loc)
859 }
860
861 pub(crate) fn location_link(
862 snap: &GlobalStateSnapshot,
863 src: Option<FileRange>,
864 target: NavigationTarget,
865 ) -> Cancellable<lsp_types::LocationLink> {
866 let origin_selection_range = match src {
867 Some(src) => {
868 let line_index = snap.file_line_index(src.file_id)?;
869 let range = range(&line_index, src.range);
870 Some(range)
871 }
872 None => None,
873 };
874 let (target_uri, target_range, target_selection_range) = location_info(snap, target)?;
875 let res = lsp_types::LocationLink {
876 origin_selection_range,
877 target_uri,
878 target_range,
879 target_selection_range,
880 };
881 Ok(res)
882 }
883
884 fn location_info(
885 snap: &GlobalStateSnapshot,
886 target: NavigationTarget,
887 ) -> Cancellable<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> {
888 let line_index = snap.file_line_index(target.file_id)?;
889
890 let target_uri = url(snap, target.file_id);
891 let target_range = range(&line_index, target.full_range);
892 let target_selection_range =
893 target.focus_range.map(|it| range(&line_index, it)).unwrap_or(target_range);
894 Ok((target_uri, target_range, target_selection_range))
895 }
896
897 pub(crate) fn goto_definition_response(
898 snap: &GlobalStateSnapshot,
899 src: Option<FileRange>,
900 targets: Vec<NavigationTarget>,
901 ) -> Cancellable<lsp_types::GotoDefinitionResponse> {
902 if snap.config.location_link() {
903 let links = targets
904 .into_iter()
905 .map(|nav| location_link(snap, src, nav))
906 .collect::<Cancellable<Vec<_>>>()?;
907 Ok(links.into())
908 } else {
909 let locations = targets
910 .into_iter()
911 .map(|nav| {
912 location(snap, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
913 })
914 .collect::<Cancellable<Vec<_>>>()?;
915 Ok(locations.into())
916 }
917 }
918
919 fn outside_workspace_annotation_id() -> String {
920 String::from("OutsideWorkspace")
921 }
922
923 fn merge_text_and_snippet_edits(
924 line_index: &LineIndex,
925 edit: TextEdit,
926 snippet_edit: SnippetEdit,
927 ) -> Vec<SnippetTextEdit> {
928 let mut edits: Vec<SnippetTextEdit> = vec![];
929 let mut snippets = snippet_edit.into_edit_ranges().into_iter().peekable();
930 let mut text_edits = edit.into_iter();
931
932 while let Some(current_indel) = text_edits.next() {
933 let new_range = {
934 let insert_len =
935 TextSize::try_from(current_indel.insert.len()).unwrap_or(TextSize::from(u32::MAX));
936 TextRange::at(current_indel.delete.start(), insert_len)
937 };
938
939 // insert any snippets before the text edit
940 for (snippet_index, snippet_range) in
941 snippets.take_while_ref(|(_, range)| range.end() < new_range.start())
942 {
943 let snippet_range = if !stdx::always!(
944 snippet_range.is_empty(),
945 "placeholder range {:?} is before current text edit range {:?}",
946 snippet_range,
947 new_range
948 ) {
949 // only possible for tabstops, so make sure it's an empty/insert range
950 TextRange::empty(snippet_range.start())
951 } else {
952 snippet_range
953 };
954
955 let range = range(&line_index, snippet_range);
956 let new_text = format!("${snippet_index}");
957
958 edits.push(SnippetTextEdit {
959 range,
960 new_text,
961 insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
962 annotation_id: None,
963 })
964 }
965
966 if snippets.peek().is_some_and(|(_, range)| new_range.intersect(*range).is_some()) {
967 // at least one snippet edit intersects this text edit,
968 // so gather all of the edits that intersect this text edit
969 let mut all_snippets = snippets
970 .take_while_ref(|(_, range)| new_range.intersect(*range).is_some())
971 .collect_vec();
972
973 // ensure all of the ranges are wholly contained inside of the new range
974 all_snippets.retain(|(_, range)| {
975 stdx::always!(
976 new_range.contains_range(*range),
977 "found placeholder range {:?} which wasn't fully inside of text edit's new range {:?}", range, new_range
978 )
979 });
980
981 let mut text_edit = text_edit(line_index, current_indel);
982
983 // escape out snippet text
984 stdx::replace(&mut text_edit.new_text, '\\', r"\\");
985 stdx::replace(&mut text_edit.new_text, '$', r"\$");
986
987 // ...and apply!
988 for (index, range) in all_snippets.iter().rev() {
989 let start = (range.start() - new_range.start()).into();
990 let end = (range.end() - new_range.start()).into();
991
992 if range.is_empty() {
993 text_edit.new_text.insert_str(start, &format!("${index}"));
994 } else {
995 text_edit.new_text.insert(end, '}');
996 text_edit.new_text.insert_str(start, &format!("${{{index}:"));
997 }
998 }
999
1000 edits.push(SnippetTextEdit {
1001 range: text_edit.range,
1002 new_text: text_edit.new_text,
1003 insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
1004 annotation_id: None,
1005 })
1006 } else {
1007 // snippet edit was beyond the current one
1008 // since it wasn't consumed, it's available for the next pass
1009 edits.push(snippet_text_edit(line_index, false, current_indel));
1010 }
1011 }
1012
1013 // insert any remaining tabstops
1014 edits.extend(snippets.map(|(snippet_index, snippet_range)| {
1015 let snippet_range = if !stdx::always!(
1016 snippet_range.is_empty(),
1017 "found placeholder snippet {:?} without a text edit",
1018 snippet_range
1019 ) {
1020 TextRange::empty(snippet_range.start())
1021 } else {
1022 snippet_range
1023 };
1024
1025 let range = range(&line_index, snippet_range);
1026 let new_text = format!("${snippet_index}");
1027
1028 SnippetTextEdit {
1029 range,
1030 new_text,
1031 insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
1032 annotation_id: None,
1033 }
1034 }));
1035
1036 edits
1037 }
1038
1039 pub(crate) fn snippet_text_document_edit(
1040 snap: &GlobalStateSnapshot,
1041 is_snippet: bool,
1042 file_id: FileId,
1043 edit: TextEdit,
1044 snippet_edit: Option<SnippetEdit>,
1045 ) -> Cancellable<lsp_ext::SnippetTextDocumentEdit> {
1046 let text_document = optional_versioned_text_document_identifier(snap, file_id);
1047 let line_index = snap.file_line_index(file_id)?;
1048 let mut edits = if let Some(snippet_edit) = snippet_edit {
1049 merge_text_and_snippet_edits(&line_index, edit, snippet_edit)
1050 } else {
1051 edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect()
1052 };
1053
1054 if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() {
1055 for edit in &mut edits {
1056 edit.annotation_id = Some(outside_workspace_annotation_id())
1057 }
1058 }
1059 Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
1060 }
1061
1062 pub(crate) fn snippet_text_document_ops(
1063 snap: &GlobalStateSnapshot,
1064 file_system_edit: FileSystemEdit,
1065 ) -> Cancellable<Vec<lsp_ext::SnippetDocumentChangeOperation>> {
1066 let mut ops = Vec::new();
1067 match file_system_edit {
1068 FileSystemEdit::CreateFile { dst, initial_contents } => {
1069 let uri = snap.anchored_path(&dst);
1070 let create_file = lsp_types::ResourceOp::Create(lsp_types::CreateFile {
1071 uri: uri.clone(),
1072 options: None,
1073 annotation_id: None,
1074 });
1075 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(create_file));
1076 if !initial_contents.is_empty() {
1077 let text_document =
1078 lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version: None };
1079 let text_edit = lsp_ext::SnippetTextEdit {
1080 range: lsp_types::Range::default(),
1081 new_text: initial_contents,
1082 insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
1083 annotation_id: None,
1084 };
1085 let edit_file =
1086 lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] };
1087 ops.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit_file));
1088 }
1089 }
1090 FileSystemEdit::MoveFile { src, dst } => {
1091 let old_uri = snap.file_id_to_url(src);
1092 let new_uri = snap.anchored_path(&dst);
1093 let mut rename_file =
1094 lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
1095 if snap.analysis.is_library_file(src).ok() == Some(true)
1096 && snap.config.change_annotation_support()
1097 {
1098 rename_file.annotation_id = Some(outside_workspace_annotation_id())
1099 }
1100 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
1101 rename_file,
1102 )))
1103 }
1104 FileSystemEdit::MoveDir { src, src_id, dst } => {
1105 let old_uri = snap.anchored_path(&src);
1106 let new_uri = snap.anchored_path(&dst);
1107 let mut rename_file =
1108 lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
1109 if snap.analysis.is_library_file(src_id).ok() == Some(true)
1110 && snap.config.change_annotation_support()
1111 {
1112 rename_file.annotation_id = Some(outside_workspace_annotation_id())
1113 }
1114 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
1115 rename_file,
1116 )))
1117 }
1118 }
1119 Ok(ops)
1120 }
1121
1122 pub(crate) fn snippet_workspace_edit(
1123 snap: &GlobalStateSnapshot,
1124 source_change: SourceChange,
1125 ) -> Cancellable<lsp_ext::SnippetWorkspaceEdit> {
1126 let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
1127
1128 for op in source_change.file_system_edits {
1129 let ops = snippet_text_document_ops(snap, op)?;
1130 document_changes.extend_from_slice(&ops);
1131 }
1132 for (file_id, (edit, snippet_edit)) in source_change.source_file_edits {
1133 let edit = snippet_text_document_edit(
1134 snap,
1135 source_change.is_snippet,
1136 file_id,
1137 edit,
1138 snippet_edit,
1139 )?;
1140 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
1141 }
1142 let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit {
1143 changes: None,
1144 document_changes: Some(document_changes),
1145 change_annotations: None,
1146 };
1147 if snap.config.change_annotation_support() {
1148 workspace_edit.change_annotations = Some(
1149 once((
1150 outside_workspace_annotation_id(),
1151 lsp_types::ChangeAnnotation {
1152 label: String::from("Edit outside of the workspace"),
1153 needs_confirmation: Some(true),
1154 description: Some(String::from(
1155 "This edit lies outside of the workspace and may affect dependencies",
1156 )),
1157 },
1158 ))
1159 .collect(),
1160 )
1161 }
1162 Ok(workspace_edit)
1163 }
1164
1165 pub(crate) fn workspace_edit(
1166 snap: &GlobalStateSnapshot,
1167 source_change: SourceChange,
1168 ) -> Cancellable<lsp_types::WorkspaceEdit> {
1169 assert!(!source_change.is_snippet);
1170 snippet_workspace_edit(snap, source_change).map(|it| it.into())
1171 }
1172
1173 impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
1174 fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::WorkspaceEdit {
1175 lsp_types::WorkspaceEdit {
1176 changes: None,
1177 document_changes: snippet_workspace_edit.document_changes.map(|changes| {
1178 lsp_types::DocumentChanges::Operations(
1179 changes
1180 .into_iter()
1181 .map(|change| match change {
1182 lsp_ext::SnippetDocumentChangeOperation::Op(op) => {
1183 lsp_types::DocumentChangeOperation::Op(op)
1184 }
1185 lsp_ext::SnippetDocumentChangeOperation::Edit(edit) => {
1186 lsp_types::DocumentChangeOperation::Edit(
1187 lsp_types::TextDocumentEdit {
1188 text_document: edit.text_document,
1189 edits: edit.edits.into_iter().map(From::from).collect(),
1190 },
1191 )
1192 }
1193 })
1194 .collect(),
1195 )
1196 }),
1197 change_annotations: snippet_workspace_edit.change_annotations,
1198 }
1199 }
1200 }
1201
1202 impl From<lsp_ext::SnippetTextEdit>
1203 for lsp_types::OneOf<lsp_types::TextEdit, lsp_types::AnnotatedTextEdit>
1204 {
1205 fn from(
1206 lsp_ext::SnippetTextEdit { annotation_id, insert_text_format:_, new_text, range }: lsp_ext::SnippetTextEdit,
1207 ) -> Self {
1208 match annotation_id {
1209 Some(annotation_id) => lsp_types::OneOf::Right(lsp_types::AnnotatedTextEdit {
1210 text_edit: lsp_types::TextEdit { range, new_text },
1211 annotation_id,
1212 }),
1213 None => lsp_types::OneOf::Left(lsp_types::TextEdit { range, new_text }),
1214 }
1215 }
1216 }
1217
1218 pub(crate) fn call_hierarchy_item(
1219 snap: &GlobalStateSnapshot,
1220 target: NavigationTarget,
1221 ) -> Cancellable<lsp_types::CallHierarchyItem> {
1222 let name = target.name.to_string();
1223 let detail = target.description.clone();
1224 let kind = target.kind.map(symbol_kind).unwrap_or(lsp_types::SymbolKind::FUNCTION);
1225 let (uri, range, selection_range) = location_info(snap, target)?;
1226 Ok(lsp_types::CallHierarchyItem {
1227 name,
1228 kind,
1229 tags: None,
1230 detail,
1231 uri,
1232 range,
1233 selection_range,
1234 data: None,
1235 })
1236 }
1237
1238 pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
1239 match kind {
1240 AssistKind::None | AssistKind::Generate => lsp_types::CodeActionKind::EMPTY,
1241 AssistKind::QuickFix => lsp_types::CodeActionKind::QUICKFIX,
1242 AssistKind::Refactor => lsp_types::CodeActionKind::REFACTOR,
1243 AssistKind::RefactorExtract => lsp_types::CodeActionKind::REFACTOR_EXTRACT,
1244 AssistKind::RefactorInline => lsp_types::CodeActionKind::REFACTOR_INLINE,
1245 AssistKind::RefactorRewrite => lsp_types::CodeActionKind::REFACTOR_REWRITE,
1246 }
1247 }
1248
1249 pub(crate) fn code_action(
1250 snap: &GlobalStateSnapshot,
1251 assist: Assist,
1252 resolve_data: Option<(usize, lsp_types::CodeActionParams)>,
1253 ) -> Cancellable<lsp_ext::CodeAction> {
1254 let mut res = lsp_ext::CodeAction {
1255 title: assist.label.to_string(),
1256 group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
1257 kind: Some(code_action_kind(assist.id.1)),
1258 edit: None,
1259 is_preferred: None,
1260 data: None,
1261 command: None,
1262 };
1263
1264 if assist.trigger_signature_help && snap.config.client_commands().trigger_parameter_hints {
1265 res.command = Some(command::trigger_parameter_hints());
1266 }
1267
1268 match (assist.source_change, resolve_data) {
1269 (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?),
1270 (None, Some((index, code_action_params))) => {
1271 res.data = Some(lsp_ext::CodeActionData {
1272 id: format!("{}:{}:{index}", assist.id.0, assist.id.1.name()),
1273 code_action_params,
1274 });
1275 }
1276 (None, None) => {
1277 stdx::never!("assist should always be resolved if client can't do lazy resolving")
1278 }
1279 };
1280 Ok(res)
1281 }
1282
1283 pub(crate) fn runnable(
1284 snap: &GlobalStateSnapshot,
1285 runnable: Runnable,
1286 ) -> Cancellable<lsp_ext::Runnable> {
1287 let config = snap.config.runnables();
1288 let spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id)?;
1289 let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
1290 let target = spec.as_ref().map(|s| s.target.clone());
1291 let (cargo_args, executable_args) =
1292 CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg);
1293 let label = runnable.label(target);
1294 let location = location_link(snap, None, runnable.nav)?;
1295
1296 Ok(lsp_ext::Runnable {
1297 label,
1298 location: Some(location),
1299 kind: lsp_ext::RunnableKind::Cargo,
1300 args: lsp_ext::CargoRunnable {
1301 workspace_root: workspace_root.map(|it| it.into()),
1302 override_cargo: config.override_cargo,
1303 cargo_args,
1304 cargo_extra_args: config.cargo_extra_args,
1305 executable_args,
1306 expect_test: None,
1307 },
1308 })
1309 }
1310
1311 pub(crate) fn code_lens(
1312 acc: &mut Vec<lsp_types::CodeLens>,
1313 snap: &GlobalStateSnapshot,
1314 annotation: Annotation,
1315 ) -> Cancellable<()> {
1316 let client_commands_config = snap.config.client_commands();
1317 match annotation.kind {
1318 AnnotationKind::Runnable(run) => {
1319 let line_index = snap.file_line_index(run.nav.file_id)?;
1320 let annotation_range = range(&line_index, annotation.range);
1321
1322 let title = run.title();
1323 let can_debug = match run.kind {
1324 ide::RunnableKind::DocTest { .. } => false,
1325 ide::RunnableKind::TestMod { .. }
1326 | ide::RunnableKind::Test { .. }
1327 | ide::RunnableKind::Bench { .. }
1328 | ide::RunnableKind::Bin => true,
1329 };
1330 let r = runnable(snap, run)?;
1331
1332 let lens_config = snap.config.lens();
1333 if lens_config.run
1334 && client_commands_config.run_single
1335 && r.args.workspace_root.is_some()
1336 {
1337 let command = command::run_single(&r, &title);
1338 acc.push(lsp_types::CodeLens {
1339 range: annotation_range,
1340 command: Some(command),
1341 data: None,
1342 })
1343 }
1344 if lens_config.debug && can_debug && client_commands_config.debug_single {
1345 let command = command::debug_single(&r);
1346 acc.push(lsp_types::CodeLens {
1347 range: annotation_range,
1348 command: Some(command),
1349 data: None,
1350 })
1351 }
1352 if lens_config.interpret {
1353 let command = command::interpret_single(&r);
1354 acc.push(lsp_types::CodeLens {
1355 range: annotation_range,
1356 command: Some(command),
1357 data: None,
1358 })
1359 }
1360 }
1361 AnnotationKind::HasImpls { pos, data } => {
1362 if !client_commands_config.show_reference {
1363 return Ok(());
1364 }
1365 let line_index = snap.file_line_index(pos.file_id)?;
1366 let annotation_range = range(&line_index, annotation.range);
1367 let url = url(snap, pos.file_id);
1368 let pos = position(&line_index, pos.offset);
1369
1370 let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
1371
1372 let doc_pos = lsp_types::TextDocumentPositionParams::new(id, pos);
1373
1374 let goto_params = lsp_types::request::GotoImplementationParams {
1375 text_document_position_params: doc_pos,
1376 work_done_progress_params: Default::default(),
1377 partial_result_params: Default::default(),
1378 };
1379
1380 let command = data.map(|ranges| {
1381 let locations: Vec<lsp_types::Location> = ranges
1382 .into_iter()
1383 .filter_map(|target| {
1384 location(
1385 snap,
1386 FileRange { file_id: target.file_id, range: target.full_range },
1387 )
1388 .ok()
1389 })
1390 .collect();
1391
1392 command::show_references(
1393 implementation_title(locations.len()),
1394 &url,
1395 pos,
1396 locations,
1397 )
1398 });
1399
1400 acc.push(lsp_types::CodeLens {
1401 range: annotation_range,
1402 command,
1403 data: (|| {
1404 let version = snap.url_file_version(&url)?;
1405 Some(
1406 to_value(lsp_ext::CodeLensResolveData {
1407 version,
1408 kind: lsp_ext::CodeLensResolveDataKind::Impls(goto_params),
1409 })
1410 .unwrap(),
1411 )
1412 })(),
1413 })
1414 }
1415 AnnotationKind::HasReferences { pos, data } => {
1416 if !client_commands_config.show_reference {
1417 return Ok(());
1418 }
1419 let line_index = snap.file_line_index(pos.file_id)?;
1420 let annotation_range = range(&line_index, annotation.range);
1421 let url = url(snap, pos.file_id);
1422 let pos = position(&line_index, pos.offset);
1423
1424 let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
1425
1426 let doc_pos = lsp_types::TextDocumentPositionParams::new(id, pos);
1427
1428 let command = data.map(|ranges| {
1429 let locations: Vec<lsp_types::Location> =
1430 ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect();
1431
1432 command::show_references(reference_title(locations.len()), &url, pos, locations)
1433 });
1434
1435 acc.push(lsp_types::CodeLens {
1436 range: annotation_range,
1437 command,
1438 data: (|| {
1439 let version = snap.url_file_version(&url)?;
1440 Some(
1441 to_value(lsp_ext::CodeLensResolveData {
1442 version,
1443 kind: lsp_ext::CodeLensResolveDataKind::References(doc_pos),
1444 })
1445 .unwrap(),
1446 )
1447 })(),
1448 })
1449 }
1450 }
1451 Ok(())
1452 }
1453
1454 pub(crate) mod command {
1455 use ide::{FileRange, NavigationTarget};
1456 use serde_json::to_value;
1457
1458 use crate::{
1459 global_state::GlobalStateSnapshot,
1460 lsp::to_proto::{location, location_link},
1461 lsp_ext,
1462 };
1463
1464 pub(crate) fn show_references(
1465 title: String,
1466 uri: &lsp_types::Url,
1467 position: lsp_types::Position,
1468 locations: Vec<lsp_types::Location>,
1469 ) -> lsp_types::Command {
1470 // We cannot use the 'editor.action.showReferences' command directly
1471 // because that command requires vscode types which we convert in the handler
1472 // on the client side.
1473
1474 lsp_types::Command {
1475 title,
1476 command: "rust-analyzer.showReferences".into(),
1477 arguments: Some(vec![
1478 to_value(uri).unwrap(),
1479 to_value(position).unwrap(),
1480 to_value(locations).unwrap(),
1481 ]),
1482 }
1483 }
1484
1485 pub(crate) fn run_single(runnable: &lsp_ext::Runnable, title: &str) -> lsp_types::Command {
1486 lsp_types::Command {
1487 title: title.to_string(),
1488 command: "rust-analyzer.runSingle".into(),
1489 arguments: Some(vec![to_value(runnable).unwrap()]),
1490 }
1491 }
1492
1493 pub(crate) fn debug_single(runnable: &lsp_ext::Runnable) -> lsp_types::Command {
1494 lsp_types::Command {
1495 title: "Debug".into(),
1496 command: "rust-analyzer.debugSingle".into(),
1497 arguments: Some(vec![to_value(runnable).unwrap()]),
1498 }
1499 }
1500
1501 pub(crate) fn interpret_single(_runnable: &lsp_ext::Runnable) -> lsp_types::Command {
1502 lsp_types::Command {
1503 title: "Interpret".into(),
1504 command: "rust-analyzer.interpretFunction".into(),
1505 // FIXME: use the `_runnable` here.
1506 arguments: Some(vec![]),
1507 }
1508 }
1509
1510 pub(crate) fn goto_location(
1511 snap: &GlobalStateSnapshot,
1512 nav: &NavigationTarget,
1513 ) -> Option<lsp_types::Command> {
1514 let value = if snap.config.location_link() {
1515 let link = location_link(snap, None, nav.clone()).ok()?;
1516 to_value(link).ok()?
1517 } else {
1518 let range = FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() };
1519 let location = location(snap, range).ok()?;
1520 to_value(location).ok()?
1521 };
1522
1523 Some(lsp_types::Command {
1524 title: nav.name.to_string(),
1525 command: "rust-analyzer.gotoLocation".into(),
1526 arguments: Some(vec![value]),
1527 })
1528 }
1529
1530 pub(crate) fn trigger_parameter_hints() -> lsp_types::Command {
1531 lsp_types::Command {
1532 title: "triggerParameterHints".into(),
1533 command: "rust-analyzer.triggerParameterHints".into(),
1534 arguments: None,
1535 }
1536 }
1537 }
1538
1539 pub(crate) fn implementation_title(count: usize) -> String {
1540 if count == 1 {
1541 "1 implementation".into()
1542 } else {
1543 format!("{count} implementations")
1544 }
1545 }
1546
1547 pub(crate) fn reference_title(count: usize) -> String {
1548 if count == 1 {
1549 "1 reference".into()
1550 } else {
1551 format!("{count} references")
1552 }
1553 }
1554
1555 pub(crate) fn markup_content(
1556 markup: Markup,
1557 kind: ide::HoverDocFormat,
1558 ) -> lsp_types::MarkupContent {
1559 let kind = match kind {
1560 ide::HoverDocFormat::Markdown => lsp_types::MarkupKind::Markdown,
1561 ide::HoverDocFormat::PlainText => lsp_types::MarkupKind::PlainText,
1562 };
1563 let value = format_docs(&Documentation::new(markup.into()));
1564 lsp_types::MarkupContent { kind, value }
1565 }
1566
1567 pub(crate) fn rename_error(err: RenameError) -> LspError {
1568 // This is wrong, but we don't have a better alternative I suppose?
1569 // https://github.com/microsoft/language-server-protocol/issues/1341
1570 invalid_params_error(err.to_string())
1571 }
1572
1573 #[cfg(test)]
1574 mod tests {
1575 use expect_test::{expect, Expect};
1576 use ide::{Analysis, FilePosition};
1577 use ide_db::source_change::Snippet;
1578 use test_utils::extract_offset;
1579 use triomphe::Arc;
1580
1581 use super::*;
1582
1583 #[test]
1584 fn conv_fold_line_folding_only_fixup() {
1585 let text = r#"mod a;
1586 mod b;
1587 mod c;
1588
1589 fn main() {
1590 if cond {
1591 a::do_a();
1592 } else {
1593 b::do_b();
1594 }
1595 }"#;
1596
1597 let (analysis, file_id) = Analysis::from_single_file(text.to_string());
1598 let folds = analysis.folding_ranges(file_id).unwrap();
1599 assert_eq!(folds.len(), 4);
1600
1601 let line_index = LineIndex {
1602 index: Arc::new(ide::LineIndex::new(text)),
1603 endings: LineEndings::Unix,
1604 encoding: PositionEncoding::Utf8,
1605 };
1606 let converted: Vec<lsp_types::FoldingRange> =
1607 folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect();
1608
1609 let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
1610 assert_eq!(converted.len(), expected_lines.len());
1611 for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
1612 assert_eq!(folding_range.start_line, *start_line);
1613 assert_eq!(folding_range.start_character, None);
1614 assert_eq!(folding_range.end_line, *end_line);
1615 assert_eq!(folding_range.end_character, None);
1616 }
1617 }
1618
1619 #[test]
1620 fn calling_function_with_ignored_code_in_signature() {
1621 let text = r#"
1622 fn foo() {
1623 bar($0);
1624 }
1625 /// ```
1626 /// # use crate::bar;
1627 /// bar(5);
1628 /// ```
1629 fn bar(_: usize) {}
1630 "#;
1631
1632 let (offset, text) = extract_offset(text);
1633 let (analysis, file_id) = Analysis::from_single_file(text);
1634 let help = signature_help(
1635 analysis.signature_help(FilePosition { file_id, offset }).unwrap().unwrap(),
1636 CallInfoConfig { params_only: false, docs: true },
1637 false,
1638 );
1639 let docs = match &help.signatures[help.active_signature.unwrap() as usize].documentation {
1640 Some(lsp_types::Documentation::MarkupContent(content)) => &content.value,
1641 _ => panic!("documentation contains markup"),
1642 };
1643 assert!(docs.contains("bar(5)"));
1644 assert!(!docs.contains("use crate::bar"));
1645 }
1646
1647 fn check_rendered_snippets(edit: TextEdit, snippets: SnippetEdit, expect: Expect) {
1648 let text = r#"/* place to put all ranges in */"#;
1649 let line_index = LineIndex {
1650 index: Arc::new(ide::LineIndex::new(text)),
1651 endings: LineEndings::Unix,
1652 encoding: PositionEncoding::Utf8,
1653 };
1654
1655 let res = merge_text_and_snippet_edits(&line_index, edit, snippets);
1656 expect.assert_debug_eq(&res);
1657 }
1658
1659 #[test]
1660 fn snippet_rendering_only_tabstops() {
1661 let edit = TextEdit::builder().finish();
1662 let snippets = SnippetEdit::new(vec![
1663 Snippet::Tabstop(0.into()),
1664 Snippet::Tabstop(0.into()),
1665 Snippet::Tabstop(1.into()),
1666 Snippet::Tabstop(1.into()),
1667 ]);
1668
1669 check_rendered_snippets(
1670 edit,
1671 snippets,
1672 expect![[r#"
1673 [
1674 SnippetTextEdit {
1675 range: Range {
1676 start: Position {
1677 line: 0,
1678 character: 0,
1679 },
1680 end: Position {
1681 line: 0,
1682 character: 0,
1683 },
1684 },
1685 new_text: "$1",
1686 insert_text_format: Some(
1687 Snippet,
1688 ),
1689 annotation_id: None,
1690 },
1691 SnippetTextEdit {
1692 range: Range {
1693 start: Position {
1694 line: 0,
1695 character: 0,
1696 },
1697 end: Position {
1698 line: 0,
1699 character: 0,
1700 },
1701 },
1702 new_text: "$2",
1703 insert_text_format: Some(
1704 Snippet,
1705 ),
1706 annotation_id: None,
1707 },
1708 SnippetTextEdit {
1709 range: Range {
1710 start: Position {
1711 line: 0,
1712 character: 1,
1713 },
1714 end: Position {
1715 line: 0,
1716 character: 1,
1717 },
1718 },
1719 new_text: "$3",
1720 insert_text_format: Some(
1721 Snippet,
1722 ),
1723 annotation_id: None,
1724 },
1725 SnippetTextEdit {
1726 range: Range {
1727 start: Position {
1728 line: 0,
1729 character: 1,
1730 },
1731 end: Position {
1732 line: 0,
1733 character: 1,
1734 },
1735 },
1736 new_text: "$0",
1737 insert_text_format: Some(
1738 Snippet,
1739 ),
1740 annotation_id: None,
1741 },
1742 ]
1743 "#]],
1744 );
1745 }
1746
1747 #[test]
1748 fn snippet_rendering_only_text_edits() {
1749 let mut edit = TextEdit::builder();
1750 edit.insert(0.into(), "abc".to_owned());
1751 edit.insert(3.into(), "def".to_owned());
1752 let edit = edit.finish();
1753 let snippets = SnippetEdit::new(vec![]);
1754
1755 check_rendered_snippets(
1756 edit,
1757 snippets,
1758 expect![[r#"
1759 [
1760 SnippetTextEdit {
1761 range: Range {
1762 start: Position {
1763 line: 0,
1764 character: 0,
1765 },
1766 end: Position {
1767 line: 0,
1768 character: 0,
1769 },
1770 },
1771 new_text: "abc",
1772 insert_text_format: None,
1773 annotation_id: None,
1774 },
1775 SnippetTextEdit {
1776 range: Range {
1777 start: Position {
1778 line: 0,
1779 character: 3,
1780 },
1781 end: Position {
1782 line: 0,
1783 character: 3,
1784 },
1785 },
1786 new_text: "def",
1787 insert_text_format: None,
1788 annotation_id: None,
1789 },
1790 ]
1791 "#]],
1792 );
1793 }
1794
1795 #[test]
1796 fn snippet_rendering_tabstop_after_text_edit() {
1797 let mut edit = TextEdit::builder();
1798 edit.insert(0.into(), "abc".to_owned());
1799 let edit = edit.finish();
1800 let snippets = SnippetEdit::new(vec![Snippet::Tabstop(7.into())]);
1801
1802 check_rendered_snippets(
1803 edit,
1804 snippets,
1805 expect![[r#"
1806 [
1807 SnippetTextEdit {
1808 range: Range {
1809 start: Position {
1810 line: 0,
1811 character: 0,
1812 },
1813 end: Position {
1814 line: 0,
1815 character: 0,
1816 },
1817 },
1818 new_text: "abc",
1819 insert_text_format: None,
1820 annotation_id: None,
1821 },
1822 SnippetTextEdit {
1823 range: Range {
1824 start: Position {
1825 line: 0,
1826 character: 7,
1827 },
1828 end: Position {
1829 line: 0,
1830 character: 7,
1831 },
1832 },
1833 new_text: "$0",
1834 insert_text_format: Some(
1835 Snippet,
1836 ),
1837 annotation_id: None,
1838 },
1839 ]
1840 "#]],
1841 );
1842 }
1843
1844 #[test]
1845 fn snippet_rendering_tabstops_before_text_edit() {
1846 let mut edit = TextEdit::builder();
1847 edit.insert(2.into(), "abc".to_owned());
1848 let edit = edit.finish();
1849 let snippets =
1850 SnippetEdit::new(vec![Snippet::Tabstop(0.into()), Snippet::Tabstop(0.into())]);
1851
1852 check_rendered_snippets(
1853 edit,
1854 snippets,
1855 expect![[r#"
1856 [
1857 SnippetTextEdit {
1858 range: Range {
1859 start: Position {
1860 line: 0,
1861 character: 0,
1862 },
1863 end: Position {
1864 line: 0,
1865 character: 0,
1866 },
1867 },
1868 new_text: "$1",
1869 insert_text_format: Some(
1870 Snippet,
1871 ),
1872 annotation_id: None,
1873 },
1874 SnippetTextEdit {
1875 range: Range {
1876 start: Position {
1877 line: 0,
1878 character: 0,
1879 },
1880 end: Position {
1881 line: 0,
1882 character: 0,
1883 },
1884 },
1885 new_text: "$0",
1886 insert_text_format: Some(
1887 Snippet,
1888 ),
1889 annotation_id: None,
1890 },
1891 SnippetTextEdit {
1892 range: Range {
1893 start: Position {
1894 line: 0,
1895 character: 2,
1896 },
1897 end: Position {
1898 line: 0,
1899 character: 2,
1900 },
1901 },
1902 new_text: "abc",
1903 insert_text_format: None,
1904 annotation_id: None,
1905 },
1906 ]
1907 "#]],
1908 );
1909 }
1910
1911 #[test]
1912 fn snippet_rendering_tabstops_between_text_edits() {
1913 let mut edit = TextEdit::builder();
1914 edit.insert(0.into(), "abc".to_owned());
1915 edit.insert(7.into(), "abc".to_owned());
1916 let edit = edit.finish();
1917 let snippets =
1918 SnippetEdit::new(vec![Snippet::Tabstop(4.into()), Snippet::Tabstop(4.into())]);
1919
1920 check_rendered_snippets(
1921 edit,
1922 snippets,
1923 expect![[r#"
1924 [
1925 SnippetTextEdit {
1926 range: Range {
1927 start: Position {
1928 line: 0,
1929 character: 0,
1930 },
1931 end: Position {
1932 line: 0,
1933 character: 0,
1934 },
1935 },
1936 new_text: "abc",
1937 insert_text_format: None,
1938 annotation_id: None,
1939 },
1940 SnippetTextEdit {
1941 range: Range {
1942 start: Position {
1943 line: 0,
1944 character: 4,
1945 },
1946 end: Position {
1947 line: 0,
1948 character: 4,
1949 },
1950 },
1951 new_text: "$1",
1952 insert_text_format: Some(
1953 Snippet,
1954 ),
1955 annotation_id: None,
1956 },
1957 SnippetTextEdit {
1958 range: Range {
1959 start: Position {
1960 line: 0,
1961 character: 4,
1962 },
1963 end: Position {
1964 line: 0,
1965 character: 4,
1966 },
1967 },
1968 new_text: "$0",
1969 insert_text_format: Some(
1970 Snippet,
1971 ),
1972 annotation_id: None,
1973 },
1974 SnippetTextEdit {
1975 range: Range {
1976 start: Position {
1977 line: 0,
1978 character: 7,
1979 },
1980 end: Position {
1981 line: 0,
1982 character: 7,
1983 },
1984 },
1985 new_text: "abc",
1986 insert_text_format: None,
1987 annotation_id: None,
1988 },
1989 ]
1990 "#]],
1991 );
1992 }
1993
1994 #[test]
1995 fn snippet_rendering_multiple_tabstops_in_text_edit() {
1996 let mut edit = TextEdit::builder();
1997 edit.insert(0.into(), "abcdefghijkl".to_owned());
1998 let edit = edit.finish();
1999 let snippets = SnippetEdit::new(vec![
2000 Snippet::Tabstop(0.into()),
2001 Snippet::Tabstop(5.into()),
2002 Snippet::Tabstop(12.into()),
2003 ]);
2004
2005 check_rendered_snippets(
2006 edit,
2007 snippets,
2008 expect![[r#"
2009 [
2010 SnippetTextEdit {
2011 range: Range {
2012 start: Position {
2013 line: 0,
2014 character: 0,
2015 },
2016 end: Position {
2017 line: 0,
2018 character: 0,
2019 },
2020 },
2021 new_text: "$1abcde$2fghijkl$0",
2022 insert_text_format: Some(
2023 Snippet,
2024 ),
2025 annotation_id: None,
2026 },
2027 ]
2028 "#]],
2029 );
2030 }
2031
2032 #[test]
2033 fn snippet_rendering_multiple_placeholders_in_text_edit() {
2034 let mut edit = TextEdit::builder();
2035 edit.insert(0.into(), "abcdefghijkl".to_owned());
2036 let edit = edit.finish();
2037 let snippets = SnippetEdit::new(vec![
2038 Snippet::Placeholder(TextRange::new(0.into(), 3.into())),
2039 Snippet::Placeholder(TextRange::new(5.into(), 7.into())),
2040 Snippet::Placeholder(TextRange::new(10.into(), 12.into())),
2041 ]);
2042
2043 check_rendered_snippets(
2044 edit,
2045 snippets,
2046 expect![[r#"
2047 [
2048 SnippetTextEdit {
2049 range: Range {
2050 start: Position {
2051 line: 0,
2052 character: 0,
2053 },
2054 end: Position {
2055 line: 0,
2056 character: 0,
2057 },
2058 },
2059 new_text: "${1:abc}de${2:fg}hij${0:kl}",
2060 insert_text_format: Some(
2061 Snippet,
2062 ),
2063 annotation_id: None,
2064 },
2065 ]
2066 "#]],
2067 );
2068 }
2069
2070 #[test]
2071 fn snippet_rendering_escape_snippet_bits() {
2072 // only needed for snippet formats
2073 let mut edit = TextEdit::builder();
2074 edit.insert(0.into(), r"abc\def$".to_owned());
2075 edit.insert(8.into(), r"ghi\jkl$".to_owned());
2076 let edit = edit.finish();
2077 let snippets =
2078 SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(0.into(), 3.into()))]);
2079
2080 check_rendered_snippets(
2081 edit,
2082 snippets,
2083 expect![[r#"
2084 [
2085 SnippetTextEdit {
2086 range: Range {
2087 start: Position {
2088 line: 0,
2089 character: 0,
2090 },
2091 end: Position {
2092 line: 0,
2093 character: 0,
2094 },
2095 },
2096 new_text: "${0:abc}\\\\def\\$",
2097 insert_text_format: Some(
2098 Snippet,
2099 ),
2100 annotation_id: None,
2101 },
2102 SnippetTextEdit {
2103 range: Range {
2104 start: Position {
2105 line: 0,
2106 character: 8,
2107 },
2108 end: Position {
2109 line: 0,
2110 character: 8,
2111 },
2112 },
2113 new_text: "ghi\\jkl$",
2114 insert_text_format: None,
2115 annotation_id: None,
2116 },
2117 ]
2118 "#]],
2119 );
2120 }
2121
2122 // `Url` is not able to parse windows paths on unix machines.
2123 #[test]
2124 #[cfg(target_os = "windows")]
2125 fn test_lowercase_drive_letter() {
2126 use std::path::Path;
2127
2128 let url = url_from_abs_path(Path::new("C:\\Test").try_into().unwrap());
2129 assert_eq!(url.to_string(), "file:///c:/Test");
2130
2131 let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#).try_into().unwrap());
2132 assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
2133 }
2134 }