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