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