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