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