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