]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/to_proto.rs
Merge #7878
[rust.git] / crates / rust-analyzer / src / to_proto.rs
1 //! Conversion of rust-analyzer specific types to lsp_types equivalents.
2 use std::{
3     path::{self, Path},
4     sync::atomic::{AtomicU32, Ordering},
5 };
6
7 use ide::{
8     Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind,
9     Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct,
10     HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, Markup, NavigationTarget,
11     ReferenceAccess, RenameError, Runnable, Severity, SourceChange, TextEdit, TextRange, TextSize,
12 };
13 use ide_db::SymbolKind;
14 use itertools::Itertools;
15 use serde_json::to_value;
16
17 use crate::{
18     cargo_target_spec::CargoTargetSpec,
19     global_state::GlobalStateSnapshot,
20     line_index::{LineEndings, LineIndex, OffsetEncoding},
21     lsp_ext, semantic_tokens, Result,
22 };
23
24 pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
25     let line_col = line_index.index.line_col(offset);
26     match line_index.encoding {
27         OffsetEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col),
28         OffsetEncoding::Utf16 => {
29             let line_col = line_index.index.to_utf16(line_col);
30             lsp_types::Position::new(line_col.line, line_col.col)
31         }
32     }
33 }
34
35 pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range {
36     let start = position(line_index, range.start());
37     let end = position(line_index, range.end());
38     lsp_types::Range::new(start, end)
39 }
40
41 pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
42     match symbol_kind {
43         SymbolKind::Function => lsp_types::SymbolKind::Function,
44         SymbolKind::Struct => lsp_types::SymbolKind::Struct,
45         SymbolKind::Enum => lsp_types::SymbolKind::Enum,
46         SymbolKind::Variant => lsp_types::SymbolKind::EnumMember,
47         SymbolKind::Trait => lsp_types::SymbolKind::Interface,
48         SymbolKind::Macro => lsp_types::SymbolKind::Function,
49         SymbolKind::Module => lsp_types::SymbolKind::Module,
50         SymbolKind::TypeAlias | SymbolKind::TypeParam => lsp_types::SymbolKind::TypeParameter,
51         SymbolKind::Field => lsp_types::SymbolKind::Field,
52         SymbolKind::Static => lsp_types::SymbolKind::Constant,
53         SymbolKind::Const => lsp_types::SymbolKind::Constant,
54         SymbolKind::ConstParam => lsp_types::SymbolKind::Constant,
55         SymbolKind::Impl => lsp_types::SymbolKind::Object,
56         SymbolKind::Local
57         | SymbolKind::SelfParam
58         | SymbolKind::LifetimeParam
59         | SymbolKind::ValueParam
60         | SymbolKind::Label => lsp_types::SymbolKind::Variable,
61         SymbolKind::Union => lsp_types::SymbolKind::Struct,
62     }
63 }
64
65 pub(crate) fn document_highlight_kind(
66     reference_access: ReferenceAccess,
67 ) -> lsp_types::DocumentHighlightKind {
68     match reference_access {
69         ReferenceAccess::Read => lsp_types::DocumentHighlightKind::Read,
70         ReferenceAccess::Write => lsp_types::DocumentHighlightKind::Write,
71     }
72 }
73
74 pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {
75     match severity {
76         Severity::Error => lsp_types::DiagnosticSeverity::Error,
77         Severity::WeakWarning => lsp_types::DiagnosticSeverity::Hint,
78     }
79 }
80
81 pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Documentation {
82     let value = crate::markdown::format_docs(documentation.as_str());
83     let markup_content = lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value };
84     lsp_types::Documentation::MarkupContent(markup_content)
85 }
86
87 pub(crate) fn insert_text_format(
88     insert_text_format: InsertTextFormat,
89 ) -> lsp_types::InsertTextFormat {
90     match insert_text_format {
91         InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet,
92         InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText,
93     }
94 }
95
96 pub(crate) fn completion_item_kind(
97     completion_item_kind: CompletionItemKind,
98 ) -> lsp_types::CompletionItemKind {
99     match completion_item_kind {
100         CompletionItemKind::Attribute => lsp_types::CompletionItemKind::EnumMember,
101         CompletionItemKind::Binding => lsp_types::CompletionItemKind::Variable,
102         CompletionItemKind::BuiltinType => lsp_types::CompletionItemKind::Struct,
103         CompletionItemKind::Keyword => lsp_types::CompletionItemKind::Keyword,
104         CompletionItemKind::Method => lsp_types::CompletionItemKind::Method,
105         CompletionItemKind::Snippet => lsp_types::CompletionItemKind::Snippet,
106         CompletionItemKind::UnresolvedReference => lsp_types::CompletionItemKind::Reference,
107         CompletionItemKind::SymbolKind(symbol) => match symbol {
108             SymbolKind::Const => lsp_types::CompletionItemKind::Constant,
109             SymbolKind::ConstParam => lsp_types::CompletionItemKind::TypeParameter,
110             SymbolKind::Enum => lsp_types::CompletionItemKind::Enum,
111             SymbolKind::Field => lsp_types::CompletionItemKind::Field,
112             SymbolKind::Function => lsp_types::CompletionItemKind::Function,
113             SymbolKind::Impl => lsp_types::CompletionItemKind::Text,
114             SymbolKind::Label => lsp_types::CompletionItemKind::Variable,
115             SymbolKind::LifetimeParam => lsp_types::CompletionItemKind::TypeParameter,
116             SymbolKind::Local => lsp_types::CompletionItemKind::Variable,
117             SymbolKind::Macro => lsp_types::CompletionItemKind::Method,
118             SymbolKind::Module => lsp_types::CompletionItemKind::Module,
119             SymbolKind::SelfParam => lsp_types::CompletionItemKind::Value,
120             SymbolKind::Static => lsp_types::CompletionItemKind::Value,
121             SymbolKind::Struct => lsp_types::CompletionItemKind::Struct,
122             SymbolKind::Trait => lsp_types::CompletionItemKind::Interface,
123             SymbolKind::TypeAlias => lsp_types::CompletionItemKind::Struct,
124             SymbolKind::TypeParam => lsp_types::CompletionItemKind::TypeParameter,
125             SymbolKind::Union => lsp_types::CompletionItemKind::Struct,
126             SymbolKind::ValueParam => lsp_types::CompletionItemKind::Value,
127             SymbolKind::Variant => lsp_types::CompletionItemKind::EnumMember,
128         },
129     }
130 }
131
132 pub(crate) fn text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::TextEdit {
133     let range = range(line_index, indel.delete);
134     let new_text = match line_index.endings {
135         LineEndings::Unix => indel.insert,
136         LineEndings::Dos => indel.insert.replace('\n', "\r\n"),
137     };
138     lsp_types::TextEdit { range, new_text }
139 }
140
141 pub(crate) fn snippet_text_edit(
142     line_index: &LineIndex,
143     is_snippet: bool,
144     indel: Indel,
145 ) -> lsp_ext::SnippetTextEdit {
146     let text_edit = text_edit(line_index, indel);
147     let insert_text_format =
148         if is_snippet { Some(lsp_types::InsertTextFormat::Snippet) } else { None };
149     lsp_ext::SnippetTextEdit {
150         range: text_edit.range,
151         new_text: text_edit.new_text,
152         insert_text_format,
153     }
154 }
155
156 pub(crate) fn text_edit_vec(
157     line_index: &LineIndex,
158     text_edit: TextEdit,
159 ) -> Vec<lsp_types::TextEdit> {
160     text_edit.into_iter().map(|indel| self::text_edit(line_index, indel)).collect()
161 }
162
163 pub(crate) fn snippet_text_edit_vec(
164     line_index: &LineIndex,
165     is_snippet: bool,
166     text_edit: TextEdit,
167 ) -> Vec<lsp_ext::SnippetTextEdit> {
168     text_edit
169         .into_iter()
170         .map(|indel| self::snippet_text_edit(line_index, is_snippet, indel))
171         .collect()
172 }
173
174 pub(crate) fn completion_item(
175     line_index: &LineIndex,
176     item: CompletionItem,
177 ) -> Vec<lsp_types::CompletionItem> {
178     let mut additional_text_edits = Vec::new();
179     let mut text_edit = None;
180     // LSP does not allow arbitrary edits in completion, so we have to do a
181     // non-trivial mapping here.
182     let source_range = item.source_range();
183     for indel in item.text_edit().iter() {
184         if indel.delete.contains_range(source_range) {
185             text_edit = Some(if indel.delete == source_range {
186                 self::text_edit(line_index, indel.clone())
187             } else {
188                 assert!(source_range.end() == indel.delete.end());
189                 let range1 = TextRange::new(indel.delete.start(), source_range.start());
190                 let range2 = source_range;
191                 let indel1 = Indel::replace(range1, String::new());
192                 let indel2 = Indel::replace(range2, indel.insert.clone());
193                 additional_text_edits.push(self::text_edit(line_index, indel1));
194                 self::text_edit(line_index, indel2)
195             })
196         } else {
197             assert!(source_range.intersect(indel.delete).is_none());
198             let text_edit = self::text_edit(line_index, indel.clone());
199             additional_text_edits.push(text_edit);
200         }
201     }
202     let text_edit = text_edit.unwrap();
203
204     let mut lsp_item = lsp_types::CompletionItem {
205         label: item.label().to_string(),
206         detail: item.detail().map(|it| it.to_string()),
207         filter_text: Some(item.lookup().to_string()),
208         kind: item.kind().map(completion_item_kind),
209         text_edit: Some(text_edit.into()),
210         additional_text_edits: Some(additional_text_edits),
211         documentation: item.documentation().map(documentation),
212         deprecated: Some(item.deprecated()),
213         ..Default::default()
214     };
215
216     if item.relevance().is_relevant() {
217         lsp_item.preselect = Some(true);
218         // HACK: sort preselect items first
219         lsp_item.sort_text = Some(format!(" {}", item.label()));
220     }
221
222     if item.deprecated() {
223         lsp_item.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
224     }
225
226     if item.trigger_call_info() {
227         lsp_item.command = Some(command::trigger_parameter_hints());
228     }
229
230     let mut res = match item.ref_match() {
231         Some(mutability) => {
232             let mut lsp_item_with_ref = lsp_item.clone();
233             lsp_item.preselect = Some(true);
234             lsp_item.sort_text = Some(format!(" {}", item.label()));
235             lsp_item_with_ref.label =
236                 format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label);
237             if let Some(lsp_types::CompletionTextEdit::Edit(it)) = &mut lsp_item_with_ref.text_edit
238             {
239                 it.new_text = format!("&{}{}", mutability.as_keyword_for_ref(), it.new_text);
240             }
241             vec![lsp_item_with_ref, lsp_item]
242         }
243         None => vec![lsp_item],
244     };
245
246     for lsp_item in res.iter_mut() {
247         lsp_item.insert_text_format = Some(insert_text_format(item.insert_text_format()));
248     }
249     res
250 }
251
252 pub(crate) fn signature_help(
253     call_info: CallInfo,
254     concise: bool,
255     label_offsets: bool,
256 ) -> lsp_types::SignatureHelp {
257     let (label, parameters) = match (concise, label_offsets) {
258         (_, false) => {
259             let params = call_info
260                 .parameter_labels()
261                 .map(|label| lsp_types::ParameterInformation {
262                     label: lsp_types::ParameterLabel::Simple(label.to_string()),
263                     documentation: None,
264                 })
265                 .collect::<Vec<_>>();
266             let label =
267                 if concise { call_info.parameter_labels().join(", ") } else { call_info.signature };
268             (label, params)
269         }
270         (false, true) => {
271             let params = call_info
272                 .parameter_ranges()
273                 .iter()
274                 .map(|it| [u32::from(it.start()).into(), u32::from(it.end()).into()])
275                 .map(|label_offsets| lsp_types::ParameterInformation {
276                     label: lsp_types::ParameterLabel::LabelOffsets(label_offsets),
277                     documentation: None,
278                 })
279                 .collect::<Vec<_>>();
280             (call_info.signature, params)
281         }
282         (true, true) => {
283             let mut params = Vec::new();
284             let mut label = String::new();
285             let mut first = true;
286             for param in call_info.parameter_labels() {
287                 if !first {
288                     label.push_str(", ");
289                 }
290                 first = false;
291                 let start = label.len() as u32;
292                 label.push_str(param);
293                 let end = label.len() as u32;
294                 params.push(lsp_types::ParameterInformation {
295                     label: lsp_types::ParameterLabel::LabelOffsets([start, end]),
296                     documentation: None,
297                 });
298             }
299
300             (label, params)
301         }
302     };
303
304     let documentation = if concise {
305         None
306     } else {
307         call_info.doc.map(|doc| {
308             lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
309                 kind: lsp_types::MarkupKind::Markdown,
310                 value: doc,
311             })
312         })
313     };
314
315     let active_parameter = call_info.active_parameter.map(|it| it as u32);
316
317     let signature = lsp_types::SignatureInformation {
318         label,
319         documentation,
320         parameters: Some(parameters),
321         active_parameter,
322     };
323     lsp_types::SignatureHelp {
324         signatures: vec![signature],
325         active_signature: None,
326         active_parameter,
327     }
328 }
329
330 pub(crate) fn inlay_hint(line_index: &LineIndex, inlay_hint: InlayHint) -> lsp_ext::InlayHint {
331     lsp_ext::InlayHint {
332         label: inlay_hint.label.to_string(),
333         range: range(line_index, inlay_hint.range),
334         kind: match inlay_hint.kind {
335             InlayKind::ParameterHint => lsp_ext::InlayKind::ParameterHint,
336             InlayKind::TypeHint => lsp_ext::InlayKind::TypeHint,
337             InlayKind::ChainingHint => lsp_ext::InlayKind::ChainingHint,
338         },
339     }
340 }
341
342 static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);
343
344 pub(crate) fn semantic_tokens(
345     text: &str,
346     line_index: &LineIndex,
347     highlights: Vec<HlRange>,
348 ) -> lsp_types::SemanticTokens {
349     let id = TOKEN_RESULT_COUNTER.fetch_add(1, Ordering::SeqCst).to_string();
350     let mut builder = semantic_tokens::SemanticTokensBuilder::new(id);
351
352     for highlight_range in highlights {
353         if highlight_range.highlight.is_empty() {
354             continue;
355         }
356         let (type_, mods) = semantic_token_type_and_modifiers(highlight_range.highlight);
357         let token_index = semantic_tokens::type_index(type_);
358         let modifier_bitset = mods.0;
359
360         for mut text_range in line_index.index.lines(highlight_range.range) {
361             if text[text_range].ends_with('\n') {
362                 text_range =
363                     TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n'));
364             }
365             let range = range(&line_index, text_range);
366             builder.push(range, token_index, modifier_bitset);
367         }
368     }
369
370     builder.build()
371 }
372
373 pub(crate) fn semantic_token_delta(
374     previous: &lsp_types::SemanticTokens,
375     current: &lsp_types::SemanticTokens,
376 ) -> lsp_types::SemanticTokensDelta {
377     let result_id = current.result_id.clone();
378     let edits = semantic_tokens::diff_tokens(&previous.data, &current.data);
379     lsp_types::SemanticTokensDelta { result_id, edits }
380 }
381
382 fn semantic_token_type_and_modifiers(
383     highlight: Highlight,
384 ) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet) {
385     let mut mods = semantic_tokens::ModifierSet::default();
386     let type_ = match highlight.tag {
387         HlTag::Symbol(symbol) => match symbol {
388             SymbolKind::Module => lsp_types::SemanticTokenType::NAMESPACE,
389             SymbolKind::Impl => lsp_types::SemanticTokenType::TYPE,
390             SymbolKind::Field => lsp_types::SemanticTokenType::PROPERTY,
391             SymbolKind::TypeParam => lsp_types::SemanticTokenType::TYPE_PARAMETER,
392             SymbolKind::ConstParam => semantic_tokens::CONST_PARAMETER,
393             SymbolKind::LifetimeParam => semantic_tokens::LIFETIME,
394             SymbolKind::Label => semantic_tokens::LABEL,
395             SymbolKind::ValueParam => lsp_types::SemanticTokenType::PARAMETER,
396             SymbolKind::SelfParam => semantic_tokens::SELF_KEYWORD,
397             SymbolKind::Local => lsp_types::SemanticTokenType::VARIABLE,
398             SymbolKind::Function => {
399                 if highlight.mods.contains(HlMod::Associated) {
400                     lsp_types::SemanticTokenType::METHOD
401                 } else {
402                     lsp_types::SemanticTokenType::FUNCTION
403                 }
404             }
405             SymbolKind::Const => {
406                 mods |= semantic_tokens::CONSTANT;
407                 mods |= lsp_types::SemanticTokenModifier::STATIC;
408                 lsp_types::SemanticTokenType::VARIABLE
409             }
410             SymbolKind::Static => {
411                 mods |= lsp_types::SemanticTokenModifier::STATIC;
412                 lsp_types::SemanticTokenType::VARIABLE
413             }
414             SymbolKind::Struct => lsp_types::SemanticTokenType::STRUCT,
415             SymbolKind::Enum => lsp_types::SemanticTokenType::ENUM,
416             SymbolKind::Variant => lsp_types::SemanticTokenType::ENUM_MEMBER,
417             SymbolKind::Union => semantic_tokens::UNION,
418             SymbolKind::TypeAlias => semantic_tokens::TYPE_ALIAS,
419             SymbolKind::Trait => lsp_types::SemanticTokenType::INTERFACE,
420             SymbolKind::Macro => lsp_types::SemanticTokenType::MACRO,
421         },
422         HlTag::BuiltinType => semantic_tokens::BUILTIN_TYPE,
423         HlTag::None => semantic_tokens::GENERIC,
424         HlTag::ByteLiteral | HlTag::NumericLiteral => lsp_types::SemanticTokenType::NUMBER,
425         HlTag::BoolLiteral => semantic_tokens::BOOLEAN,
426         HlTag::StringLiteral => lsp_types::SemanticTokenType::STRING,
427         HlTag::CharLiteral => semantic_tokens::CHAR_LITERAL,
428         HlTag::Comment => lsp_types::SemanticTokenType::COMMENT,
429         HlTag::Attribute => semantic_tokens::ATTRIBUTE,
430         HlTag::Keyword => lsp_types::SemanticTokenType::KEYWORD,
431         HlTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE,
432         HlTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
433         HlTag::Operator => lsp_types::SemanticTokenType::OPERATOR,
434         HlTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
435         HlTag::Punctuation(punct) => match punct {
436             HlPunct::Bracket => semantic_tokens::BRACKET,
437             HlPunct::Brace => semantic_tokens::BRACE,
438             HlPunct::Parenthesis => semantic_tokens::PARENTHESIS,
439             HlPunct::Angle => semantic_tokens::ANGLE,
440             HlPunct::Comma => semantic_tokens::COMMA,
441             HlPunct::Dot => semantic_tokens::DOT,
442             HlPunct::Colon => semantic_tokens::COLON,
443             HlPunct::Semi => semantic_tokens::SEMICOLON,
444             HlPunct::Other => semantic_tokens::PUNCTUATION,
445         },
446     };
447
448     for modifier in highlight.mods.iter() {
449         let modifier = match modifier {
450             HlMod::Attribute => semantic_tokens::ATTRIBUTE_MODIFIER,
451             HlMod::Definition => lsp_types::SemanticTokenModifier::DECLARATION,
452             HlMod::Documentation => lsp_types::SemanticTokenModifier::DOCUMENTATION,
453             HlMod::Injected => semantic_tokens::INJECTED,
454             HlMod::ControlFlow => semantic_tokens::CONTROL_FLOW,
455             HlMod::Mutable => semantic_tokens::MUTABLE,
456             HlMod::Consuming => semantic_tokens::CONSUMING,
457             HlMod::Unsafe => semantic_tokens::UNSAFE,
458             HlMod::Callable => semantic_tokens::CALLABLE,
459             HlMod::Static => lsp_types::SemanticTokenModifier::STATIC,
460             HlMod::Associated => continue,
461         };
462         mods |= modifier;
463     }
464
465     (type_, mods)
466 }
467
468 pub(crate) fn folding_range(
469     text: &str,
470     line_index: &LineIndex,
471     line_folding_only: bool,
472     fold: Fold,
473 ) -> lsp_types::FoldingRange {
474     let kind = match fold.kind {
475         FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
476         FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
477         FoldKind::Mods | FoldKind::Block | FoldKind::ArgList | FoldKind::Region => None,
478     };
479
480     let range = range(line_index, fold.range);
481
482     if line_folding_only {
483         // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
484         // even if it contains text not in the folding range. To prevent that we exclude
485         // range.end.line from the folding region if there is more text after range.end
486         // on the same line.
487         let has_more_text_on_end_line = text[TextRange::new(fold.range.end(), TextSize::of(text))]
488             .chars()
489             .take_while(|it| *it != '\n')
490             .any(|it| !it.is_whitespace());
491
492         let end_line = if has_more_text_on_end_line {
493             range.end.line.saturating_sub(1)
494         } else {
495             range.end.line
496         };
497
498         lsp_types::FoldingRange {
499             start_line: range.start.line,
500             start_character: None,
501             end_line,
502             end_character: None,
503             kind,
504         }
505     } else {
506         lsp_types::FoldingRange {
507             start_line: range.start.line,
508             start_character: Some(range.start.character),
509             end_line: range.end.line,
510             end_character: Some(range.end.character),
511             kind,
512         }
513     }
514 }
515
516 pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url {
517     snap.file_id_to_url(file_id)
518 }
519
520 /// Returns a `Url` object from a given path, will lowercase drive letters if present.
521 /// This will only happen when processing windows paths.
522 ///
523 /// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
524 pub(crate) fn url_from_abs_path(path: &Path) -> lsp_types::Url {
525     assert!(path.is_absolute());
526     let url = lsp_types::Url::from_file_path(path).unwrap();
527     match path.components().next() {
528         Some(path::Component::Prefix(prefix))
529             if matches!(prefix.kind(), path::Prefix::Disk(_) | path::Prefix::VerbatimDisk(_)) =>
530         {
531             // Need to lowercase driver letter
532         }
533         _ => return url,
534     }
535
536     let driver_letter_range = {
537         let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() {
538             Some(it) => it,
539             None => return url,
540         };
541         let start = scheme.len() + ':'.len_utf8();
542         start..(start + drive_letter.len())
543     };
544
545     // Note: lowercasing the `path` itself doesn't help, the `Url::parse`
546     // machinery *also* canonicalizes the drive letter. So, just massage the
547     // string in place.
548     let mut url = url.into_string();
549     url[driver_letter_range].make_ascii_lowercase();
550     lsp_types::Url::parse(&url).unwrap()
551 }
552
553 pub(crate) fn optional_versioned_text_document_identifier(
554     snap: &GlobalStateSnapshot,
555     file_id: FileId,
556 ) -> lsp_types::OptionalVersionedTextDocumentIdentifier {
557     let url = url(snap, file_id);
558     let version = snap.url_file_version(&url);
559     lsp_types::OptionalVersionedTextDocumentIdentifier { uri: url, version }
560 }
561
562 pub(crate) fn location(
563     snap: &GlobalStateSnapshot,
564     frange: FileRange,
565 ) -> Result<lsp_types::Location> {
566     let url = url(snap, frange.file_id);
567     let line_index = snap.file_line_index(frange.file_id)?;
568     let range = range(&line_index, frange.range);
569     let loc = lsp_types::Location::new(url, range);
570     Ok(loc)
571 }
572
573 /// Perefer using `location_link`, if the client has the cap.
574 pub(crate) fn location_from_nav(
575     snap: &GlobalStateSnapshot,
576     nav: NavigationTarget,
577 ) -> Result<lsp_types::Location> {
578     let url = url(snap, nav.file_id);
579     let line_index = snap.file_line_index(nav.file_id)?;
580     let range = range(&line_index, nav.full_range);
581     let loc = lsp_types::Location::new(url, range);
582     Ok(loc)
583 }
584
585 pub(crate) fn location_link(
586     snap: &GlobalStateSnapshot,
587     src: Option<FileRange>,
588     target: NavigationTarget,
589 ) -> Result<lsp_types::LocationLink> {
590     let origin_selection_range = match src {
591         Some(src) => {
592             let line_index = snap.file_line_index(src.file_id)?;
593             let range = range(&line_index, src.range);
594             Some(range)
595         }
596         None => None,
597     };
598     let (target_uri, target_range, target_selection_range) = location_info(snap, target)?;
599     let res = lsp_types::LocationLink {
600         origin_selection_range,
601         target_uri,
602         target_range,
603         target_selection_range,
604     };
605     Ok(res)
606 }
607
608 fn location_info(
609     snap: &GlobalStateSnapshot,
610     target: NavigationTarget,
611 ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> {
612     let line_index = snap.file_line_index(target.file_id)?;
613
614     let target_uri = url(snap, target.file_id);
615     let target_range = range(&line_index, target.full_range);
616     let target_selection_range =
617         target.focus_range.map(|it| range(&line_index, it)).unwrap_or(target_range);
618     Ok((target_uri, target_range, target_selection_range))
619 }
620
621 pub(crate) fn goto_definition_response(
622     snap: &GlobalStateSnapshot,
623     src: Option<FileRange>,
624     targets: Vec<NavigationTarget>,
625 ) -> Result<lsp_types::GotoDefinitionResponse> {
626     if snap.config.location_link() {
627         let links = targets
628             .into_iter()
629             .map(|nav| location_link(snap, src, nav))
630             .collect::<Result<Vec<_>>>()?;
631         Ok(links.into())
632     } else {
633         let locations = targets
634             .into_iter()
635             .map(|nav| {
636                 location(snap, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
637             })
638             .collect::<Result<Vec<_>>>()?;
639         Ok(locations.into())
640     }
641 }
642
643 pub(crate) fn snippet_text_document_edit(
644     snap: &GlobalStateSnapshot,
645     is_snippet: bool,
646     file_id: FileId,
647     edit: TextEdit,
648 ) -> Result<lsp_ext::SnippetTextDocumentEdit> {
649     let text_document = optional_versioned_text_document_identifier(snap, file_id);
650     let line_index = snap.file_line_index(file_id)?;
651     let edits = edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect();
652     Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
653 }
654
655 pub(crate) fn snippet_text_document_ops(
656     snap: &GlobalStateSnapshot,
657     file_system_edit: FileSystemEdit,
658 ) -> Vec<lsp_ext::SnippetDocumentChangeOperation> {
659     let mut ops = Vec::new();
660     match file_system_edit {
661         FileSystemEdit::CreateFile { dst, initial_contents } => {
662             let uri = snap.anchored_path(&dst);
663             let create_file = lsp_types::ResourceOp::Create(lsp_types::CreateFile {
664                 uri: uri.clone(),
665                 options: None,
666                 annotation_id: None,
667             });
668             ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(create_file));
669             if !initial_contents.is_empty() {
670                 let text_document =
671                     lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version: None };
672                 let text_edit = lsp_ext::SnippetTextEdit {
673                     range: lsp_types::Range::default(),
674                     new_text: initial_contents,
675                     insert_text_format: Some(lsp_types::InsertTextFormat::PlainText),
676                 };
677                 let edit_file =
678                     lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] };
679                 ops.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit_file));
680             }
681         }
682         FileSystemEdit::MoveFile { src, dst } => {
683             let old_uri = snap.file_id_to_url(src);
684             let new_uri = snap.anchored_path(&dst);
685             let rename_file = lsp_types::ResourceOp::Rename(lsp_types::RenameFile {
686                 old_uri,
687                 new_uri,
688                 options: None,
689                 annotation_id: None,
690             });
691             ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(rename_file))
692         }
693     }
694     ops
695 }
696
697 pub(crate) fn snippet_workspace_edit(
698     snap: &GlobalStateSnapshot,
699     source_change: SourceChange,
700 ) -> Result<lsp_ext::SnippetWorkspaceEdit> {
701     let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
702     for op in source_change.file_system_edits {
703         let ops = snippet_text_document_ops(snap, op);
704         document_changes.extend_from_slice(&ops);
705     }
706     for (file_id, edit) in source_change.source_file_edits {
707         let edit = snippet_text_document_edit(&snap, source_change.is_snippet, file_id, edit)?;
708         document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
709     }
710     let workspace_edit =
711         lsp_ext::SnippetWorkspaceEdit { changes: None, document_changes: Some(document_changes) };
712     Ok(workspace_edit)
713 }
714
715 pub(crate) fn workspace_edit(
716     snap: &GlobalStateSnapshot,
717     source_change: SourceChange,
718 ) -> Result<lsp_types::WorkspaceEdit> {
719     assert!(!source_change.is_snippet);
720     snippet_workspace_edit(snap, source_change).map(|it| it.into())
721 }
722
723 impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
724     fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::WorkspaceEdit {
725         lsp_types::WorkspaceEdit {
726             changes: None,
727             document_changes: snippet_workspace_edit.document_changes.map(|changes| {
728                 lsp_types::DocumentChanges::Operations(
729                     changes
730                         .into_iter()
731                         .map(|change| match change {
732                             lsp_ext::SnippetDocumentChangeOperation::Op(op) => {
733                                 lsp_types::DocumentChangeOperation::Op(op)
734                             }
735                             lsp_ext::SnippetDocumentChangeOperation::Edit(edit) => {
736                                 lsp_types::DocumentChangeOperation::Edit(
737                                     lsp_types::TextDocumentEdit {
738                                         text_document: edit.text_document,
739                                         edits: edit
740                                             .edits
741                                             .into_iter()
742                                             .map(|edit| {
743                                                 lsp_types::OneOf::Left(lsp_types::TextEdit {
744                                                     range: edit.range,
745                                                     new_text: edit.new_text,
746                                                 })
747                                             })
748                                             .collect(),
749                                     },
750                                 )
751                             }
752                         })
753                         .collect(),
754                 )
755             }),
756             change_annotations: None,
757         }
758     }
759 }
760
761 pub(crate) fn call_hierarchy_item(
762     snap: &GlobalStateSnapshot,
763     target: NavigationTarget,
764 ) -> Result<lsp_types::CallHierarchyItem> {
765     let name = target.name.to_string();
766     let detail = target.description.clone();
767     let kind = target.kind.map(symbol_kind).unwrap_or(lsp_types::SymbolKind::Function);
768     let (uri, range, selection_range) = location_info(snap, target)?;
769     Ok(lsp_types::CallHierarchyItem {
770         name,
771         kind,
772         tags: None,
773         detail,
774         uri,
775         range,
776         selection_range,
777         data: None,
778     })
779 }
780
781 pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
782     match kind {
783         AssistKind::None | AssistKind::Generate => lsp_types::CodeActionKind::EMPTY,
784         AssistKind::QuickFix => lsp_types::CodeActionKind::QUICKFIX,
785         AssistKind::Refactor => lsp_types::CodeActionKind::REFACTOR,
786         AssistKind::RefactorExtract => lsp_types::CodeActionKind::REFACTOR_EXTRACT,
787         AssistKind::RefactorInline => lsp_types::CodeActionKind::REFACTOR_INLINE,
788         AssistKind::RefactorRewrite => lsp_types::CodeActionKind::REFACTOR_REWRITE,
789     }
790 }
791
792 pub(crate) fn unresolved_code_action(
793     snap: &GlobalStateSnapshot,
794     code_action_params: lsp_types::CodeActionParams,
795     assist: Assist,
796     index: usize,
797 ) -> Result<lsp_ext::CodeAction> {
798     assert!(assist.source_change.is_none());
799     let res = lsp_ext::CodeAction {
800         title: assist.label.to_string(),
801         group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
802         kind: Some(code_action_kind(assist.id.1)),
803         edit: None,
804         is_preferred: None,
805         data: Some(lsp_ext::CodeActionData {
806             id: format!("{}:{}", assist.id.0, index.to_string()),
807             code_action_params,
808         }),
809     };
810     Ok(res)
811 }
812
813 pub(crate) fn resolved_code_action(
814     snap: &GlobalStateSnapshot,
815     assist: Assist,
816 ) -> Result<lsp_ext::CodeAction> {
817     let change = assist.source_change.unwrap();
818     let res = lsp_ext::CodeAction {
819         edit: Some(snippet_workspace_edit(snap, change)?),
820         title: assist.label.to_string(),
821         group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
822         kind: Some(code_action_kind(assist.id.1)),
823         is_preferred: None,
824         data: None,
825     };
826     Ok(res)
827 }
828
829 pub(crate) fn runnable(
830     snap: &GlobalStateSnapshot,
831     file_id: FileId,
832     runnable: Runnable,
833 ) -> Result<lsp_ext::Runnable> {
834     let config = snap.config.runnables();
835     let spec = CargoTargetSpec::for_file(snap, file_id)?;
836     let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
837     let target = spec.as_ref().map(|s| s.target.clone());
838     let (cargo_args, executable_args) =
839         CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg)?;
840     let label = runnable.label(target);
841     let location = location_link(snap, None, runnable.nav)?;
842
843     Ok(lsp_ext::Runnable {
844         label,
845         location: Some(location),
846         kind: lsp_ext::RunnableKind::Cargo,
847         args: lsp_ext::CargoRunnable {
848             workspace_root: workspace_root.map(|it| it.into()),
849             override_cargo: config.override_cargo,
850             cargo_args,
851             cargo_extra_args: config.cargo_extra_args,
852             executable_args,
853             expect_test: None,
854         },
855     })
856 }
857
858 pub(crate) fn code_lens(
859     snap: &GlobalStateSnapshot,
860     annotation: Annotation,
861 ) -> Result<lsp_types::CodeLens> {
862     match annotation.kind {
863         AnnotationKind::Runnable { debug, runnable: run } => {
864             let line_index = snap.file_line_index(run.nav.file_id)?;
865             let annotation_range = range(&line_index, annotation.range);
866
867             let action = run.action();
868             let r = runnable(&snap, run.nav.file_id, run)?;
869
870             let command = if debug {
871                 command::debug_single(&r)
872             } else {
873                 let title = action.run_title.to_string();
874                 command::run_single(&r, &title)
875             };
876
877             Ok(lsp_types::CodeLens { range: annotation_range, command: Some(command), data: None })
878         }
879         AnnotationKind::HasImpls { position: file_position, data } => {
880             let line_index = snap.file_line_index(file_position.file_id)?;
881             let annotation_range = range(&line_index, annotation.range);
882             let url = url(snap, file_position.file_id);
883
884             let position = position(&line_index, file_position.offset);
885
886             let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
887
888             let doc_pos = lsp_types::TextDocumentPositionParams::new(id, position);
889
890             let goto_params = lsp_types::request::GotoImplementationParams {
891                 text_document_position_params: doc_pos,
892                 work_done_progress_params: Default::default(),
893                 partial_result_params: Default::default(),
894             };
895
896             let command = data.map(|ranges| {
897                 let locations: Vec<lsp_types::Location> = ranges
898                     .into_iter()
899                     .filter_map(|target| {
900                         location(
901                             snap,
902                             FileRange { file_id: target.file_id, range: target.full_range },
903                         )
904                         .ok()
905                     })
906                     .collect();
907
908                 command::show_references(
909                     implementation_title(locations.len()),
910                     &url,
911                     position,
912                     locations,
913                 )
914             });
915
916             Ok(lsp_types::CodeLens {
917                 range: annotation_range,
918                 command,
919                 data: Some(to_value(lsp_ext::CodeLensResolveData::Impls(goto_params)).unwrap()),
920             })
921         }
922         AnnotationKind::HasReferences { position: file_position, data } => {
923             let line_index = snap.file_line_index(file_position.file_id)?;
924             let annotation_range = range(&line_index, annotation.range);
925             let url = url(snap, file_position.file_id);
926
927             let position = position(&line_index, file_position.offset);
928
929             let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
930
931             let doc_pos = lsp_types::TextDocumentPositionParams::new(id, position);
932
933             let command = data.map(|ranges| {
934                 let locations: Vec<lsp_types::Location> =
935                     ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect();
936
937                 command::show_references(
938                     reference_title(locations.len()),
939                     &url,
940                     position,
941                     locations,
942                 )
943             });
944
945             Ok(lsp_types::CodeLens {
946                 range: annotation_range,
947                 command,
948                 data: Some(to_value(lsp_ext::CodeLensResolveData::References(doc_pos)).unwrap()),
949             })
950         }
951     }
952 }
953
954 pub(crate) mod command {
955     use ide::{FileRange, NavigationTarget};
956     use serde_json::to_value;
957
958     use crate::{
959         global_state::GlobalStateSnapshot,
960         lsp_ext,
961         to_proto::{location, location_link},
962     };
963
964     pub(crate) fn show_references(
965         title: String,
966         uri: &lsp_types::Url,
967         position: lsp_types::Position,
968         locations: Vec<lsp_types::Location>,
969     ) -> lsp_types::Command {
970         // We cannot use the 'editor.action.showReferences' command directly
971         // because that command requires vscode types which we convert in the handler
972         // on the client side.
973
974         lsp_types::Command {
975             title,
976             command: "rust-analyzer.showReferences".into(),
977             arguments: Some(vec![
978                 to_value(uri).unwrap(),
979                 to_value(position).unwrap(),
980                 to_value(locations).unwrap(),
981             ]),
982         }
983     }
984
985     pub(crate) fn run_single(runnable: &lsp_ext::Runnable, title: &str) -> lsp_types::Command {
986         lsp_types::Command {
987             title: title.to_string(),
988             command: "rust-analyzer.runSingle".into(),
989             arguments: Some(vec![to_value(runnable).unwrap()]),
990         }
991     }
992
993     pub(crate) fn debug_single(runnable: &lsp_ext::Runnable) -> lsp_types::Command {
994         lsp_types::Command {
995             title: "Debug".into(),
996             command: "rust-analyzer.debugSingle".into(),
997             arguments: Some(vec![to_value(runnable).unwrap()]),
998         }
999     }
1000
1001     pub(crate) fn goto_location(
1002         snap: &GlobalStateSnapshot,
1003         nav: &NavigationTarget,
1004     ) -> Option<lsp_types::Command> {
1005         let value = if snap.config.location_link() {
1006             let link = location_link(snap, None, nav.clone()).ok()?;
1007             to_value(link).ok()?
1008         } else {
1009             let range = FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() };
1010             let location = location(snap, range).ok()?;
1011             to_value(location).ok()?
1012         };
1013
1014         Some(lsp_types::Command {
1015             title: nav.name.to_string(),
1016             command: "rust-analyzer.gotoLocation".into(),
1017             arguments: Some(vec![value]),
1018         })
1019     }
1020
1021     pub(crate) fn trigger_parameter_hints() -> lsp_types::Command {
1022         lsp_types::Command {
1023             title: "triggerParameterHints".into(),
1024             command: "editor.action.triggerParameterHints".into(),
1025             arguments: None,
1026         }
1027     }
1028 }
1029
1030 pub(crate) fn implementation_title(count: usize) -> String {
1031     if count == 1 {
1032         "1 implementation".into()
1033     } else {
1034         format!("{} implementations", count)
1035     }
1036 }
1037
1038 pub(crate) fn reference_title(count: usize) -> String {
1039     if count == 1 {
1040         "1 reference".into()
1041     } else {
1042         format!("{} references", count)
1043     }
1044 }
1045
1046 pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent {
1047     let value = crate::markdown::format_docs(markup.as_str());
1048     lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value }
1049 }
1050
1051 pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
1052     crate::LspError { code: lsp_server::ErrorCode::InvalidParams as i32, message: err.to_string() }
1053 }
1054
1055 #[cfg(test)]
1056 mod tests {
1057     use std::sync::Arc;
1058
1059     use hir::PrefixKind;
1060     use ide::Analysis;
1061     use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
1062
1063     use super::*;
1064
1065     #[test]
1066     fn test_completion_with_ref() {
1067         let fixture = r#"
1068         struct Foo;
1069         fn foo(arg: &Foo) {}
1070         fn main() {
1071             let arg = Foo;
1072             foo($0)
1073         }"#;
1074
1075         let (offset, text) = test_utils::extract_offset(fixture);
1076         let line_index = LineIndex {
1077             index: Arc::new(ide::LineIndex::new(&text)),
1078             endings: LineEndings::Unix,
1079             encoding: OffsetEncoding::Utf16,
1080         };
1081         let (analysis, file_id) = Analysis::from_single_file(text);
1082         let completions: Vec<(String, Option<String>)> = analysis
1083             .completions(
1084                 &ide::CompletionConfig {
1085                     enable_postfix_completions: true,
1086                     enable_imports_on_the_fly: true,
1087                     add_call_parenthesis: true,
1088                     add_call_argument_snippets: true,
1089                     snippet_cap: SnippetCap::new(true),
1090                     insert_use: InsertUseConfig {
1091                         merge: None,
1092                         prefix_kind: PrefixKind::Plain,
1093                         group: true,
1094                     },
1095                 },
1096                 ide_db::base_db::FilePosition { file_id, offset },
1097             )
1098             .unwrap()
1099             .unwrap()
1100             .into_iter()
1101             .filter(|c| c.label().ends_with("arg"))
1102             .map(|c| completion_item(&line_index, c))
1103             .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text)))
1104             .collect();
1105         expect_test::expect![[r#"
1106             [
1107                 (
1108                     "&arg",
1109                     Some(
1110                         " arg",
1111                     ),
1112                 ),
1113                 (
1114                     "arg",
1115                     Some(
1116                         " arg",
1117                     ),
1118                 ),
1119             ]
1120         "#]]
1121         .assert_debug_eq(&completions);
1122     }
1123
1124     #[test]
1125     fn conv_fold_line_folding_only_fixup() {
1126         let text = r#"mod a;
1127 mod b;
1128 mod c;
1129
1130 fn main() {
1131     if cond {
1132         a::do_a();
1133     } else {
1134         b::do_b();
1135     }
1136 }"#;
1137
1138         let (analysis, file_id) = Analysis::from_single_file(text.to_string());
1139         let folds = analysis.folding_ranges(file_id).unwrap();
1140         assert_eq!(folds.len(), 4);
1141
1142         let line_index = LineIndex {
1143             index: Arc::new(ide::LineIndex::new(&text)),
1144             endings: LineEndings::Unix,
1145             encoding: OffsetEncoding::Utf16,
1146         };
1147         let converted: Vec<lsp_types::FoldingRange> =
1148             folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect();
1149
1150         let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
1151         assert_eq!(converted.len(), expected_lines.len());
1152         for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
1153             assert_eq!(folding_range.start_line, *start_line);
1154             assert_eq!(folding_range.start_character, None);
1155             assert_eq!(folding_range.end_line, *end_line);
1156             assert_eq!(folding_range.end_character, None);
1157         }
1158     }
1159
1160     // `Url` is not able to parse windows paths on unix machines.
1161     #[test]
1162     #[cfg(target_os = "windows")]
1163     fn test_lowercase_drive_letter_with_drive() {
1164         let url = url_from_abs_path(Path::new("C:\\Test"));
1165         assert_eq!(url.to_string(), "file:///c:/Test");
1166     }
1167
1168     #[test]
1169     #[cfg(target_os = "windows")]
1170     fn test_drive_without_colon_passthrough() {
1171         let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#));
1172         assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
1173     }
1174 }