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