1use std::{
3 iter::once,
4 mem,
5 ops::Not as _,
6 sync::atomic::{AtomicU32, Ordering},
7};
8
9use base64::{prelude::BASE64_STANDARD, Engine};
10use ide::{
11 Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionFieldsToResolve,
12 CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange,
13 FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel,
14 InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, LazyProperty,
15 Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp,
16 SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
17 UpdateTest,
18};
19use ide_db::{assists, rust_doc::format_docs, FxHasher};
20use itertools::Itertools;
21use paths::{Utf8Component, Utf8Prefix};
22use semver::VersionReq;
23use serde_json::to_value;
24use vfs::AbsPath;
25
26use crate::{
27 config::{CallInfoConfig, Config},
28 global_state::GlobalStateSnapshot,
29 line_index::{LineEndings, LineIndex, PositionEncoding},
30 lsp::{
31 completion_item_hash,
32 ext::ShellRunnableArgs,
33 semantic_tokens::{self, standard_fallback_type},
34 utils::invalid_params_error,
35 LspError,
36 },
37 lsp_ext::{self, SnippetTextEdit},
38 target_spec::{CargoTargetSpec, TargetSpec},
39};
40
41pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
42 let line_col = line_index.index.line_col(offset);
43 match line_index.encoding {
44 PositionEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col),
45 PositionEncoding::Wide(enc) => {
46 let line_col = line_index.index.to_wide(enc, line_col).unwrap();
47 lsp_types::Position::new(line_col.line, line_col.col)
48 }
49 }
50}
51
52pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range {
53 let start = position(line_index, range.start());
54 let end = position(line_index, range.end());
55 lsp_types::Range::new(start, end)
56}
57
58pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
59 match symbol_kind {
60 SymbolKind::Function => lsp_types::SymbolKind::FUNCTION,
61 SymbolKind::Method => lsp_types::SymbolKind::METHOD,
62 SymbolKind::Struct => lsp_types::SymbolKind::STRUCT,
63 SymbolKind::Enum => lsp_types::SymbolKind::ENUM,
64 SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER,
65 SymbolKind::Trait | SymbolKind::TraitAlias => lsp_types::SymbolKind::INTERFACE,
66 SymbolKind::Macro
67 | SymbolKind::ProcMacro
68 | SymbolKind::BuiltinAttr
69 | SymbolKind::Attribute
70 | SymbolKind::Derive
71 | SymbolKind::DeriveHelper => lsp_types::SymbolKind::FUNCTION,
72 SymbolKind::Module | SymbolKind::ToolModule => lsp_types::SymbolKind::MODULE,
73 SymbolKind::TypeAlias | SymbolKind::TypeParam | SymbolKind::SelfType => {
74 lsp_types::SymbolKind::TYPE_PARAMETER
75 }
76 SymbolKind::Field => lsp_types::SymbolKind::FIELD,
77 SymbolKind::Static => lsp_types::SymbolKind::CONSTANT,
78 SymbolKind::Const => lsp_types::SymbolKind::CONSTANT,
79 SymbolKind::ConstParam => lsp_types::SymbolKind::CONSTANT,
80 SymbolKind::Impl => lsp_types::SymbolKind::OBJECT,
81 SymbolKind::Local
82 | SymbolKind::SelfParam
83 | SymbolKind::LifetimeParam
84 | SymbolKind::ValueParam
85 | SymbolKind::Label => lsp_types::SymbolKind::VARIABLE,
86 SymbolKind::Union => lsp_types::SymbolKind::STRUCT,
87 SymbolKind::InlineAsmRegOrRegClass => lsp_types::SymbolKind::VARIABLE,
88 }
89}
90
91pub(crate) fn structure_node_kind(kind: StructureNodeKind) -> lsp_types::SymbolKind {
92 match kind {
93 StructureNodeKind::SymbolKind(symbol) => symbol_kind(symbol),
94 StructureNodeKind::Region => lsp_types::SymbolKind::NAMESPACE,
95 StructureNodeKind::ExternBlock => lsp_types::SymbolKind::NAMESPACE,
96 }
97}
98
99pub(crate) fn document_highlight_kind(
100 category: ReferenceCategory,
101) -> Option<lsp_types::DocumentHighlightKind> {
102 if category.contains(ReferenceCategory::WRITE) {
103 return Some(lsp_types::DocumentHighlightKind::WRITE);
104 }
105 if category.contains(ReferenceCategory::READ) {
106 return Some(lsp_types::DocumentHighlightKind::READ);
107 }
108 None
109}
110
111pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {
112 match severity {
113 Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
114 Severity::Warning => lsp_types::DiagnosticSeverity::WARNING,
115 Severity::WeakWarning => lsp_types::DiagnosticSeverity::HINT,
116 Severity::Allow => lsp_types::DiagnosticSeverity::INFORMATION,
118 }
119}
120
121pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Documentation {
122 let value = format_docs(&documentation);
123 let markup_content = lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value };
124 lsp_types::Documentation::MarkupContent(markup_content)
125}
126
127pub(crate) fn completion_item_kind(
128 completion_item_kind: CompletionItemKind,
129) -> lsp_types::CompletionItemKind {
130 match completion_item_kind {
131 CompletionItemKind::Binding => lsp_types::CompletionItemKind::VARIABLE,
132 CompletionItemKind::BuiltinType => lsp_types::CompletionItemKind::STRUCT,
133 CompletionItemKind::InferredType => lsp_types::CompletionItemKind::SNIPPET,
134 CompletionItemKind::Keyword => lsp_types::CompletionItemKind::KEYWORD,
135 CompletionItemKind::Snippet => lsp_types::CompletionItemKind::SNIPPET,
136 CompletionItemKind::UnresolvedReference => lsp_types::CompletionItemKind::REFERENCE,
137 CompletionItemKind::Expression => lsp_types::CompletionItemKind::SNIPPET,
138 CompletionItemKind::SymbolKind(symbol) => match symbol {
139 SymbolKind::Attribute => lsp_types::CompletionItemKind::FUNCTION,
140 SymbolKind::Method => lsp_types::CompletionItemKind::METHOD,
141 SymbolKind::Const => lsp_types::CompletionItemKind::CONSTANT,
142 SymbolKind::ConstParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
143 SymbolKind::Derive => lsp_types::CompletionItemKind::FUNCTION,
144 SymbolKind::DeriveHelper => lsp_types::CompletionItemKind::FUNCTION,
145 SymbolKind::Enum => lsp_types::CompletionItemKind::ENUM,
146 SymbolKind::Field => lsp_types::CompletionItemKind::FIELD,
147 SymbolKind::Function => lsp_types::CompletionItemKind::FUNCTION,
148 SymbolKind::Impl => lsp_types::CompletionItemKind::TEXT,
149 SymbolKind::Label => lsp_types::CompletionItemKind::VARIABLE,
150 SymbolKind::LifetimeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
151 SymbolKind::Local => lsp_types::CompletionItemKind::VARIABLE,
152 SymbolKind::Macro => lsp_types::CompletionItemKind::FUNCTION,
153 SymbolKind::ProcMacro => lsp_types::CompletionItemKind::FUNCTION,
154 SymbolKind::Module => lsp_types::CompletionItemKind::MODULE,
155 SymbolKind::SelfParam => lsp_types::CompletionItemKind::VALUE,
156 SymbolKind::SelfType => lsp_types::CompletionItemKind::TYPE_PARAMETER,
157 SymbolKind::Static => lsp_types::CompletionItemKind::VALUE,
158 SymbolKind::Struct => lsp_types::CompletionItemKind::STRUCT,
159 SymbolKind::Trait => lsp_types::CompletionItemKind::INTERFACE,
160 SymbolKind::TraitAlias => lsp_types::CompletionItemKind::INTERFACE,
161 SymbolKind::TypeAlias => lsp_types::CompletionItemKind::STRUCT,
162 SymbolKind::TypeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
163 SymbolKind::Union => lsp_types::CompletionItemKind::STRUCT,
164 SymbolKind::ValueParam => lsp_types::CompletionItemKind::VALUE,
165 SymbolKind::Variant => lsp_types::CompletionItemKind::ENUM_MEMBER,
166 SymbolKind::BuiltinAttr => lsp_types::CompletionItemKind::FUNCTION,
167 SymbolKind::ToolModule => lsp_types::CompletionItemKind::MODULE,
168 SymbolKind::InlineAsmRegOrRegClass => lsp_types::CompletionItemKind::KEYWORD,
169 },
170 }
171}
172
173pub(crate) fn text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::TextEdit {
174 let range = range(line_index, indel.delete);
175 let new_text = match line_index.endings {
176 LineEndings::Unix => indel.insert,
177 LineEndings::Dos => indel.insert.replace('\n', "\r\n"),
178 };
179 lsp_types::TextEdit { range, new_text }
180}
181
182pub(crate) fn completion_text_edit(
183 line_index: &LineIndex,
184 insert_replace_support: Option<lsp_types::Position>,
185 indel: Indel,
186) -> lsp_types::CompletionTextEdit {
187 let text_edit = text_edit(line_index, indel);
188 match insert_replace_support {
189 Some(cursor_pos) => lsp_types::InsertReplaceEdit {
190 new_text: text_edit.new_text,
191 insert: lsp_types::Range { start: text_edit.range.start, end: cursor_pos },
192 replace: text_edit.range,
193 }
194 .into(),
195 None => text_edit.into(),
196 }
197}
198
199pub(crate) fn snippet_text_edit(
200 line_index: &LineIndex,
201 is_snippet: bool,
202 indel: Indel,
203) -> lsp_ext::SnippetTextEdit {
204 let text_edit = text_edit(line_index, indel);
205 let insert_text_format =
206 if is_snippet { Some(lsp_types::InsertTextFormat::SNIPPET) } else { None };
207 lsp_ext::SnippetTextEdit {
208 range: text_edit.range,
209 new_text: text_edit.new_text,
210 insert_text_format,
211 annotation_id: None,
212 }
213}
214
215pub(crate) fn text_edit_vec(
216 line_index: &LineIndex,
217 text_edit: TextEdit,
218) -> Vec<lsp_types::TextEdit> {
219 text_edit.into_iter().map(|indel| self::text_edit(line_index, indel)).collect()
220}
221
222pub(crate) fn snippet_text_edit_vec(
223 line_index: &LineIndex,
224 is_snippet: bool,
225 text_edit: TextEdit,
226) -> Vec<lsp_ext::SnippetTextEdit> {
227 text_edit
228 .into_iter()
229 .map(|indel| self::snippet_text_edit(line_index, is_snippet, indel))
230 .collect()
231}
232
233pub(crate) fn completion_items(
234 config: &Config,
235 fields_to_resolve: &CompletionFieldsToResolve,
236 line_index: &LineIndex,
237 version: Option<i32>,
238 tdpp: lsp_types::TextDocumentPositionParams,
239 completion_trigger_character: Option<char>,
240 mut items: Vec<CompletionItem>,
241) -> Vec<lsp_types::CompletionItem> {
242 if config.completion_hide_deprecated() {
243 items.retain(|item| !item.deprecated);
244 }
245
246 let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default();
247 let mut res = Vec::with_capacity(items.len());
248 for item in items {
249 completion_item(
250 &mut res,
251 config,
252 fields_to_resolve,
253 line_index,
254 version,
255 &tdpp,
256 max_relevance,
257 completion_trigger_character,
258 item,
259 );
260 }
261
262 if let Some(limit) = config.completion(None).limit {
263 res.sort_by(|item1, item2| item1.sort_text.cmp(&item2.sort_text));
264 res.truncate(limit);
265 }
266
267 res
268}
269
270fn completion_item(
271 acc: &mut Vec<lsp_types::CompletionItem>,
272 config: &Config,
273 fields_to_resolve: &CompletionFieldsToResolve,
274 line_index: &LineIndex,
275 version: Option<i32>,
276 tdpp: &lsp_types::TextDocumentPositionParams,
277 max_relevance: u32,
278 completion_trigger_character: Option<char>,
279 item: CompletionItem,
280) {
281 let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
282 let ref_match = item.ref_match();
283
284 let mut additional_text_edits = Vec::new();
285 let mut something_to_resolve = false;
286
287 let filter_text = if fields_to_resolve.resolve_filter_text {
288 something_to_resolve |= !item.lookup().is_empty();
289 None
290 } else {
291 Some(item.lookup().to_owned())
292 };
293
294 let text_edit = if fields_to_resolve.resolve_text_edit {
295 something_to_resolve |= true;
296 None
297 } else {
298 let mut text_edit = None;
301 let source_range = item.source_range;
302 for indel in &item.text_edit {
303 if indel.delete.contains_range(source_range) {
304 text_edit = Some(if indel.delete == source_range {
306 self::completion_text_edit(line_index, insert_replace_support, indel.clone())
307 } else {
308 assert!(source_range.end() == indel.delete.end());
309 let range1 = TextRange::new(indel.delete.start(), source_range.start());
310 let range2 = source_range;
311 let indel1 = Indel::delete(range1);
312 let indel2 = Indel::replace(range2, indel.insert.clone());
313 additional_text_edits.push(self::text_edit(line_index, indel1));
314 self::completion_text_edit(line_index, insert_replace_support, indel2)
315 })
316 } else {
317 assert!(source_range.intersect(indel.delete).is_none());
318 let text_edit = self::text_edit(line_index, indel.clone());
319 additional_text_edits.push(text_edit);
320 }
321 }
322 Some(text_edit.unwrap())
323 };
324
325 let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET);
326 let tags = if fields_to_resolve.resolve_tags {
327 something_to_resolve |= item.deprecated;
328 None
329 } else {
330 item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED])
331 };
332 let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints {
333 if fields_to_resolve.resolve_command {
334 something_to_resolve |= true;
335 None
336 } else {
337 Some(command::trigger_parameter_hints())
338 }
339 } else {
340 None
341 };
342
343 let detail = if fields_to_resolve.resolve_detail {
344 something_to_resolve |= item.detail.is_some();
345 None
346 } else {
347 item.detail.clone()
348 };
349
350 let documentation = if fields_to_resolve.resolve_documentation {
351 something_to_resolve |= item.documentation.is_some();
352 None
353 } else {
354 item.documentation.clone().map(documentation)
355 };
356
357 let mut lsp_item = lsp_types::CompletionItem {
358 label: item.label.primary.to_string(),
359 detail,
360 filter_text,
361 kind: Some(completion_item_kind(item.kind)),
362 text_edit,
363 additional_text_edits: additional_text_edits
364 .is_empty()
365 .not()
366 .then_some(additional_text_edits),
367 documentation,
368 deprecated: item.deprecated.then_some(item.deprecated),
369 tags,
370 command,
371 insert_text_format,
372 ..Default::default()
373 };
374
375 if config.completion_label_details_support() {
376 let has_label_details =
377 item.label.detail_left.is_some() || item.label.detail_right.is_some();
378 if fields_to_resolve.resolve_label_details {
379 something_to_resolve |= has_label_details;
380 } else if has_label_details {
381 lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails {
382 detail: item.label.detail_left.clone(),
383 description: item.label.detail_right.clone(),
384 });
385 }
386 } else if let Some(label_detail) = &item.label.detail_left {
387 lsp_item.label.push_str(label_detail.as_str());
388 }
389
390 set_score(&mut lsp_item, max_relevance, item.relevance);
391
392 let imports =
393 if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() {
394 item.import_to_add
395 .clone()
396 .into_iter()
397 .map(|import_path| lsp_ext::CompletionImport { full_import_path: import_path })
398 .collect()
399 } else {
400 Vec::new()
401 };
402 let (ref_resolve_data, resolve_data) = if something_to_resolve || !imports.is_empty() {
403 let ref_resolve_data = if ref_match.is_some() {
404 let ref_resolve_data = lsp_ext::CompletionResolveData {
405 position: tdpp.clone(),
406 imports: Vec::new(),
407 version,
408 trigger_character: completion_trigger_character,
409 for_ref: true,
410 hash: BASE64_STANDARD.encode(completion_item_hash(&item, true)),
411 };
412 Some(to_value(ref_resolve_data).unwrap())
413 } else {
414 None
415 };
416 let resolve_data = lsp_ext::CompletionResolveData {
417 position: tdpp.clone(),
418 imports,
419 version,
420 trigger_character: completion_trigger_character,
421 for_ref: false,
422 hash: BASE64_STANDARD.encode(completion_item_hash(&item, false)),
423 };
424 (ref_resolve_data, Some(to_value(resolve_data).unwrap()))
425 } else {
426 (None, None)
427 };
428
429 if let Some((label, indel, relevance)) = ref_match {
430 let mut lsp_item_with_ref =
431 lsp_types::CompletionItem { label, data: ref_resolve_data, ..lsp_item.clone() };
432 lsp_item_with_ref
433 .additional_text_edits
434 .get_or_insert_with(Default::default)
435 .push(self::text_edit(line_index, indel));
436 set_score(&mut lsp_item_with_ref, max_relevance, relevance);
437 acc.push(lsp_item_with_ref);
438 };
439
440 lsp_item.data = resolve_data;
441 acc.push(lsp_item);
442
443 fn set_score(
444 res: &mut lsp_types::CompletionItem,
445 max_relevance: u32,
446 relevance: CompletionRelevance,
447 ) {
448 if relevance.is_relevant() && relevance.score() == max_relevance {
449 res.preselect = Some(true);
450 }
451 let sort_score = relevance.score() ^ 0xFF_FF_FF_FF;
454 res.sort_text = Some(format!("{sort_score:08x}"));
459 }
460}
461
462pub(crate) fn signature_help(
463 call_info: SignatureHelp,
464 config: CallInfoConfig,
465 label_offsets: bool,
466) -> lsp_types::SignatureHelp {
467 let (label, parameters) = match (config.params_only, label_offsets) {
468 (concise, false) => {
469 let params = call_info
470 .parameter_labels()
471 .map(|label| lsp_types::ParameterInformation {
472 label: lsp_types::ParameterLabel::Simple(label.to_owned()),
473 documentation: None,
474 })
475 .collect::<Vec<_>>();
476 let label =
477 if concise { call_info.parameter_labels().join(", ") } else { call_info.signature };
478 (label, params)
479 }
480 (false, true) => {
481 let params = call_info
482 .parameter_ranges()
483 .iter()
484 .map(|it| {
485 let start = call_info.signature[..it.start().into()].chars().count() as u32;
486 let end = call_info.signature[..it.end().into()].chars().count() as u32;
487 [start, end]
488 })
489 .map(|label_offsets| lsp_types::ParameterInformation {
490 label: lsp_types::ParameterLabel::LabelOffsets(label_offsets),
491 documentation: None,
492 })
493 .collect::<Vec<_>>();
494 (call_info.signature, params)
495 }
496 (true, true) => {
497 let mut params = Vec::new();
498 let mut label = String::new();
499 let mut first = true;
500 for param in call_info.parameter_labels() {
501 if !first {
502 label.push_str(", ");
503 }
504 first = false;
505 let start = label.chars().count() as u32;
506 label.push_str(param);
507 let end = label.chars().count() as u32;
508 params.push(lsp_types::ParameterInformation {
509 label: lsp_types::ParameterLabel::LabelOffsets([start, end]),
510 documentation: None,
511 });
512 }
513
514 (label, params)
515 }
516 };
517
518 let documentation = call_info.doc.filter(|_| config.docs).map(|doc| {
519 lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
520 kind: lsp_types::MarkupKind::Markdown,
521 value: format_docs(&doc),
522 })
523 });
524
525 let active_parameter = call_info.active_parameter.map(|it| it as u32);
526
527 let signature = lsp_types::SignatureInformation {
528 label,
529 documentation,
530 parameters: Some(parameters),
531 active_parameter,
532 };
533 lsp_types::SignatureHelp {
534 signatures: vec![signature],
535 active_signature: Some(0),
536 active_parameter,
537 }
538}
539
540pub(crate) fn inlay_hint(
541 snap: &GlobalStateSnapshot,
542 fields_to_resolve: &InlayFieldsToResolve,
543 line_index: &LineIndex,
544 file_id: FileId,
545 mut inlay_hint: InlayHint,
546) -> Cancellable<lsp_types::InlayHint> {
547 let hint_needs_resolve = |hint: &InlayHint| -> Option<TextRange> {
548 hint.resolve_parent.filter(|_| {
549 hint.text_edit.as_ref().is_some_and(LazyProperty::is_lazy)
550 || hint.label.parts.iter().any(|part| {
551 part.linked_location.as_ref().is_some_and(LazyProperty::is_lazy)
552 || part.tooltip.as_ref().is_some_and(LazyProperty::is_lazy)
553 })
554 })
555 };
556
557 let resolve_range_and_hash = hint_needs_resolve(&inlay_hint).map(|range| {
558 (
559 range,
560 std::hash::BuildHasher::hash_one(
561 &std::hash::BuildHasherDefault::<FxHasher>::default(),
562 &inlay_hint,
563 ),
564 )
565 });
566
567 let mut something_to_resolve = false;
568 let text_edits = inlay_hint
569 .text_edit
570 .take()
571 .and_then(|it| match it {
572 LazyProperty::Computed(it) => Some(it),
573 LazyProperty::Lazy => {
574 something_to_resolve |=
575 snap.config.visual_studio_code_version().is_none_or(|version| {
576 VersionReq::parse(">=1.86.0").unwrap().matches(version)
577 }) && resolve_range_and_hash.is_some()
578 && fields_to_resolve.resolve_text_edits;
579 None
580 }
581 })
582 .map(|it| text_edit_vec(line_index, it));
583 let (label, tooltip) = inlay_hint_label(
584 snap,
585 fields_to_resolve,
586 &mut something_to_resolve,
587 resolve_range_and_hash.is_some(),
588 inlay_hint.label,
589 )?;
590
591 let data = match resolve_range_and_hash {
592 Some((resolve_range, hash)) if something_to_resolve => Some(
593 to_value(lsp_ext::InlayHintResolveData {
594 file_id: file_id.index(),
595 hash: hash.to_string(),
596 version: snap.file_version(file_id),
597 resolve_range: range(line_index, resolve_range),
598 })
599 .unwrap(),
600 ),
601 _ => None,
602 };
603
604 Ok(lsp_types::InlayHint {
605 position: match inlay_hint.position {
606 ide::InlayHintPosition::Before => position(line_index, inlay_hint.range.start()),
607 ide::InlayHintPosition::After => position(line_index, inlay_hint.range.end()),
608 },
609 padding_left: Some(inlay_hint.pad_left),
610 padding_right: Some(inlay_hint.pad_right),
611 kind: match inlay_hint.kind {
612 InlayKind::Parameter | InlayKind::GenericParameter => {
613 Some(lsp_types::InlayHintKind::PARAMETER)
614 }
615 InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE),
616 _ => None,
617 },
618 text_edits,
619 data,
620 tooltip,
621 label,
622 })
623}
624
625fn inlay_hint_label(
626 snap: &GlobalStateSnapshot,
627 fields_to_resolve: &InlayFieldsToResolve,
628 something_to_resolve: &mut bool,
629 needs_resolve: bool,
630 mut label: InlayHintLabel,
631) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)> {
632 let (label, tooltip) = match &*label.parts {
633 [InlayHintLabelPart { linked_location: None, .. }] => {
634 let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap();
635 let tooltip = tooltip.and_then(|it| match it {
636 LazyProperty::Computed(it) => Some(it),
637 LazyProperty::Lazy => {
638 *something_to_resolve |=
639 needs_resolve && fields_to_resolve.resolve_hint_tooltip;
640 None
641 }
642 });
643 let hint_tooltip = match tooltip {
644 Some(ide::InlayTooltip::String(s)) => Some(lsp_types::InlayHintTooltip::String(s)),
645 Some(ide::InlayTooltip::Markdown(s)) => {
646 Some(lsp_types::InlayHintTooltip::MarkupContent(lsp_types::MarkupContent {
647 kind: lsp_types::MarkupKind::Markdown,
648 value: s,
649 }))
650 }
651 None => None,
652 };
653 (lsp_types::InlayHintLabel::String(text), hint_tooltip)
654 }
655 _ => {
656 let parts = label
657 .parts
658 .into_iter()
659 .map(|part| {
660 let tooltip = part.tooltip.and_then(|it| match it {
661 LazyProperty::Computed(it) => Some(it),
662 LazyProperty::Lazy => {
663 *something_to_resolve |= fields_to_resolve.resolve_label_tooltip;
664 None
665 }
666 });
667 let tooltip = match tooltip {
668 Some(ide::InlayTooltip::String(s)) => {
669 Some(lsp_types::InlayHintLabelPartTooltip::String(s))
670 }
671 Some(ide::InlayTooltip::Markdown(s)) => {
672 Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
673 lsp_types::MarkupContent {
674 kind: lsp_types::MarkupKind::Markdown,
675 value: s,
676 },
677 ))
678 }
679 None => None,
680 };
681 let location = part
682 .linked_location
683 .and_then(|it| match it {
684 LazyProperty::Computed(it) => Some(it),
685 LazyProperty::Lazy => {
686 *something_to_resolve |= fields_to_resolve.resolve_label_location;
687 None
688 }
689 })
690 .map(|range| location(snap, range))
691 .transpose()?;
692 Ok(lsp_types::InlayHintLabelPart {
693 value: part.text,
694 tooltip,
695 location,
696 command: None,
697 })
698 })
699 .collect::<Cancellable<_>>()?;
700 (lsp_types::InlayHintLabel::LabelParts(parts), None)
701 }
702 };
703 Ok((label, tooltip))
704}
705
706static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);
707
708pub(crate) fn semantic_tokens(
709 text: &str,
710 line_index: &LineIndex,
711 highlights: Vec<HlRange>,
712 semantics_tokens_augments_syntax_tokens: bool,
713 non_standard_tokens: bool,
714) -> lsp_types::SemanticTokens {
715 let id = TOKEN_RESULT_COUNTER.fetch_add(1, Ordering::SeqCst).to_string();
716 let mut builder = semantic_tokens::SemanticTokensBuilder::new(id);
717
718 for highlight_range in highlights {
719 if highlight_range.highlight.is_empty() {
720 continue;
721 }
722
723 if semantics_tokens_augments_syntax_tokens {
724 match highlight_range.highlight.tag {
725 HlTag::BoolLiteral
726 | HlTag::ByteLiteral
727 | HlTag::CharLiteral
728 | HlTag::Comment
729 | HlTag::Keyword
730 | HlTag::NumericLiteral
731 | HlTag::Operator(_)
732 | HlTag::Punctuation(_)
733 | HlTag::StringLiteral
734 | HlTag::None
735 if highlight_range.highlight.mods.is_empty() =>
736 {
737 continue
738 }
739 _ => (),
740 }
741 }
742
743 let (mut ty, mut mods) = semantic_token_type_and_modifiers(highlight_range.highlight);
744
745 if !non_standard_tokens {
746 ty = match standard_fallback_type(ty) {
747 Some(ty) => ty,
748 None => continue,
749 };
750 mods.standard_fallback();
751 }
752 let token_index = semantic_tokens::type_index(ty);
753 let modifier_bitset = mods.0;
754
755 for mut text_range in line_index.index.lines(highlight_range.range) {
756 if text[text_range].ends_with('\n') {
757 text_range =
758 TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n'));
759 }
760 let range = range(line_index, text_range);
761 builder.push(range, token_index, modifier_bitset);
762 }
763 }
764
765 builder.build()
766}
767
768pub(crate) fn semantic_token_delta(
769 previous: &lsp_types::SemanticTokens,
770 current: &lsp_types::SemanticTokens,
771) -> lsp_types::SemanticTokensDelta {
772 let result_id = current.result_id.clone();
773 let edits = semantic_tokens::diff_tokens(&previous.data, ¤t.data);
774 lsp_types::SemanticTokensDelta { result_id, edits }
775}
776
777fn semantic_token_type_and_modifiers(
778 highlight: Highlight,
779) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet) {
780 use semantic_tokens::{modifiers as mods, types};
781
782 let ty = match highlight.tag {
783 HlTag::Symbol(symbol) => match symbol {
784 SymbolKind::Attribute => types::DECORATOR,
785 SymbolKind::Derive => types::DERIVE,
786 SymbolKind::DeriveHelper => types::DERIVE_HELPER,
787 SymbolKind::Module => types::NAMESPACE,
788 SymbolKind::Impl => types::TYPE_ALIAS,
789 SymbolKind::Field => types::PROPERTY,
790 SymbolKind::TypeParam => types::TYPE_PARAMETER,
791 SymbolKind::ConstParam => types::CONST_PARAMETER,
792 SymbolKind::LifetimeParam => types::LIFETIME,
793 SymbolKind::Label => types::LABEL,
794 SymbolKind::ValueParam => types::PARAMETER,
795 SymbolKind::SelfParam => types::SELF_KEYWORD,
796 SymbolKind::SelfType => types::SELF_TYPE_KEYWORD,
797 SymbolKind::Local => types::VARIABLE,
798 SymbolKind::Method => types::METHOD,
799 SymbolKind::Function => types::FUNCTION,
800 SymbolKind::Const => types::CONST,
801 SymbolKind::Static => types::STATIC,
802 SymbolKind::Struct => types::STRUCT,
803 SymbolKind::Enum => types::ENUM,
804 SymbolKind::Variant => types::ENUM_MEMBER,
805 SymbolKind::Union => types::UNION,
806 SymbolKind::TypeAlias => types::TYPE_ALIAS,
807 SymbolKind::Trait => types::INTERFACE,
808 SymbolKind::TraitAlias => types::INTERFACE,
809 SymbolKind::Macro => types::MACRO,
810 SymbolKind::ProcMacro => types::PROC_MACRO,
811 SymbolKind::BuiltinAttr => types::BUILTIN_ATTRIBUTE,
812 SymbolKind::ToolModule => types::TOOL_MODULE,
813 SymbolKind::InlineAsmRegOrRegClass => types::KEYWORD,
814 },
815 HlTag::AttributeBracket => types::ATTRIBUTE_BRACKET,
816 HlTag::BoolLiteral => types::BOOLEAN,
817 HlTag::BuiltinType => types::BUILTIN_TYPE,
818 HlTag::ByteLiteral | HlTag::NumericLiteral => types::NUMBER,
819 HlTag::CharLiteral => types::CHAR,
820 HlTag::Comment => types::COMMENT,
821 HlTag::EscapeSequence => types::ESCAPE_SEQUENCE,
822 HlTag::InvalidEscapeSequence => types::INVALID_ESCAPE_SEQUENCE,
823 HlTag::FormatSpecifier => types::FORMAT_SPECIFIER,
824 HlTag::Keyword => types::KEYWORD,
825 HlTag::None => types::GENERIC,
826 HlTag::Operator(op) => match op {
827 HlOperator::Bitwise => types::BITWISE,
828 HlOperator::Arithmetic => types::ARITHMETIC,
829 HlOperator::Logical => types::LOGICAL,
830 HlOperator::Comparison => types::COMPARISON,
831 HlOperator::Other => types::OPERATOR,
832 },
833 HlTag::StringLiteral => types::STRING,
834 HlTag::UnresolvedReference => types::UNRESOLVED_REFERENCE,
835 HlTag::Punctuation(punct) => match punct {
836 HlPunct::Bracket => types::BRACKET,
837 HlPunct::Brace => types::BRACE,
838 HlPunct::Parenthesis => types::PARENTHESIS,
839 HlPunct::Angle => types::ANGLE,
840 HlPunct::Comma => types::COMMA,
841 HlPunct::Dot => types::DOT,
842 HlPunct::Colon => types::COLON,
843 HlPunct::Semi => types::SEMICOLON,
844 HlPunct::Other => types::PUNCTUATION,
845 HlPunct::MacroBang => types::MACRO_BANG,
846 },
847 };
848
849 let mut mods = semantic_tokens::ModifierSet::default();
850 for modifier in highlight.mods.iter() {
851 let modifier = match modifier {
852 HlMod::Associated => mods::ASSOCIATED,
853 HlMod::Async => mods::ASYNC,
854 HlMod::Attribute => mods::ATTRIBUTE_MODIFIER,
855 HlMod::Callable => mods::CALLABLE,
856 HlMod::Const => mods::CONSTANT,
857 HlMod::Consuming => mods::CONSUMING,
858 HlMod::ControlFlow => mods::CONTROL_FLOW,
859 HlMod::CrateRoot => mods::CRATE_ROOT,
860 HlMod::DefaultLibrary => mods::DEFAULT_LIBRARY,
861 HlMod::Definition => mods::DECLARATION,
862 HlMod::Documentation => mods::DOCUMENTATION,
863 HlMod::Injected => mods::INJECTED,
864 HlMod::IntraDocLink => mods::INTRA_DOC_LINK,
865 HlMod::Library => mods::LIBRARY,
866 HlMod::Macro => mods::MACRO_MODIFIER,
867 HlMod::ProcMacro => mods::PROC_MACRO_MODIFIER,
868 HlMod::Mutable => mods::MUTABLE,
869 HlMod::Public => mods::PUBLIC,
870 HlMod::Reference => mods::REFERENCE,
871 HlMod::Static => mods::STATIC,
872 HlMod::Trait => mods::TRAIT_MODIFIER,
873 HlMod::Unsafe => mods::UNSAFE,
874 };
875 mods |= modifier;
876 }
877
878 (ty, mods)
879}
880
881pub(crate) fn folding_range(
882 text: &str,
883 line_index: &LineIndex,
884 line_folding_only: bool,
885 fold: Fold,
886) -> lsp_types::FoldingRange {
887 let kind = match fold.kind {
888 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
889 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
890 FoldKind::Region => Some(lsp_types::FoldingRangeKind::Region),
891 FoldKind::Mods
892 | FoldKind::Block
893 | FoldKind::ArgList
894 | FoldKind::Consts
895 | FoldKind::Statics
896 | FoldKind::WhereClause
897 | FoldKind::ReturnType
898 | FoldKind::Array
899 | FoldKind::MatchArm => None,
900 };
901
902 let range = range(line_index, fold.range);
903
904 if line_folding_only {
905 let has_more_text_on_end_line = text[TextRange::new(fold.range.end(), TextSize::of(text))]
910 .chars()
911 .take_while(|it| *it != '\n')
912 .any(|it| !it.is_whitespace());
913
914 let end_line = if has_more_text_on_end_line {
915 range.end.line.saturating_sub(1)
916 } else {
917 range.end.line
918 };
919
920 lsp_types::FoldingRange {
921 start_line: range.start.line,
922 start_character: None,
923 end_line,
924 end_character: None,
925 kind,
926 collapsed_text: None,
927 }
928 } else {
929 lsp_types::FoldingRange {
930 start_line: range.start.line,
931 start_character: Some(range.start.character),
932 end_line: range.end.line,
933 end_character: Some(range.end.character),
934 kind,
935 collapsed_text: None,
936 }
937 }
938}
939
940pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url {
941 snap.file_id_to_url(file_id)
942}
943
944pub(crate) fn url_from_abs_path(path: &AbsPath) -> lsp_types::Url {
949 let url = lsp_types::Url::from_file_path(path).unwrap();
950 match path.components().next() {
951 Some(Utf8Component::Prefix(prefix))
952 if matches!(prefix.kind(), Utf8Prefix::Disk(_) | Utf8Prefix::VerbatimDisk(_)) =>
953 {
954 }
956 _ => return url,
957 }
958
959 let driver_letter_range = {
960 let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() {
961 Some(it) => it,
962 None => return url,
963 };
964 let start = scheme.len() + ':'.len_utf8();
965 start..(start + drive_letter.len())
966 };
967
968 let mut url: String = url.into();
972 url[driver_letter_range].make_ascii_lowercase();
973 lsp_types::Url::parse(&url).unwrap()
974}
975
976pub(crate) fn optional_versioned_text_document_identifier(
977 snap: &GlobalStateSnapshot,
978 file_id: FileId,
979) -> lsp_types::OptionalVersionedTextDocumentIdentifier {
980 let url = url(snap, file_id);
981 let version = snap.url_file_version(&url);
982 lsp_types::OptionalVersionedTextDocumentIdentifier { uri: url, version }
983}
984
985pub(crate) fn location(
986 snap: &GlobalStateSnapshot,
987 frange: FileRange,
988) -> Cancellable<lsp_types::Location> {
989 let url = url(snap, frange.file_id);
990 let line_index = snap.file_line_index(frange.file_id)?;
991 let range = range(&line_index, frange.range);
992 let loc = lsp_types::Location::new(url, range);
993 Ok(loc)
994}
995
996pub(crate) fn location_from_nav(
998 snap: &GlobalStateSnapshot,
999 nav: NavigationTarget,
1000) -> Cancellable<lsp_types::Location> {
1001 let url = url(snap, nav.file_id);
1002 let line_index = snap.file_line_index(nav.file_id)?;
1003 let range = range(&line_index, nav.focus_or_full_range());
1004 let loc = lsp_types::Location::new(url, range);
1005 Ok(loc)
1006}
1007
1008pub(crate) fn location_link(
1009 snap: &GlobalStateSnapshot,
1010 src: Option<FileRange>,
1011 target: NavigationTarget,
1012) -> Cancellable<lsp_types::LocationLink> {
1013 let origin_selection_range = match src {
1014 Some(src) => {
1015 let line_index = snap.file_line_index(src.file_id)?;
1016 let range = range(&line_index, src.range);
1017 Some(range)
1018 }
1019 None => None,
1020 };
1021 let (target_uri, target_range, target_selection_range) = location_info(snap, target)?;
1022 let res = lsp_types::LocationLink {
1023 origin_selection_range,
1024 target_uri,
1025 target_range,
1026 target_selection_range,
1027 };
1028 Ok(res)
1029}
1030
1031fn location_info(
1032 snap: &GlobalStateSnapshot,
1033 target: NavigationTarget,
1034) -> Cancellable<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> {
1035 let line_index = snap.file_line_index(target.file_id)?;
1036
1037 let target_uri = url(snap, target.file_id);
1038 let target_range = range(&line_index, target.full_range);
1039 let target_selection_range =
1040 target.focus_range.map(|it| range(&line_index, it)).unwrap_or(target_range);
1041 Ok((target_uri, target_range, target_selection_range))
1042}
1043
1044pub(crate) fn goto_definition_response(
1045 snap: &GlobalStateSnapshot,
1046 src: Option<FileRange>,
1047 targets: Vec<NavigationTarget>,
1048) -> Cancellable<lsp_types::GotoDefinitionResponse> {
1049 if snap.config.location_link() {
1050 let links = targets
1051 .into_iter()
1052 .unique_by(|nav| (nav.file_id, nav.full_range, nav.focus_range))
1053 .map(|nav| location_link(snap, src, nav))
1054 .collect::<Cancellable<Vec<_>>>()?;
1055 Ok(links.into())
1056 } else {
1057 let locations = targets
1058 .into_iter()
1059 .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
1060 .unique()
1061 .map(|range| location(snap, range))
1062 .collect::<Cancellable<Vec<_>>>()?;
1063 Ok(locations.into())
1064 }
1065}
1066
1067fn outside_workspace_annotation_id() -> String {
1068 String::from("OutsideWorkspace")
1069}
1070
1071fn merge_text_and_snippet_edits(
1072 line_index: &LineIndex,
1073 edit: TextEdit,
1074 snippet_edit: SnippetEdit,
1075) -> Vec<SnippetTextEdit> {
1076 let mut edits: Vec<SnippetTextEdit> = vec![];
1077 let mut snippets = snippet_edit.into_edit_ranges().into_iter().peekable();
1078 let text_edits = edit.into_iter();
1079 let mut source_text_offset = 0i32;
1081
1082 let offset_range = |range: TextRange, offset: i32| -> TextRange {
1083 let start = u32::from(range.start()).checked_add_signed(offset).unwrap_or(0);
1085 let end = u32::from(range.end()).checked_add_signed(offset).unwrap_or(0);
1086
1087 TextRange::new(start.into(), end.into())
1088 };
1089
1090 for current_indel in text_edits {
1091 let new_range = {
1092 let insert_len =
1093 TextSize::try_from(current_indel.insert.len()).unwrap_or(TextSize::from(u32::MAX));
1094 TextRange::at(current_indel.delete.start(), insert_len)
1095 };
1096
1097 let offset_adjustment =
1099 u32::from(current_indel.delete.len()) as i32 - u32::from(new_range.len()) as i32;
1100
1101 for (snippet_index, snippet_range) in snippets.peeking_take_while(|(_, range)| {
1103 offset_range(*range, source_text_offset).end() < new_range.start()
1104 }) {
1105 let snippet_range = offset_range(snippet_range, source_text_offset);
1107
1108 let snippet_range = if !stdx::always!(
1109 snippet_range.is_empty(),
1110 "placeholder range {:?} is before current text edit range {:?}",
1111 snippet_range,
1112 new_range
1113 ) {
1114 TextRange::empty(snippet_range.start())
1116 } else {
1117 snippet_range
1118 };
1119
1120 edits.push(snippet_text_edit(
1121 line_index,
1122 true,
1123 Indel { insert: format!("${snippet_index}"), delete: snippet_range },
1124 ))
1125 }
1126
1127 if snippets.peek().is_some_and(|(_, range)| {
1128 new_range.intersect(offset_range(*range, source_text_offset)).is_some()
1129 }) {
1130 let mut all_snippets = snippets
1133 .peeking_take_while(|(_, range)| {
1134 new_range.intersect(offset_range(*range, source_text_offset)).is_some()
1135 })
1136 .map(|(tabstop, range)| (tabstop, offset_range(range, source_text_offset)))
1137 .collect_vec();
1138
1139 all_snippets.retain(|(_, range)| {
1141 stdx::always!(
1142 new_range.contains_range(*range),
1143 "found placeholder range {:?} which wasn't fully inside of text edit's new range {:?}", range, new_range
1144 )
1145 });
1146
1147 let mut new_text = current_indel.insert;
1148
1149 let escape_places =
1151 new_text.rmatch_indices(['\\', '$', '}']).map(|(insert, _)| insert).collect_vec();
1152 let mut escape_places = escape_places.into_iter().peekable();
1153 let mut escape_prior_bits = |new_text: &mut String, up_to: usize| {
1154 for before in escape_places.peeking_take_while(|insert| *insert >= up_to) {
1155 new_text.insert(before, '\\');
1156 }
1157 };
1158
1159 for (index, range) in all_snippets.iter().rev() {
1161 let text_range = range - new_range.start();
1162 let (start, end) = (text_range.start().into(), text_range.end().into());
1163
1164 if range.is_empty() {
1165 escape_prior_bits(&mut new_text, start);
1166 new_text.insert_str(start, &format!("${index}"));
1167 } else {
1168 escape_prior_bits(&mut new_text, end);
1169 new_text.insert(end, '}');
1170 escape_prior_bits(&mut new_text, start);
1171 new_text.insert_str(start, &format!("${{{index}:"));
1172 }
1173 }
1174
1175 escape_prior_bits(&mut new_text, 0);
1177
1178 edits.push(snippet_text_edit(
1179 line_index,
1180 true,
1181 Indel { insert: new_text, delete: current_indel.delete },
1182 ))
1183 } else {
1184 edits.push(snippet_text_edit(line_index, false, current_indel));
1187 }
1188
1189 source_text_offset += offset_adjustment;
1191 }
1192
1193 edits.extend(snippets.map(|(snippet_index, snippet_range)| {
1195 let snippet_range = offset_range(snippet_range, source_text_offset);
1197
1198 let snippet_range = if !stdx::always!(
1199 snippet_range.is_empty(),
1200 "found placeholder snippet {:?} without a text edit",
1201 snippet_range
1202 ) {
1203 TextRange::empty(snippet_range.start())
1204 } else {
1205 snippet_range
1206 };
1207
1208 snippet_text_edit(
1209 line_index,
1210 true,
1211 Indel { insert: format!("${snippet_index}"), delete: snippet_range },
1212 )
1213 }));
1214
1215 edits
1216}
1217
1218pub(crate) fn snippet_text_document_edit(
1219 snap: &GlobalStateSnapshot,
1220 is_snippet: bool,
1221 file_id: FileId,
1222 edit: TextEdit,
1223 snippet_edit: Option<SnippetEdit>,
1224) -> Cancellable<lsp_ext::SnippetTextDocumentEdit> {
1225 let text_document = optional_versioned_text_document_identifier(snap, file_id);
1226 let line_index = snap.file_line_index(file_id)?;
1227 let mut edits = if let Some(snippet_edit) = snippet_edit {
1228 merge_text_and_snippet_edits(&line_index, edit, snippet_edit)
1229 } else {
1230 edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect()
1231 };
1232
1233 if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() {
1234 for edit in &mut edits {
1235 edit.annotation_id = Some(outside_workspace_annotation_id())
1236 }
1237 }
1238 Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
1239}
1240
1241pub(crate) fn snippet_text_document_ops(
1242 snap: &GlobalStateSnapshot,
1243 file_system_edit: FileSystemEdit,
1244) -> Cancellable<Vec<lsp_ext::SnippetDocumentChangeOperation>> {
1245 let mut ops = Vec::new();
1246 match file_system_edit {
1247 FileSystemEdit::CreateFile { dst, initial_contents } => {
1248 let uri = snap.anchored_path(&dst);
1249 let create_file = lsp_types::ResourceOp::Create(lsp_types::CreateFile {
1250 uri: uri.clone(),
1251 options: None,
1252 annotation_id: None,
1253 });
1254 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(create_file));
1255 if !initial_contents.is_empty() {
1256 let text_document =
1257 lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version: None };
1258 let text_edit = lsp_ext::SnippetTextEdit {
1259 range: lsp_types::Range::default(),
1260 new_text: initial_contents,
1261 insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
1262 annotation_id: None,
1263 };
1264 let edit_file =
1265 lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] };
1266 ops.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit_file));
1267 }
1268 }
1269 FileSystemEdit::MoveFile { src, dst } => {
1270 let old_uri = snap.file_id_to_url(src);
1271 let new_uri = snap.anchored_path(&dst);
1272 let mut rename_file =
1273 lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
1274 if snap.analysis.is_library_file(src).ok() == Some(true)
1275 && snap.config.change_annotation_support()
1276 {
1277 rename_file.annotation_id = Some(outside_workspace_annotation_id())
1278 }
1279 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
1280 rename_file,
1281 )))
1282 }
1283 FileSystemEdit::MoveDir { src, src_id, dst } => {
1284 let old_uri = snap.anchored_path(&src);
1285 let new_uri = snap.anchored_path(&dst);
1286 let mut rename_file =
1287 lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
1288 if snap.analysis.is_library_file(src_id).ok() == Some(true)
1289 && snap.config.change_annotation_support()
1290 {
1291 rename_file.annotation_id = Some(outside_workspace_annotation_id())
1292 }
1293 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
1294 rename_file,
1295 )))
1296 }
1297 }
1298 Ok(ops)
1299}
1300
1301pub(crate) fn snippet_workspace_edit(
1302 snap: &GlobalStateSnapshot,
1303 mut source_change: SourceChange,
1304) -> Cancellable<lsp_ext::SnippetWorkspaceEdit> {
1305 let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
1306
1307 for op in &mut source_change.file_system_edits {
1308 if let FileSystemEdit::CreateFile { dst, initial_contents } = op {
1309 let op = FileSystemEdit::CreateFile {
1311 dst: dst.clone(),
1312 initial_contents: mem::take(initial_contents),
1313 };
1314 let ops = snippet_text_document_ops(snap, op)?;
1315 document_changes.extend_from_slice(&ops);
1316 }
1317 }
1318 for (file_id, (edit, snippet_edit)) in source_change.source_file_edits {
1319 let edit = snippet_text_document_edit(
1320 snap,
1321 source_change.is_snippet,
1322 file_id,
1323 edit,
1324 snippet_edit,
1325 )?;
1326 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
1327 }
1328 for op in source_change.file_system_edits {
1329 if !matches!(op, FileSystemEdit::CreateFile { .. }) {
1330 let ops = snippet_text_document_ops(snap, op)?;
1331 document_changes.extend_from_slice(&ops);
1332 }
1333 }
1334 let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit {
1335 changes: None,
1336 document_changes: Some(document_changes),
1337 change_annotations: None,
1338 };
1339 if snap.config.change_annotation_support() {
1340 workspace_edit.change_annotations = Some(
1341 once((
1342 outside_workspace_annotation_id(),
1343 lsp_types::ChangeAnnotation {
1344 label: String::from("Edit outside of the workspace"),
1345 needs_confirmation: Some(true),
1346 description: Some(String::from(
1347 "This edit lies outside of the workspace and may affect dependencies",
1348 )),
1349 },
1350 ))
1351 .collect(),
1352 )
1353 }
1354 Ok(workspace_edit)
1355}
1356
1357pub(crate) fn workspace_edit(
1358 snap: &GlobalStateSnapshot,
1359 source_change: SourceChange,
1360) -> Cancellable<lsp_types::WorkspaceEdit> {
1361 assert!(!source_change.is_snippet);
1362 snippet_workspace_edit(snap, source_change).map(|it| it.into())
1363}
1364
1365impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
1366 fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::WorkspaceEdit {
1367 lsp_types::WorkspaceEdit {
1368 changes: None,
1369 document_changes: snippet_workspace_edit.document_changes.map(|changes| {
1370 lsp_types::DocumentChanges::Operations(
1371 changes
1372 .into_iter()
1373 .map(|change| match change {
1374 lsp_ext::SnippetDocumentChangeOperation::Op(op) => {
1375 lsp_types::DocumentChangeOperation::Op(op)
1376 }
1377 lsp_ext::SnippetDocumentChangeOperation::Edit(edit) => {
1378 lsp_types::DocumentChangeOperation::Edit(
1379 lsp_types::TextDocumentEdit {
1380 text_document: edit.text_document,
1381 edits: edit.edits.into_iter().map(From::from).collect(),
1382 },
1383 )
1384 }
1385 })
1386 .collect(),
1387 )
1388 }),
1389 change_annotations: snippet_workspace_edit.change_annotations,
1390 }
1391 }
1392}
1393
1394impl From<lsp_ext::SnippetTextEdit>
1395 for lsp_types::OneOf<lsp_types::TextEdit, lsp_types::AnnotatedTextEdit>
1396{
1397 fn from(
1398 lsp_ext::SnippetTextEdit { annotation_id, insert_text_format:_, new_text, range }: lsp_ext::SnippetTextEdit,
1399 ) -> Self {
1400 match annotation_id {
1401 Some(annotation_id) => lsp_types::OneOf::Right(lsp_types::AnnotatedTextEdit {
1402 text_edit: lsp_types::TextEdit { range, new_text },
1403 annotation_id,
1404 }),
1405 None => lsp_types::OneOf::Left(lsp_types::TextEdit { range, new_text }),
1406 }
1407 }
1408}
1409
1410pub(crate) fn call_hierarchy_item(
1411 snap: &GlobalStateSnapshot,
1412 target: NavigationTarget,
1413) -> Cancellable<lsp_types::CallHierarchyItem> {
1414 let name = target.name.to_string();
1415 let detail = target.description.clone();
1416 let kind = target.kind.map(symbol_kind).unwrap_or(lsp_types::SymbolKind::FUNCTION);
1417 let (uri, range, selection_range) = location_info(snap, target)?;
1418 Ok(lsp_types::CallHierarchyItem {
1419 name,
1420 kind,
1421 tags: None,
1422 detail,
1423 uri,
1424 range,
1425 selection_range,
1426 data: None,
1427 })
1428}
1429
1430pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
1431 match kind {
1432 AssistKind::None | AssistKind::Generate => lsp_types::CodeActionKind::EMPTY,
1433 AssistKind::QuickFix => lsp_types::CodeActionKind::QUICKFIX,
1434 AssistKind::Refactor => lsp_types::CodeActionKind::REFACTOR,
1435 AssistKind::RefactorExtract => lsp_types::CodeActionKind::REFACTOR_EXTRACT,
1436 AssistKind::RefactorInline => lsp_types::CodeActionKind::REFACTOR_INLINE,
1437 AssistKind::RefactorRewrite => lsp_types::CodeActionKind::REFACTOR_REWRITE,
1438 }
1439}
1440
1441pub(crate) fn code_action(
1442 snap: &GlobalStateSnapshot,
1443 assist: Assist,
1444 resolve_data: Option<(usize, lsp_types::CodeActionParams, Option<i32>)>,
1445) -> Cancellable<lsp_ext::CodeAction> {
1446 let mut res = lsp_ext::CodeAction {
1447 title: assist.label.to_string(),
1448 group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
1449 kind: Some(code_action_kind(assist.id.1)),
1450 edit: None,
1451 is_preferred: None,
1452 data: None,
1453 command: None,
1454 };
1455
1456 let commands = snap.config.client_commands();
1457 res.command = match assist.command {
1458 Some(assists::Command::TriggerParameterHints) if commands.trigger_parameter_hints => {
1459 Some(command::trigger_parameter_hints())
1460 }
1461 Some(assists::Command::Rename) if commands.rename => Some(command::rename()),
1462 _ => None,
1463 };
1464
1465 match (assist.source_change, resolve_data) {
1466 (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?),
1467 (None, Some((index, code_action_params, version))) => {
1468 res.data = Some(lsp_ext::CodeActionData {
1469 id: format!("{}:{}:{index}", assist.id.0, assist.id.1.name()),
1470 code_action_params,
1471 version,
1472 });
1473 }
1474 (None, None) => {
1475 stdx::never!("assist should always be resolved if client can't do lazy resolving")
1476 }
1477 };
1478 Ok(res)
1479}
1480
1481pub(crate) fn runnable(
1482 snap: &GlobalStateSnapshot,
1483 runnable: Runnable,
1484) -> Cancellable<Option<lsp_ext::Runnable>> {
1485 let target_spec = TargetSpec::for_file(snap, runnable.nav.file_id)?;
1486 let source_root = snap.analysis.source_root_id(runnable.nav.file_id).ok();
1487 let config = snap.config.runnables(source_root);
1488
1489 match target_spec {
1490 Some(TargetSpec::Cargo(spec)) => {
1491 let workspace_root = spec.workspace_root.clone();
1492
1493 let target = spec.target.clone();
1494
1495 let (cargo_args, executable_args) = CargoTargetSpec::runnable_args(
1496 snap,
1497 Some(spec.clone()),
1498 &runnable.kind,
1499 &runnable.cfg,
1500 );
1501
1502 let cwd = match runnable.kind {
1503 ide::RunnableKind::Bin { .. } => workspace_root.clone(),
1504 _ => spec.cargo_toml.parent().to_owned(),
1505 };
1506
1507 let label = runnable.label(Some(&target));
1508 let location = location_link(snap, None, runnable.nav)?;
1509
1510 Ok(Some(lsp_ext::Runnable {
1511 label,
1512 location: Some(location),
1513 kind: lsp_ext::RunnableKind::Cargo,
1514 args: lsp_ext::RunnableArgs::Cargo(lsp_ext::CargoRunnableArgs {
1515 workspace_root: Some(workspace_root.into()),
1516 override_cargo: config.override_cargo,
1517 cargo_args,
1518 cwd: cwd.into(),
1519 executable_args,
1520 environment: spec
1521 .sysroot_root
1522 .map(|root| ("RUSTC_TOOLCHAIN".to_owned(), root.to_string()))
1523 .into_iter()
1524 .collect(),
1525 }),
1526 }))
1527 }
1528 Some(TargetSpec::ProjectJson(spec)) => {
1529 let label = runnable.label(Some(&spec.label));
1530 let location = location_link(snap, None, runnable.nav)?;
1531
1532 match spec.runnable_args(&runnable.kind) {
1533 Some(json_shell_runnable_args) => {
1534 let runnable_args = ShellRunnableArgs {
1535 program: json_shell_runnable_args.program,
1536 args: json_shell_runnable_args.args,
1537 cwd: json_shell_runnable_args.cwd,
1538 environment: Default::default(),
1539 };
1540 Ok(Some(lsp_ext::Runnable {
1541 label,
1542 location: Some(location),
1543 kind: lsp_ext::RunnableKind::Shell,
1544 args: lsp_ext::RunnableArgs::Shell(runnable_args),
1545 }))
1546 }
1547 None => Ok(None),
1548 }
1549 }
1550 None => {
1551 let Some(path) = snap.file_id_to_file_path(runnable.nav.file_id).parent() else {
1552 return Ok(None);
1553 };
1554 let (cargo_args, executable_args) =
1555 CargoTargetSpec::runnable_args(snap, None, &runnable.kind, &runnable.cfg);
1556
1557 let label = runnable.label(None);
1558 let location = location_link(snap, None, runnable.nav)?;
1559
1560 Ok(Some(lsp_ext::Runnable {
1561 label,
1562 location: Some(location),
1563 kind: lsp_ext::RunnableKind::Cargo,
1564 args: lsp_ext::RunnableArgs::Cargo(lsp_ext::CargoRunnableArgs {
1565 workspace_root: None,
1566 override_cargo: config.override_cargo,
1567 cargo_args,
1568 cwd: path.as_path().unwrap().to_path_buf().into(),
1569 executable_args,
1570 environment: Default::default(),
1571 }),
1572 }))
1573 }
1574 }
1575}
1576
1577pub(crate) fn code_lens(
1578 acc: &mut Vec<lsp_types::CodeLens>,
1579 snap: &GlobalStateSnapshot,
1580 annotation: Annotation,
1581) -> Cancellable<()> {
1582 let client_commands_config = snap.config.client_commands();
1583 match annotation.kind {
1584 AnnotationKind::Runnable(run) => {
1585 let line_index = snap.file_line_index(run.nav.file_id)?;
1586 let annotation_range = range(&line_index, annotation.range);
1587
1588 let update_test = run.update_test;
1589 let title = run.title();
1590 let can_debug = match run.kind {
1591 ide::RunnableKind::DocTest { .. } => false,
1592 ide::RunnableKind::TestMod { .. }
1593 | ide::RunnableKind::Test { .. }
1594 | ide::RunnableKind::Bench { .. }
1595 | ide::RunnableKind::Bin => true,
1596 };
1597 let r = runnable(snap, run)?;
1598
1599 if let Some(r) = r {
1600 let has_root = match &r.args {
1601 lsp_ext::RunnableArgs::Cargo(c) => c.workspace_root.is_some(),
1602 lsp_ext::RunnableArgs::Shell(_) => true,
1603 };
1604
1605 let lens_config = snap.config.lens();
1606
1607 if has_root {
1608 if lens_config.run && client_commands_config.run_single {
1609 let command = command::run_single(&r, &title);
1610 acc.push(lsp_types::CodeLens {
1611 range: annotation_range,
1612 command: Some(command),
1613 data: None,
1614 })
1615 }
1616 if lens_config.debug && can_debug && client_commands_config.debug_single {
1617 let command = command::debug_single(&r);
1618 acc.push(lsp_types::CodeLens {
1619 range: annotation_range,
1620 command: Some(command),
1621 data: None,
1622 })
1623 }
1624 if lens_config.update_test && client_commands_config.run_single {
1625 let label = update_test.label();
1626 if let Some(r) = make_update_runnable(&r, update_test) {
1627 let command = command::run_single(&r, label.unwrap().as_str());
1628 acc.push(lsp_types::CodeLens {
1629 range: annotation_range,
1630 command: Some(command),
1631 data: None,
1632 })
1633 }
1634 }
1635 }
1636
1637 if lens_config.interpret {
1638 let command = command::interpret_single(&r);
1639 acc.push(lsp_types::CodeLens {
1640 range: annotation_range,
1641 command: Some(command),
1642 data: None,
1643 })
1644 }
1645 }
1646 }
1647 AnnotationKind::HasImpls { pos, data } => {
1648 if !client_commands_config.show_reference {
1649 return Ok(());
1650 }
1651 let line_index = snap.file_line_index(pos.file_id)?;
1652 let annotation_range = range(&line_index, annotation.range);
1653 let url = url(snap, pos.file_id);
1654 let pos = position(&line_index, pos.offset);
1655
1656 let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
1657
1658 let doc_pos = lsp_types::TextDocumentPositionParams::new(id, pos);
1659
1660 let goto_params = lsp_types::request::GotoImplementationParams {
1661 text_document_position_params: doc_pos,
1662 work_done_progress_params: Default::default(),
1663 partial_result_params: Default::default(),
1664 };
1665
1666 let command = data.map(|ranges| {
1667 let locations: Vec<lsp_types::Location> = ranges
1668 .into_iter()
1669 .filter_map(|target| {
1670 location(
1671 snap,
1672 FileRange { file_id: target.file_id, range: target.full_range },
1673 )
1674 .ok()
1675 })
1676 .collect();
1677
1678 command::show_references(
1679 implementation_title(locations.len()),
1680 &url,
1681 pos,
1682 locations,
1683 )
1684 });
1685
1686 acc.push(lsp_types::CodeLens {
1687 range: annotation_range,
1688 command,
1689 data: (|| {
1690 let version = snap.url_file_version(&url)?;
1691 Some(
1692 to_value(lsp_ext::CodeLensResolveData {
1693 version,
1694 kind: lsp_ext::CodeLensResolveDataKind::Impls(goto_params),
1695 })
1696 .unwrap(),
1697 )
1698 })(),
1699 })
1700 }
1701 AnnotationKind::HasReferences { pos, data } => {
1702 if !client_commands_config.show_reference {
1703 return Ok(());
1704 }
1705 let line_index = snap.file_line_index(pos.file_id)?;
1706 let annotation_range = range(&line_index, annotation.range);
1707 let url = url(snap, pos.file_id);
1708 let pos = position(&line_index, pos.offset);
1709
1710 let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
1711
1712 let doc_pos = lsp_types::TextDocumentPositionParams::new(id, pos);
1713
1714 let command = data.map(|ranges| {
1715 let locations: Vec<lsp_types::Location> =
1716 ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect();
1717
1718 command::show_references(reference_title(locations.len()), &url, pos, locations)
1719 });
1720
1721 acc.push(lsp_types::CodeLens {
1722 range: annotation_range,
1723 command,
1724 data: (|| {
1725 let version = snap.url_file_version(&url)?;
1726 Some(
1727 to_value(lsp_ext::CodeLensResolveData {
1728 version,
1729 kind: lsp_ext::CodeLensResolveDataKind::References(doc_pos),
1730 })
1731 .unwrap(),
1732 )
1733 })(),
1734 })
1735 }
1736 }
1737 Ok(())
1738}
1739
1740pub(crate) fn test_item(
1741 snap: &GlobalStateSnapshot,
1742 test_item: ide::TestItem,
1743 line_index: Option<&LineIndex>,
1744) -> Option<lsp_ext::TestItem> {
1745 Some(lsp_ext::TestItem {
1746 id: test_item.id,
1747 label: test_item.label,
1748 kind: match test_item.kind {
1749 ide::TestItemKind::Crate(id) => match snap.target_spec_for_crate(id) {
1750 Some(target_spec) => match target_spec.target_kind() {
1751 project_model::TargetKind::Bin
1752 | project_model::TargetKind::Lib { .. }
1753 | project_model::TargetKind::Example
1754 | project_model::TargetKind::BuildScript
1755 | project_model::TargetKind::Other => lsp_ext::TestItemKind::Package,
1756 project_model::TargetKind::Test => lsp_ext::TestItemKind::Test,
1757 project_model::TargetKind::Bench => return None,
1759 },
1760 None => lsp_ext::TestItemKind::Package,
1761 },
1762 ide::TestItemKind::Module => lsp_ext::TestItemKind::Module,
1763 ide::TestItemKind::Function => lsp_ext::TestItemKind::Test,
1764 },
1765 can_resolve_children: matches!(
1766 test_item.kind,
1767 ide::TestItemKind::Crate(_) | ide::TestItemKind::Module
1768 ),
1769 parent: test_item.parent,
1770 text_document: test_item
1771 .file
1772 .map(|f| lsp_types::TextDocumentIdentifier { uri: url(snap, f) }),
1773 range: line_index.and_then(|l| Some(range(l, test_item.text_range?))),
1774 runnable: test_item.runnable.and_then(|r| runnable(snap, r).ok()).flatten(),
1775 })
1776}
1777
1778pub(crate) mod command {
1779 use ide::{FileRange, NavigationTarget};
1780 use serde_json::to_value;
1781
1782 use crate::{
1783 global_state::GlobalStateSnapshot,
1784 lsp::to_proto::{location, location_link},
1785 lsp_ext,
1786 };
1787
1788 pub(crate) fn show_references(
1789 title: String,
1790 uri: &lsp_types::Url,
1791 position: lsp_types::Position,
1792 locations: Vec<lsp_types::Location>,
1793 ) -> lsp_types::Command {
1794 lsp_types::Command {
1799 title,
1800 command: "rust-analyzer.showReferences".into(),
1801 arguments: Some(vec![
1802 to_value(uri).unwrap(),
1803 to_value(position).unwrap(),
1804 to_value(locations).unwrap(),
1805 ]),
1806 }
1807 }
1808
1809 pub(crate) fn run_single(runnable: &lsp_ext::Runnable, title: &str) -> lsp_types::Command {
1810 lsp_types::Command {
1811 title: title.to_owned(),
1812 command: "rust-analyzer.runSingle".into(),
1813 arguments: Some(vec![to_value(runnable).unwrap()]),
1814 }
1815 }
1816
1817 pub(crate) fn debug_single(runnable: &lsp_ext::Runnable) -> lsp_types::Command {
1818 lsp_types::Command {
1819 title: "âš™\u{fe0e} Debug".into(),
1820 command: "rust-analyzer.debugSingle".into(),
1821 arguments: Some(vec![to_value(runnable).unwrap()]),
1822 }
1823 }
1824
1825 pub(crate) fn interpret_single(_runnable: &lsp_ext::Runnable) -> lsp_types::Command {
1826 lsp_types::Command {
1827 title: "Interpret".into(),
1828 command: "rust-analyzer.interpretFunction".into(),
1829 arguments: Some(vec![]),
1831 }
1832 }
1833
1834 pub(crate) fn goto_location(
1835 snap: &GlobalStateSnapshot,
1836 nav: &NavigationTarget,
1837 ) -> Option<lsp_types::Command> {
1838 let value = if snap.config.location_link() {
1839 let link = location_link(snap, None, nav.clone()).ok()?;
1840 to_value(link).ok()?
1841 } else {
1842 let range = FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() };
1843 let location = location(snap, range).ok()?;
1844 to_value(location).ok()?
1845 };
1846
1847 Some(lsp_types::Command {
1848 title: nav.name.to_string(),
1849 command: "rust-analyzer.gotoLocation".into(),
1850 arguments: Some(vec![value]),
1851 })
1852 }
1853
1854 pub(crate) fn trigger_parameter_hints() -> lsp_types::Command {
1855 lsp_types::Command {
1856 title: "triggerParameterHints".into(),
1857 command: "rust-analyzer.triggerParameterHints".into(),
1858 arguments: None,
1859 }
1860 }
1861
1862 pub(crate) fn rename() -> lsp_types::Command {
1863 lsp_types::Command {
1864 title: "rename".into(),
1865 command: "rust-analyzer.rename".into(),
1866 arguments: None,
1867 }
1868 }
1869}
1870
1871pub(crate) fn make_update_runnable(
1872 runnable: &lsp_ext::Runnable,
1873 update_test: UpdateTest,
1874) -> Option<lsp_ext::Runnable> {
1875 let label = update_test.label()?;
1876
1877 let mut runnable = runnable.clone();
1878 runnable.label = format!("{} + {}", runnable.label, label);
1879
1880 let lsp_ext::RunnableArgs::Cargo(r) = &mut runnable.args else {
1881 return None;
1882 };
1883
1884 r.environment.extend(update_test.env().iter().map(|(k, v)| (k.to_string(), v.to_string())));
1885
1886 if update_test.insta {
1887 r.cargo_args.insert(0, "insta".to_owned());
1888 }
1889
1890 Some(runnable)
1891}
1892
1893pub(crate) fn implementation_title(count: usize) -> String {
1894 if count == 1 {
1895 "1 implementation".into()
1896 } else {
1897 format!("{count} implementations")
1898 }
1899}
1900
1901pub(crate) fn reference_title(count: usize) -> String {
1902 if count == 1 {
1903 "1 reference".into()
1904 } else {
1905 format!("{count} references")
1906 }
1907}
1908
1909pub(crate) fn markup_content(
1910 markup: Markup,
1911 kind: ide::HoverDocFormat,
1912) -> lsp_types::MarkupContent {
1913 let kind = match kind {
1914 ide::HoverDocFormat::Markdown => lsp_types::MarkupKind::Markdown,
1915 ide::HoverDocFormat::PlainText => lsp_types::MarkupKind::PlainText,
1916 };
1917 let value = format_docs(&Documentation::new(markup.into()));
1918 lsp_types::MarkupContent { kind, value }
1919}
1920
1921pub(crate) fn rename_error(err: RenameError) -> LspError {
1922 invalid_params_error(err.to_string())
1925}
1926
1927#[cfg(test)]
1928mod tests {
1929 use expect_test::{expect, Expect};
1930 use ide::{Analysis, FilePosition};
1931 use ide_db::source_change::Snippet;
1932 use test_utils::extract_offset;
1933 use triomphe::Arc;
1934
1935 use super::*;
1936
1937 #[test]
1938 fn conv_fold_line_folding_only_fixup() {
1939 let text = r#"mod a;
1940mod b;
1941mod c;
1942
1943fn main() {
1944 if cond {
1945 a::do_a();
1946 } else {
1947 b::do_b();
1948 }
1949}"#;
1950
1951 let (analysis, file_id) = Analysis::from_single_file(text.to_owned());
1952 let folds = analysis.folding_ranges(file_id).unwrap();
1953 assert_eq!(folds.len(), 4);
1954
1955 let line_index = LineIndex {
1956 index: Arc::new(ide::LineIndex::new(text)),
1957 endings: LineEndings::Unix,
1958 encoding: PositionEncoding::Utf8,
1959 };
1960 let converted: Vec<lsp_types::FoldingRange> =
1961 folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect();
1962
1963 let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
1964 assert_eq!(converted.len(), expected_lines.len());
1965 for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
1966 assert_eq!(folding_range.start_line, *start_line);
1967 assert_eq!(folding_range.start_character, None);
1968 assert_eq!(folding_range.end_line, *end_line);
1969 assert_eq!(folding_range.end_character, None);
1970 }
1971 }
1972
1973 #[test]
1974 fn calling_function_with_ignored_code_in_signature() {
1975 let text = r#"
1976fn foo() {
1977 bar($0);
1978}
1979/// ```
1980/// # use crate::bar;
1981/// bar(5);
1982/// ```
1983fn bar(_: usize) {}
1984"#;
1985
1986 let (offset, text) = extract_offset(text);
1987 let (analysis, file_id) = Analysis::from_single_file(text);
1988 let help = signature_help(
1989 analysis.signature_help(FilePosition { file_id, offset }).unwrap().unwrap(),
1990 CallInfoConfig { params_only: false, docs: true },
1991 false,
1992 );
1993 let docs = match &help.signatures[help.active_signature.unwrap() as usize].documentation {
1994 Some(lsp_types::Documentation::MarkupContent(content)) => &content.value,
1995 _ => panic!("documentation contains markup"),
1996 };
1997 assert!(docs.contains("bar(5)"));
1998 assert!(!docs.contains("use crate::bar"));
1999 }
2000
2001 #[track_caller]
2002 fn check_rendered_snippets(edit: TextEdit, snippets: SnippetEdit, expect: Expect) {
2003 check_rendered_snippets_in_source(
2004 r"/* place to put all ranges in */",
2005 edit,
2006 snippets,
2007 expect,
2008 );
2009 }
2010
2011 #[track_caller]
2012 fn check_rendered_snippets_in_source(
2013 #[ra_ap_rust_analyzer::rust_fixture] ra_fixture: &str,
2014 edit: TextEdit,
2015 snippets: SnippetEdit,
2016 expect: Expect,
2017 ) {
2018 let source = stdx::trim_indent(ra_fixture);
2019 let endings = if source.contains('\r') { LineEndings::Dos } else { LineEndings::Unix };
2020 let line_index = LineIndex {
2021 index: Arc::new(ide::LineIndex::new(&source)),
2022 endings,
2023 encoding: PositionEncoding::Utf8,
2024 };
2025
2026 let res = merge_text_and_snippet_edits(&line_index, edit, snippets);
2027
2028 {
2030 let mut sorted = res.clone();
2031 sorted.sort_by_key(|edit| (edit.range.start, edit.range.end));
2032 let disjoint_ranges = sorted
2033 .iter()
2034 .zip(sorted.iter().skip(1))
2035 .all(|(l, r)| l.range.end <= r.range.start || l == r);
2036 assert!(disjoint_ranges, "ranges overlap for {res:#?}");
2037 }
2038
2039 expect.assert_debug_eq(&res);
2040 }
2041
2042 #[test]
2043 fn snippet_rendering_only_tabstops() {
2044 let edit = TextEdit::builder().finish();
2045 let snippets = SnippetEdit::new(vec![
2046 Snippet::Tabstop(0.into()),
2047 Snippet::Tabstop(0.into()),
2048 Snippet::Tabstop(1.into()),
2049 Snippet::Tabstop(1.into()),
2050 ]);
2051
2052 check_rendered_snippets(
2053 edit,
2054 snippets,
2055 expect![[r#"
2056 [
2057 SnippetTextEdit {
2058 range: Range {
2059 start: Position {
2060 line: 0,
2061 character: 0,
2062 },
2063 end: Position {
2064 line: 0,
2065 character: 0,
2066 },
2067 },
2068 new_text: "$1",
2069 insert_text_format: Some(
2070 Snippet,
2071 ),
2072 annotation_id: None,
2073 },
2074 SnippetTextEdit {
2075 range: Range {
2076 start: Position {
2077 line: 0,
2078 character: 0,
2079 },
2080 end: Position {
2081 line: 0,
2082 character: 0,
2083 },
2084 },
2085 new_text: "$2",
2086 insert_text_format: Some(
2087 Snippet,
2088 ),
2089 annotation_id: None,
2090 },
2091 SnippetTextEdit {
2092 range: Range {
2093 start: Position {
2094 line: 0,
2095 character: 1,
2096 },
2097 end: Position {
2098 line: 0,
2099 character: 1,
2100 },
2101 },
2102 new_text: "$3",
2103 insert_text_format: Some(
2104 Snippet,
2105 ),
2106 annotation_id: None,
2107 },
2108 SnippetTextEdit {
2109 range: Range {
2110 start: Position {
2111 line: 0,
2112 character: 1,
2113 },
2114 end: Position {
2115 line: 0,
2116 character: 1,
2117 },
2118 },
2119 new_text: "$0",
2120 insert_text_format: Some(
2121 Snippet,
2122 ),
2123 annotation_id: None,
2124 },
2125 ]
2126 "#]],
2127 );
2128 }
2129
2130 #[test]
2131 fn snippet_rendering_only_text_edits() {
2132 let mut edit = TextEdit::builder();
2133 edit.insert(0.into(), "abc".to_owned());
2134 edit.insert(3.into(), "def".to_owned());
2135 let edit = edit.finish();
2136 let snippets = SnippetEdit::new(vec![]);
2137
2138 check_rendered_snippets(
2139 edit,
2140 snippets,
2141 expect![[r#"
2142 [
2143 SnippetTextEdit {
2144 range: Range {
2145 start: Position {
2146 line: 0,
2147 character: 0,
2148 },
2149 end: Position {
2150 line: 0,
2151 character: 0,
2152 },
2153 },
2154 new_text: "abc",
2155 insert_text_format: None,
2156 annotation_id: None,
2157 },
2158 SnippetTextEdit {
2159 range: Range {
2160 start: Position {
2161 line: 0,
2162 character: 3,
2163 },
2164 end: Position {
2165 line: 0,
2166 character: 3,
2167 },
2168 },
2169 new_text: "def",
2170 insert_text_format: None,
2171 annotation_id: None,
2172 },
2173 ]
2174 "#]],
2175 );
2176 }
2177
2178 #[test]
2179 fn snippet_rendering_tabstop_after_text_edit() {
2180 let mut edit = TextEdit::builder();
2181 edit.insert(0.into(), "abc".to_owned());
2182 let edit = edit.finish();
2183 let snippets = SnippetEdit::new(vec![Snippet::Tabstop(10.into())]);
2185
2186 check_rendered_snippets(
2187 edit,
2188 snippets,
2189 expect![[r#"
2190 [
2191 SnippetTextEdit {
2192 range: Range {
2193 start: Position {
2194 line: 0,
2195 character: 0,
2196 },
2197 end: Position {
2198 line: 0,
2199 character: 0,
2200 },
2201 },
2202 new_text: "abc",
2203 insert_text_format: None,
2204 annotation_id: None,
2205 },
2206 SnippetTextEdit {
2207 range: Range {
2208 start: Position {
2209 line: 0,
2210 character: 7,
2211 },
2212 end: Position {
2213 line: 0,
2214 character: 7,
2215 },
2216 },
2217 new_text: "$0",
2218 insert_text_format: Some(
2219 Snippet,
2220 ),
2221 annotation_id: None,
2222 },
2223 ]
2224 "#]],
2225 );
2226 }
2227
2228 #[test]
2229 fn snippet_rendering_tabstops_before_text_edit() {
2230 let mut edit = TextEdit::builder();
2231 edit.insert(2.into(), "abc".to_owned());
2232 let edit = edit.finish();
2233 let snippets =
2234 SnippetEdit::new(vec![Snippet::Tabstop(0.into()), Snippet::Tabstop(0.into())]);
2235
2236 check_rendered_snippets(
2237 edit,
2238 snippets,
2239 expect![[r#"
2240 [
2241 SnippetTextEdit {
2242 range: Range {
2243 start: Position {
2244 line: 0,
2245 character: 0,
2246 },
2247 end: Position {
2248 line: 0,
2249 character: 0,
2250 },
2251 },
2252 new_text: "$1",
2253 insert_text_format: Some(
2254 Snippet,
2255 ),
2256 annotation_id: None,
2257 },
2258 SnippetTextEdit {
2259 range: Range {
2260 start: Position {
2261 line: 0,
2262 character: 0,
2263 },
2264 end: Position {
2265 line: 0,
2266 character: 0,
2267 },
2268 },
2269 new_text: "$0",
2270 insert_text_format: Some(
2271 Snippet,
2272 ),
2273 annotation_id: None,
2274 },
2275 SnippetTextEdit {
2276 range: Range {
2277 start: Position {
2278 line: 0,
2279 character: 2,
2280 },
2281 end: Position {
2282 line: 0,
2283 character: 2,
2284 },
2285 },
2286 new_text: "abc",
2287 insert_text_format: None,
2288 annotation_id: None,
2289 },
2290 ]
2291 "#]],
2292 );
2293 }
2294
2295 #[test]
2296 fn snippet_rendering_tabstops_between_text_edits() {
2297 let mut edit = TextEdit::builder();
2298 edit.insert(0.into(), "abc".to_owned());
2299 edit.insert(7.into(), "abc".to_owned());
2300 let edit = edit.finish();
2301 let snippets =
2303 SnippetEdit::new(vec![Snippet::Tabstop(7.into()), Snippet::Tabstop(7.into())]);
2304
2305 check_rendered_snippets(
2306 edit,
2307 snippets,
2308 expect![[r#"
2309 [
2310 SnippetTextEdit {
2311 range: Range {
2312 start: Position {
2313 line: 0,
2314 character: 0,
2315 },
2316 end: Position {
2317 line: 0,
2318 character: 0,
2319 },
2320 },
2321 new_text: "abc",
2322 insert_text_format: None,
2323 annotation_id: None,
2324 },
2325 SnippetTextEdit {
2326 range: Range {
2327 start: Position {
2328 line: 0,
2329 character: 4,
2330 },
2331 end: Position {
2332 line: 0,
2333 character: 4,
2334 },
2335 },
2336 new_text: "$1",
2337 insert_text_format: Some(
2338 Snippet,
2339 ),
2340 annotation_id: None,
2341 },
2342 SnippetTextEdit {
2343 range: Range {
2344 start: Position {
2345 line: 0,
2346 character: 4,
2347 },
2348 end: Position {
2349 line: 0,
2350 character: 4,
2351 },
2352 },
2353 new_text: "$0",
2354 insert_text_format: Some(
2355 Snippet,
2356 ),
2357 annotation_id: None,
2358 },
2359 SnippetTextEdit {
2360 range: Range {
2361 start: Position {
2362 line: 0,
2363 character: 7,
2364 },
2365 end: Position {
2366 line: 0,
2367 character: 7,
2368 },
2369 },
2370 new_text: "abc",
2371 insert_text_format: None,
2372 annotation_id: None,
2373 },
2374 ]
2375 "#]],
2376 );
2377 }
2378
2379 #[test]
2380 fn snippet_rendering_multiple_tabstops_in_text_edit() {
2381 let mut edit = TextEdit::builder();
2382 edit.insert(0.into(), "abcdefghijkl".to_owned());
2383 let edit = edit.finish();
2384 let snippets = SnippetEdit::new(vec![
2385 Snippet::Tabstop(0.into()),
2386 Snippet::Tabstop(5.into()),
2387 Snippet::Tabstop(12.into()),
2388 ]);
2389
2390 check_rendered_snippets(
2391 edit,
2392 snippets,
2393 expect![[r#"
2394 [
2395 SnippetTextEdit {
2396 range: Range {
2397 start: Position {
2398 line: 0,
2399 character: 0,
2400 },
2401 end: Position {
2402 line: 0,
2403 character: 0,
2404 },
2405 },
2406 new_text: "$1abcde$2fghijkl$0",
2407 insert_text_format: Some(
2408 Snippet,
2409 ),
2410 annotation_id: None,
2411 },
2412 ]
2413 "#]],
2414 );
2415 }
2416
2417 #[test]
2418 fn snippet_rendering_multiple_placeholders_in_text_edit() {
2419 let mut edit = TextEdit::builder();
2420 edit.insert(0.into(), "abcdefghijkl".to_owned());
2421 let edit = edit.finish();
2422 let snippets = SnippetEdit::new(vec![
2423 Snippet::Placeholder(TextRange::new(0.into(), 3.into())),
2424 Snippet::Placeholder(TextRange::new(5.into(), 7.into())),
2425 Snippet::Placeholder(TextRange::new(10.into(), 12.into())),
2426 ]);
2427
2428 check_rendered_snippets(
2429 edit,
2430 snippets,
2431 expect![[r#"
2432 [
2433 SnippetTextEdit {
2434 range: Range {
2435 start: Position {
2436 line: 0,
2437 character: 0,
2438 },
2439 end: Position {
2440 line: 0,
2441 character: 0,
2442 },
2443 },
2444 new_text: "${1:abc}de${2:fg}hij${0:kl}",
2445 insert_text_format: Some(
2446 Snippet,
2447 ),
2448 annotation_id: None,
2449 },
2450 ]
2451 "#]],
2452 );
2453 }
2454
2455 #[test]
2456 fn snippet_rendering_escape_snippet_bits() {
2457 let mut edit = TextEdit::builder();
2459 edit.insert(0.into(), r"$ab{}$c\def".to_owned());
2460 edit.insert(8.into(), r"ghi\jk<-check_insert_here$".to_owned());
2461 edit.insert(10.into(), r"a\\b\\c{}$".to_owned());
2462 let edit = edit.finish();
2463 let snippets = SnippetEdit::new(vec![
2464 Snippet::Placeholder(TextRange::new(1.into(), 9.into())),
2465 Snippet::Tabstop(25.into()),
2466 ]);
2467
2468 check_rendered_snippets(
2469 edit,
2470 snippets,
2471 expect![[r#"
2472 [
2473 SnippetTextEdit {
2474 range: Range {
2475 start: Position {
2476 line: 0,
2477 character: 0,
2478 },
2479 end: Position {
2480 line: 0,
2481 character: 0,
2482 },
2483 },
2484 new_text: "\\$${1:ab{\\}\\$c\\\\d}ef",
2485 insert_text_format: Some(
2486 Snippet,
2487 ),
2488 annotation_id: None,
2489 },
2490 SnippetTextEdit {
2491 range: Range {
2492 start: Position {
2493 line: 0,
2494 character: 8,
2495 },
2496 end: Position {
2497 line: 0,
2498 character: 8,
2499 },
2500 },
2501 new_text: "ghi\\\\jk$0<-check_insert_here\\$",
2502 insert_text_format: Some(
2503 Snippet,
2504 ),
2505 annotation_id: None,
2506 },
2507 SnippetTextEdit {
2508 range: Range {
2509 start: Position {
2510 line: 0,
2511 character: 10,
2512 },
2513 end: Position {
2514 line: 0,
2515 character: 10,
2516 },
2517 },
2518 new_text: "a\\\\b\\\\c{}$",
2519 insert_text_format: None,
2520 annotation_id: None,
2521 },
2522 ]
2523 "#]],
2524 );
2525 }
2526
2527 #[test]
2528 fn snippet_rendering_tabstop_adjust_offset_deleted() {
2529 let mut edit = TextEdit::builder();
2531 edit.replace(TextRange::new(47.into(), 56.into()), "let".to_owned());
2532 edit.replace(
2533 TextRange::new(57.into(), 89.into()),
2534 "disabled = false;\n ProcMacro {\n disabled,\n }".to_owned(),
2535 );
2536 let edit = edit.finish();
2537 let snippets = SnippetEdit::new(vec![Snippet::Tabstop(51.into())]);
2538
2539 check_rendered_snippets_in_source(
2540 r"
2541fn expander_to_proc_macro() -> ProcMacro {
2542 ProcMacro {
2543 disabled: false,
2544 }
2545}
2546
2547struct ProcMacro {
2548 disabled: bool,
2549}",
2550 edit,
2551 snippets,
2552 expect![[r#"
2553 [
2554 SnippetTextEdit {
2555 range: Range {
2556 start: Position {
2557 line: 1,
2558 character: 4,
2559 },
2560 end: Position {
2561 line: 1,
2562 character: 13,
2563 },
2564 },
2565 new_text: "let",
2566 insert_text_format: None,
2567 annotation_id: None,
2568 },
2569 SnippetTextEdit {
2570 range: Range {
2571 start: Position {
2572 line: 1,
2573 character: 14,
2574 },
2575 end: Position {
2576 line: 3,
2577 character: 5,
2578 },
2579 },
2580 new_text: "$0disabled = false;\n ProcMacro {\n disabled,\n \\}",
2581 insert_text_format: Some(
2582 Snippet,
2583 ),
2584 annotation_id: None,
2585 },
2586 ]
2587 "#]],
2588 );
2589 }
2590
2591 #[test]
2592 fn snippet_rendering_tabstop_adjust_offset_added() {
2593 let mut edit = TextEdit::builder();
2595 edit.replace(TextRange::new(39.into(), 40.into()), "let".to_owned());
2596 edit.replace(
2597 TextRange::new(41.into(), 73.into()),
2598 "disabled = false;\n ProcMacro {\n disabled,\n }".to_owned(),
2599 );
2600 let edit = edit.finish();
2601 let snippets = SnippetEdit::new(vec![Snippet::Tabstop(43.into())]);
2602
2603 check_rendered_snippets_in_source(
2604 r"
2605fn expander_to_proc_macro() -> P {
2606 P {
2607 disabled: false,
2608 }
2609}
2610
2611struct P {
2612 disabled: bool,
2613}",
2614 edit,
2615 snippets,
2616 expect![[r#"
2617 [
2618 SnippetTextEdit {
2619 range: Range {
2620 start: Position {
2621 line: 1,
2622 character: 4,
2623 },
2624 end: Position {
2625 line: 1,
2626 character: 5,
2627 },
2628 },
2629 new_text: "let",
2630 insert_text_format: None,
2631 annotation_id: None,
2632 },
2633 SnippetTextEdit {
2634 range: Range {
2635 start: Position {
2636 line: 1,
2637 character: 6,
2638 },
2639 end: Position {
2640 line: 3,
2641 character: 5,
2642 },
2643 },
2644 new_text: "$0disabled = false;\n ProcMacro {\n disabled,\n \\}",
2645 insert_text_format: Some(
2646 Snippet,
2647 ),
2648 annotation_id: None,
2649 },
2650 ]
2651 "#]],
2652 );
2653 }
2654
2655 #[test]
2656 fn snippet_rendering_placeholder_adjust_offset_deleted() {
2657 let mut edit = TextEdit::builder();
2659 edit.replace(TextRange::new(47.into(), 56.into()), "let".to_owned());
2660 edit.replace(
2661 TextRange::new(57.into(), 89.into()),
2662 "disabled = false;\n ProcMacro {\n disabled,\n }".to_owned(),
2663 );
2664 let edit = edit.finish();
2665 let snippets =
2666 SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(51.into(), 59.into()))]);
2667
2668 check_rendered_snippets_in_source(
2669 r"
2670fn expander_to_proc_macro() -> ProcMacro {
2671 ProcMacro {
2672 disabled: false,
2673 }
2674}
2675
2676struct ProcMacro {
2677 disabled: bool,
2678}",
2679 edit,
2680 snippets,
2681 expect![[r#"
2682 [
2683 SnippetTextEdit {
2684 range: Range {
2685 start: Position {
2686 line: 1,
2687 character: 4,
2688 },
2689 end: Position {
2690 line: 1,
2691 character: 13,
2692 },
2693 },
2694 new_text: "let",
2695 insert_text_format: None,
2696 annotation_id: None,
2697 },
2698 SnippetTextEdit {
2699 range: Range {
2700 start: Position {
2701 line: 1,
2702 character: 14,
2703 },
2704 end: Position {
2705 line: 3,
2706 character: 5,
2707 },
2708 },
2709 new_text: "${0:disabled} = false;\n ProcMacro {\n disabled,\n \\}",
2710 insert_text_format: Some(
2711 Snippet,
2712 ),
2713 annotation_id: None,
2714 },
2715 ]
2716 "#]],
2717 );
2718 }
2719
2720 #[test]
2721 fn snippet_rendering_placeholder_adjust_offset_added() {
2722 let mut edit = TextEdit::builder();
2724 edit.replace(TextRange::new(39.into(), 40.into()), "let".to_owned());
2725 edit.replace(
2726 TextRange::new(41.into(), 73.into()),
2727 "disabled = false;\n ProcMacro {\n disabled,\n }".to_owned(),
2728 );
2729 let edit = edit.finish();
2730 let snippets =
2731 SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(43.into(), 51.into()))]);
2732
2733 check_rendered_snippets_in_source(
2734 r"
2735fn expander_to_proc_macro() -> P {
2736 P {
2737 disabled: false,
2738 }
2739}
2740
2741struct P {
2742 disabled: bool,
2743}",
2744 edit,
2745 snippets,
2746 expect![[r#"
2747 [
2748 SnippetTextEdit {
2749 range: Range {
2750 start: Position {
2751 line: 1,
2752 character: 4,
2753 },
2754 end: Position {
2755 line: 1,
2756 character: 5,
2757 },
2758 },
2759 new_text: "let",
2760 insert_text_format: None,
2761 annotation_id: None,
2762 },
2763 SnippetTextEdit {
2764 range: Range {
2765 start: Position {
2766 line: 1,
2767 character: 6,
2768 },
2769 end: Position {
2770 line: 3,
2771 character: 5,
2772 },
2773 },
2774 new_text: "${0:disabled} = false;\n ProcMacro {\n disabled,\n \\}",
2775 insert_text_format: Some(
2776 Snippet,
2777 ),
2778 annotation_id: None,
2779 },
2780 ]
2781 "#]],
2782 );
2783 }
2784
2785 #[test]
2786 fn snippet_rendering_tabstop_adjust_offset_between_text_edits() {
2787 let mut edit = TextEdit::builder();
2789 edit.replace(TextRange::new(47.into(), 56.into()), "let".to_owned());
2790 edit.replace(
2791 TextRange::new(58.into(), 90.into()),
2792 "disabled = false;\n ProcMacro {\n disabled,\n }".to_owned(),
2793 );
2794 let edit = edit.finish();
2795 let snippets = SnippetEdit::new(vec![Snippet::Tabstop(51.into())]);
2796
2797 check_rendered_snippets_in_source(
2799 r"
2800fn expander_to_proc_macro() -> ProcMacro {
2801 ProcMacro {
2802 disabled: false,
2803 }
2804}
2805
2806struct ProcMacro {
2807 disabled: bool,
2808}",
2809 edit,
2810 snippets,
2811 expect![[r#"
2812 [
2813 SnippetTextEdit {
2814 range: Range {
2815 start: Position {
2816 line: 1,
2817 character: 4,
2818 },
2819 end: Position {
2820 line: 1,
2821 character: 13,
2822 },
2823 },
2824 new_text: "let",
2825 insert_text_format: None,
2826 annotation_id: None,
2827 },
2828 SnippetTextEdit {
2829 range: Range {
2830 start: Position {
2831 line: 1,
2832 character: 14,
2833 },
2834 end: Position {
2835 line: 1,
2836 character: 14,
2837 },
2838 },
2839 new_text: "$0",
2840 insert_text_format: Some(
2841 Snippet,
2842 ),
2843 annotation_id: None,
2844 },
2845 SnippetTextEdit {
2846 range: Range {
2847 start: Position {
2848 line: 1,
2849 character: 15,
2850 },
2851 end: Position {
2852 line: 3,
2853 character: 5,
2854 },
2855 },
2856 new_text: "disabled = false;\n ProcMacro {\n disabled,\n }",
2857 insert_text_format: None,
2858 annotation_id: None,
2859 },
2860 ]
2861"#]],
2862 );
2863 }
2864
2865 #[test]
2866 fn snippet_rendering_tabstop_adjust_offset_after_text_edits() {
2867 let mut edit = TextEdit::builder();
2869 edit.replace(TextRange::new(47.into(), 56.into()), "let".to_owned());
2870 edit.replace(
2871 TextRange::new(57.into(), 89.into()),
2872 "disabled = false;\n ProcMacro {\n disabled,\n }".to_owned(),
2873 );
2874 let edit = edit.finish();
2875 let snippets = SnippetEdit::new(vec![Snippet::Tabstop(109.into())]);
2876
2877 check_rendered_snippets_in_source(
2878 r"
2879fn expander_to_proc_macro() -> ProcMacro {
2880 ProcMacro {
2881 disabled: false,
2882 }
2883}
2884
2885struct ProcMacro {
2886 disabled: bool,
2887}",
2888 edit,
2889 snippets,
2890 expect![[r#"
2891 [
2892 SnippetTextEdit {
2893 range: Range {
2894 start: Position {
2895 line: 1,
2896 character: 4,
2897 },
2898 end: Position {
2899 line: 1,
2900 character: 13,
2901 },
2902 },
2903 new_text: "let",
2904 insert_text_format: None,
2905 annotation_id: None,
2906 },
2907 SnippetTextEdit {
2908 range: Range {
2909 start: Position {
2910 line: 1,
2911 character: 14,
2912 },
2913 end: Position {
2914 line: 3,
2915 character: 5,
2916 },
2917 },
2918 new_text: "disabled = false;\n ProcMacro {\n disabled,\n }",
2919 insert_text_format: None,
2920 annotation_id: None,
2921 },
2922 SnippetTextEdit {
2923 range: Range {
2924 start: Position {
2925 line: 4,
2926 character: 0,
2927 },
2928 end: Position {
2929 line: 4,
2930 character: 0,
2931 },
2932 },
2933 new_text: "$0",
2934 insert_text_format: Some(
2935 Snippet,
2936 ),
2937 annotation_id: None,
2938 },
2939 ]
2940"#]],
2941 );
2942 }
2943
2944 #[test]
2945 fn snippet_rendering_handle_dos_line_endings() {
2946 let mut edit = TextEdit::builder();
2948 edit.insert(6.into(), "\n\n->".to_owned());
2949
2950 let edit = edit.finish();
2951 let snippets = SnippetEdit::new(vec![Snippet::Tabstop(10.into())]);
2952
2953 check_rendered_snippets_in_source(
2954 "yeah\r\n<-tabstop here",
2955 edit,
2956 snippets,
2957 expect![[r#"
2958 [
2959 SnippetTextEdit {
2960 range: Range {
2961 start: Position {
2962 line: 1,
2963 character: 0,
2964 },
2965 end: Position {
2966 line: 1,
2967 character: 0,
2968 },
2969 },
2970 new_text: "\r\n\r\n->$0",
2971 insert_text_format: Some(
2972 Snippet,
2973 ),
2974 annotation_id: None,
2975 },
2976 ]
2977 "#]],
2978 )
2979 }
2980
2981 #[test]
2983 #[cfg(target_os = "windows")]
2984 fn test_lowercase_drive_letter() {
2985 use paths::Utf8Path;
2986
2987 let url = url_from_abs_path(Utf8Path::new("C:\\Test").try_into().unwrap());
2988 assert_eq!(url.to_string(), "file:///c:/Test");
2989
2990 let url = url_from_abs_path(Utf8Path::new(r#"\\localhost\C$\my_dir"#).try_into().unwrap());
2991 assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
2992 }
2993}