]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/item.rs
Remove syntax highlighting hack for builtin attrs
[rust.git] / crates / ide_completion / src / item.rs
1 //! See `CompletionItem` structure.
2
3 use std::fmt;
4
5 use hir::{Documentation, Mutability};
6 use ide_db::{
7     helpers::{
8         import_assets::LocatedImport,
9         insert_use::{self, ImportScope, InsertUseConfig},
10         mod_path_to_ast, SnippetCap,
11     },
12     SymbolKind,
13 };
14 use smallvec::SmallVec;
15 use stdx::{impl_from, never};
16 use syntax::{algo, SmolStr, TextRange};
17 use text_edit::TextEdit;
18
19 /// `CompletionItem` describes a single completion variant in the editor pop-up.
20 /// It is basically a POD with various properties. To construct a
21 /// `CompletionItem`, use `new` method and the `Builder` struct.
22 #[derive(Clone)]
23 pub struct CompletionItem {
24     /// Label in the completion pop up which identifies completion.
25     label: SmolStr,
26     /// Range of identifier that is being completed.
27     ///
28     /// It should be used primarily for UI, but we also use this to convert
29     /// generic TextEdit into LSP's completion edit (see conv.rs).
30     ///
31     /// `source_range` must contain the completion offset. `insert_text` should
32     /// start with what `source_range` points to, or VSCode will filter out the
33     /// completion silently.
34     source_range: TextRange,
35     /// What happens when user selects this item.
36     ///
37     /// Typically, replaces `source_range` with new identifier.
38     text_edit: TextEdit,
39     is_snippet: bool,
40
41     /// What item (struct, function, etc) are we completing.
42     kind: CompletionItemKind,
43
44     /// Lookup is used to check if completion item indeed can complete current
45     /// ident.
46     ///
47     /// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
48     /// contains `bar` sub sequence), and `quux` will rejected.
49     lookup: Option<SmolStr>,
50
51     /// Additional info to show in the UI pop up.
52     detail: Option<String>,
53     documentation: Option<Documentation>,
54
55     /// Whether this item is marked as deprecated
56     deprecated: bool,
57
58     /// If completing a function call, ask the editor to show parameter popup
59     /// after completion.
60     trigger_call_info: bool,
61
62     /// We use this to sort completion. Relevance records facts like "do the
63     /// types align precisely?". We can't sort by relevances directly, they are
64     /// only partially ordered.
65     ///
66     /// Note that Relevance ignores fuzzy match score. We compute Relevance for
67     /// all possible items, and then separately build an ordered completion list
68     /// based on relevance and fuzzy matching with the already typed identifier.
69     relevance: CompletionRelevance,
70
71     /// Indicates that a reference or mutable reference to this variable is a
72     /// possible match.
73     ref_match: Option<Mutability>,
74
75     /// The import data to add to completion's edits.
76     import_to_add: SmallVec<[ImportEdit; 1]>,
77 }
78
79 // We use custom debug for CompletionItem to make snapshot tests more readable.
80 impl fmt::Debug for CompletionItem {
81     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82         let mut s = f.debug_struct("CompletionItem");
83         s.field("label", &self.label()).field("source_range", &self.source_range());
84         if self.text_edit().len() == 1 {
85             let atom = &self.text_edit().iter().next().unwrap();
86             s.field("delete", &atom.delete);
87             s.field("insert", &atom.insert);
88         } else {
89             s.field("text_edit", &self.text_edit);
90         }
91         s.field("kind", &self.kind());
92         if self.lookup() != self.label() {
93             s.field("lookup", &self.lookup());
94         }
95         if let Some(detail) = self.detail() {
96             s.field("detail", &detail);
97         }
98         if let Some(documentation) = self.documentation() {
99             s.field("documentation", &documentation);
100         }
101         if self.deprecated {
102             s.field("deprecated", &true);
103         }
104
105         if self.relevance != CompletionRelevance::default() {
106             s.field("relevance", &self.relevance);
107         }
108
109         if let Some(mutability) = &self.ref_match {
110             s.field("ref_match", &format!("&{}", mutability.as_keyword_for_ref()));
111         }
112         if self.trigger_call_info {
113             s.field("trigger_call_info", &true);
114         }
115         s.finish()
116     }
117 }
118
119 #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
120 pub struct CompletionRelevance {
121     /// This is set in cases like these:
122     ///
123     /// ```
124     /// fn f(spam: String) {}
125     /// fn main {
126     ///     let spam = 92;
127     ///     f($0) // name of local matches the name of param
128     /// }
129     /// ```
130     pub exact_name_match: bool,
131     /// See CompletionRelevanceTypeMatch doc comments for cases where this is set.
132     pub type_match: Option<CompletionRelevanceTypeMatch>,
133     /// This is set in cases like these:
134     ///
135     /// ```
136     /// fn foo(a: u32) {
137     ///     let b = 0;
138     ///     $0 // `a` and `b` are local
139     /// }
140     /// ```
141     pub is_local: bool,
142     /// This is set in cases like these:
143     ///
144     /// ```
145     /// (a > b).not$0
146     /// ```
147     ///
148     /// Basically, we want to guarantee that postfix snippets always takes
149     /// precedence over everything else.
150     pub exact_postfix_snippet_match: bool,
151 }
152
153 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
154 pub enum CompletionRelevanceTypeMatch {
155     /// This is set in cases like these:
156     ///
157     /// ```
158     /// enum Option<T> { Some(T), None }
159     /// fn f(a: Option<u32>) {}
160     /// fn main {
161     ///     f(Option::N$0) // type `Option<T>` could unify with `Option<u32>`
162     /// }
163     /// ```
164     CouldUnify,
165     /// This is set in cases like these:
166     ///
167     /// ```
168     /// fn f(spam: String) {}
169     /// fn main {
170     ///     let foo = String::new();
171     ///     f($0) // type of local matches the type of param
172     /// }
173     /// ```
174     Exact,
175 }
176
177 impl CompletionRelevance {
178     /// Provides a relevance score. Higher values are more relevant.
179     ///
180     /// The absolute value of the relevance score is not meaningful, for
181     /// example a value of 0 doesn't mean "not relevant", rather
182     /// it means "least relevant". The score value should only be used
183     /// for relative ordering.
184     ///
185     /// See is_relevant if you need to make some judgement about score
186     /// in an absolute sense.
187     pub fn score(&self) -> u32 {
188         let mut score = 0;
189
190         if self.exact_name_match {
191             score += 1;
192         }
193         score += match self.type_match {
194             Some(CompletionRelevanceTypeMatch::Exact) => 4,
195             Some(CompletionRelevanceTypeMatch::CouldUnify) => 3,
196             None => 0,
197         };
198         if self.is_local {
199             score += 1;
200         }
201         if self.exact_postfix_snippet_match {
202             score += 100;
203         }
204         score
205     }
206
207     /// Returns true when the score for this threshold is above
208     /// some threshold such that we think it is especially likely
209     /// to be relevant.
210     pub fn is_relevant(&self) -> bool {
211         self.score() > 0
212     }
213 }
214
215 /// The type of the completion item.
216 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
217 pub enum CompletionItemKind {
218     SymbolKind(SymbolKind),
219     Attribute,
220     Binding,
221     BuiltinType,
222     Keyword,
223     Method,
224     Snippet,
225     UnresolvedReference,
226 }
227
228 impl_from!(SymbolKind for CompletionItemKind);
229
230 impl CompletionItemKind {
231     #[cfg(test)]
232     pub(crate) fn tag(&self) -> &'static str {
233         match self {
234             CompletionItemKind::SymbolKind(kind) => match kind {
235                 SymbolKind::BuiltinAttr => "ba",
236                 SymbolKind::Const => "ct",
237                 SymbolKind::ConstParam => "cp",
238                 SymbolKind::Enum => "en",
239                 SymbolKind::Field => "fd",
240                 SymbolKind::Function => "fn",
241                 SymbolKind::Impl => "im",
242                 SymbolKind::Label => "lb",
243                 SymbolKind::LifetimeParam => "lt",
244                 SymbolKind::Local => "lc",
245                 SymbolKind::Macro => "ma",
246                 SymbolKind::Module => "md",
247                 SymbolKind::SelfParam => "sp",
248                 SymbolKind::Static => "sc",
249                 SymbolKind::Struct => "st",
250                 SymbolKind::Tool => "tl",
251                 SymbolKind::Trait => "tt",
252                 SymbolKind::TypeAlias => "ta",
253                 SymbolKind::TypeParam => "tp",
254                 SymbolKind::Union => "un",
255                 SymbolKind::ValueParam => "vp",
256                 SymbolKind::Variant => "ev",
257             },
258             CompletionItemKind::Attribute => "at",
259             CompletionItemKind::Binding => "bn",
260             CompletionItemKind::BuiltinType => "bt",
261             CompletionItemKind::Keyword => "kw",
262             CompletionItemKind::Method => "me",
263             CompletionItemKind::Snippet => "sn",
264             CompletionItemKind::UnresolvedReference => "??",
265         }
266     }
267 }
268
269 impl CompletionItem {
270     pub(crate) fn new(
271         kind: impl Into<CompletionItemKind>,
272         source_range: TextRange,
273         label: impl Into<SmolStr>,
274     ) -> Builder {
275         let label = label.into();
276         Builder {
277             source_range,
278             label,
279             insert_text: None,
280             is_snippet: false,
281             trait_name: None,
282             detail: None,
283             documentation: None,
284             lookup: None,
285             kind: kind.into(),
286             text_edit: None,
287             deprecated: false,
288             trigger_call_info: None,
289             relevance: CompletionRelevance::default(),
290             ref_match: None,
291             imports_to_add: Default::default(),
292         }
293     }
294
295     /// What user sees in pop-up in the UI.
296     pub fn label(&self) -> &str {
297         &self.label
298     }
299     pub fn source_range(&self) -> TextRange {
300         self.source_range
301     }
302
303     pub fn text_edit(&self) -> &TextEdit {
304         &self.text_edit
305     }
306     /// Whether `text_edit` is a snippet (contains `$0` markers).
307     pub fn is_snippet(&self) -> bool {
308         self.is_snippet
309     }
310
311     /// Short one-line additional information, like a type
312     pub fn detail(&self) -> Option<&str> {
313         self.detail.as_deref()
314     }
315     /// A doc-comment
316     pub fn documentation(&self) -> Option<Documentation> {
317         self.documentation.clone()
318     }
319     /// What string is used for filtering.
320     pub fn lookup(&self) -> &str {
321         self.lookup.as_deref().unwrap_or(&self.label)
322     }
323
324     pub fn kind(&self) -> CompletionItemKind {
325         self.kind
326     }
327
328     pub fn deprecated(&self) -> bool {
329         self.deprecated
330     }
331
332     pub fn relevance(&self) -> CompletionRelevance {
333         self.relevance
334     }
335
336     pub fn trigger_call_info(&self) -> bool {
337         self.trigger_call_info
338     }
339
340     pub fn ref_match(&self) -> Option<(Mutability, CompletionRelevance)> {
341         // Relevance of the ref match should be the same as the original
342         // match, but with exact type match set because self.ref_match
343         // is only set if there is an exact type match.
344         let mut relevance = self.relevance;
345         relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
346
347         self.ref_match.map(|mutability| (mutability, relevance))
348     }
349
350     pub fn imports_to_add(&self) -> &[ImportEdit] {
351         &self.import_to_add
352     }
353 }
354
355 /// An extra import to add after the completion is applied.
356 #[derive(Debug, Clone)]
357 pub struct ImportEdit {
358     pub import: LocatedImport,
359     pub scope: ImportScope,
360 }
361
362 impl ImportEdit {
363     /// Attempts to insert the import to the given scope, producing a text edit.
364     /// May return no edit in edge cases, such as scope already containing the import.
365     pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> {
366         let _p = profile::span("ImportEdit::to_text_edit");
367
368         let new_ast = self.scope.clone_for_update();
369         insert_use::insert_use(&new_ast, mod_path_to_ast(&self.import.import_path), &cfg);
370         let mut import_insert = TextEdit::builder();
371         algo::diff(self.scope.as_syntax_node(), new_ast.as_syntax_node())
372             .into_text_edit(&mut import_insert);
373
374         Some(import_insert.finish())
375     }
376 }
377
378 /// A helper to make `CompletionItem`s.
379 #[must_use]
380 #[derive(Clone)]
381 pub(crate) struct Builder {
382     source_range: TextRange,
383     imports_to_add: SmallVec<[ImportEdit; 1]>,
384     trait_name: Option<SmolStr>,
385     label: SmolStr,
386     insert_text: Option<String>,
387     is_snippet: bool,
388     detail: Option<String>,
389     documentation: Option<Documentation>,
390     lookup: Option<SmolStr>,
391     kind: CompletionItemKind,
392     text_edit: Option<TextEdit>,
393     deprecated: bool,
394     trigger_call_info: Option<bool>,
395     relevance: CompletionRelevance,
396     ref_match: Option<Mutability>,
397 }
398
399 impl Builder {
400     pub(crate) fn build(self) -> CompletionItem {
401         let _p = profile::span("item::Builder::build");
402
403         let mut label = self.label;
404         let mut lookup = self.lookup;
405         let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
406
407         if let [import_edit] = &*self.imports_to_add {
408             // snippets can have multiple imports, but normal completions only have up to one
409             if let Some(original_path) = import_edit.import.original_path.as_ref() {
410                 lookup = lookup.or_else(|| Some(label.clone()));
411                 label = SmolStr::from(format!("{} (use {})", label, original_path));
412             }
413         } else if let Some(trait_name) = self.trait_name {
414             label = SmolStr::from(format!("{} (as {})", label, trait_name));
415         }
416
417         let text_edit = match self.text_edit {
418             Some(it) => it,
419             None => TextEdit::replace(self.source_range, insert_text),
420         };
421
422         CompletionItem {
423             source_range: self.source_range,
424             label,
425             text_edit,
426             is_snippet: self.is_snippet,
427             detail: self.detail,
428             documentation: self.documentation,
429             lookup,
430             kind: self.kind,
431             deprecated: self.deprecated,
432             trigger_call_info: self.trigger_call_info.unwrap_or(false),
433             relevance: self.relevance,
434             ref_match: self.ref_match,
435             import_to_add: self.imports_to_add,
436         }
437     }
438     pub(crate) fn lookup_by(&mut self, lookup: impl Into<SmolStr>) -> &mut Builder {
439         self.lookup = Some(lookup.into());
440         self
441     }
442     pub(crate) fn label(&mut self, label: impl Into<SmolStr>) -> &mut Builder {
443         self.label = label.into();
444         self
445     }
446     pub(crate) fn trait_name(&mut self, trait_name: SmolStr) -> &mut Builder {
447         self.trait_name = Some(trait_name);
448         self
449     }
450     pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
451         self.insert_text = Some(insert_text.into());
452         self
453     }
454     pub(crate) fn insert_snippet(
455         &mut self,
456         cap: SnippetCap,
457         snippet: impl Into<String>,
458     ) -> &mut Builder {
459         let _ = cap;
460         self.is_snippet = true;
461         self.insert_text(snippet)
462     }
463     pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
464         self.text_edit = Some(edit);
465         self
466     }
467     pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
468         self.is_snippet = true;
469         self.text_edit(edit)
470     }
471     pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
472         self.set_detail(Some(detail))
473     }
474     pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder {
475         self.detail = detail.map(Into::into);
476         if let Some(detail) = &self.detail {
477             if never!(detail.contains('\n'), "multiline detail:\n{}", detail) {
478                 self.detail = Some(detail.splitn(2, '\n').next().unwrap().to_string());
479             }
480         }
481         self
482     }
483     #[allow(unused)]
484     pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder {
485         self.set_documentation(Some(docs))
486     }
487     pub(crate) fn set_documentation(&mut self, docs: Option<Documentation>) -> &mut Builder {
488         self.documentation = docs.map(Into::into);
489         self
490     }
491     pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
492         self.deprecated = deprecated;
493         self
494     }
495     pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
496         self.relevance = relevance;
497         self
498     }
499     pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
500         self.trigger_call_info = Some(true);
501         self
502     }
503     pub(crate) fn add_import(&mut self, import_to_add: ImportEdit) -> &mut Builder {
504         self.imports_to_add.push(import_to_add);
505         self
506     }
507     pub(crate) fn ref_match(&mut self, mutability: Mutability) -> &mut Builder {
508         self.ref_match = Some(mutability);
509         self
510     }
511 }
512
513 #[cfg(test)]
514 mod tests {
515     use itertools::Itertools;
516     use test_utils::assert_eq_text;
517
518     use super::{CompletionRelevance, CompletionRelevanceTypeMatch};
519
520     /// Check that these are CompletionRelevance are sorted in ascending order
521     /// by their relevance score.
522     ///
523     /// We want to avoid making assertions about the absolute score of any
524     /// item, but we do want to assert whether each is >, <, or == to the
525     /// others.
526     ///
527     /// If provided vec![vec![a], vec![b, c], vec![d]], then this will assert:
528     ///     a.score < b.score == c.score < d.score
529     fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) {
530         let expected = format!("{:#?}", &expected_relevance_order);
531
532         let actual_relevance_order = expected_relevance_order
533             .into_iter()
534             .flatten()
535             .map(|r| (r.score(), r))
536             .sorted_by_key(|(score, _r)| *score)
537             .fold(
538                 (u32::MIN, vec![vec![]]),
539                 |(mut currently_collecting_score, mut out), (score, r)| {
540                     if currently_collecting_score == score {
541                         out.last_mut().unwrap().push(r);
542                     } else {
543                         currently_collecting_score = score;
544                         out.push(vec![r]);
545                     }
546                     (currently_collecting_score, out)
547                 },
548             )
549             .1;
550
551         let actual = format!("{:#?}", &actual_relevance_order);
552
553         assert_eq_text!(&expected, &actual);
554     }
555
556     #[test]
557     fn relevance_score() {
558         // This test asserts that the relevance score for these items is ascending, and
559         // that any items in the same vec have the same score.
560         let expected_relevance_order = vec![
561             vec![CompletionRelevance::default()],
562             vec![
563                 CompletionRelevance { exact_name_match: true, ..CompletionRelevance::default() },
564                 CompletionRelevance { is_local: true, ..CompletionRelevance::default() },
565             ],
566             vec![CompletionRelevance {
567                 exact_name_match: true,
568                 is_local: true,
569                 ..CompletionRelevance::default()
570             }],
571             vec![CompletionRelevance {
572                 type_match: Some(CompletionRelevanceTypeMatch::CouldUnify),
573                 ..CompletionRelevance::default()
574             }],
575             vec![CompletionRelevance {
576                 type_match: Some(CompletionRelevanceTypeMatch::Exact),
577                 ..CompletionRelevance::default()
578             }],
579             vec![CompletionRelevance {
580                 exact_name_match: true,
581                 type_match: Some(CompletionRelevanceTypeMatch::Exact),
582                 ..CompletionRelevance::default()
583             }],
584             vec![CompletionRelevance {
585                 exact_name_match: true,
586                 type_match: Some(CompletionRelevanceTypeMatch::Exact),
587                 is_local: true,
588                 ..CompletionRelevance::default()
589             }],
590             vec![CompletionRelevance {
591                 exact_name_match: false,
592                 type_match: None,
593                 is_local: false,
594                 exact_postfix_snippet_match: true,
595             }],
596         ];
597
598         check_relevance_score_ordered(expected_relevance_order);
599     }
600 }