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