1 //! See `CompletionItem` structure.
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;
13 context::{CompletionContext, PathCompletionCtx},
14 render::{render_path_resolution, RenderContext},
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.
21 pub struct CompletionItem {
22 /// Label in the completion pop up which identifies completion.
24 /// Range of identifier that is being completed.
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).
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.
35 /// Typically, replaces `source_range` with new identifier.
39 /// What item (struct, function, etc) are we completing.
40 kind: CompletionItemKind,
42 /// Lookup is used to check if completion item indeed can complete current
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>,
49 /// Additional info to show in the UI pop up.
50 detail: Option<String>,
51 documentation: Option<Documentation>,
53 /// Whether this item is marked as deprecated
56 /// If completing a function call, ask the editor to show parameter popup
58 trigger_call_info: bool,
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.
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,
69 /// Indicates that a reference or mutable reference to this variable is a
71 ref_match: Option<(Mutability, TextSize)>,
73 /// The import data to add to completion's edits.
74 import_to_add: SmallVec<[LocatedImport; 1]>,
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);
87 s.field("text_edit", &self.text_edit);
89 s.field("kind", &self.kind());
90 if self.lookup() != self.label() {
91 s.field("lookup", &self.lookup());
93 if let Some(detail) = self.detail() {
94 s.field("detail", &detail);
96 if let Some(documentation) = self.documentation() {
97 s.field("documentation", &documentation);
100 s.field("deprecated", &true);
103 if self.relevance != CompletionRelevance::default() {
104 s.field("relevance", &self.relevance);
107 if let Some((mutability, offset)) = &self.ref_match {
108 s.field("ref_match", &format!("&{}@{offset:?}", mutability.as_keyword_for_ref()));
110 if self.trigger_call_info {
111 s.field("trigger_call_info", &true);
117 #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
118 pub struct CompletionRelevance {
119 /// This is set in cases like these:
122 /// fn f(spam: String) {}
125 /// f($0) // name of local matches the name of param
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:
136 /// $0 // `a` and `b` are local
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,
156 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
157 pub enum CompletionRelevanceTypeMatch {
158 /// This is set in cases like these:
161 /// enum Option<T> { Some(T), None }
162 /// fn f(a: Option<u32>) {}
164 /// f(Option::N$0) // type `Option<T>` could unify with `Option<u32>`
168 /// This is set in cases like these:
171 /// fn f(spam: String) {}
173 /// let foo = String::new();
174 /// f($0) // type of local matches the type of param
180 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
181 pub enum CompletionRelevancePostfixMatch {
182 /// Set in cases when item is postfix, but not exact
184 /// This is set in cases like these:
190 /// Basically, we want to guarantee that postfix snippets always takes
191 /// precedence over everything else.
195 impl CompletionRelevance {
196 /// Provides a relevance score. Higher values are more relevant.
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.
203 /// See is_relevant if you need to make some judgement about score
204 /// in an absolute sense.
205 pub fn score(self) -> u32 {
207 let CompletionRelevance {
212 is_name_already_imported,
220 // lower rank private things
221 if !is_private_editable {
224 // lower rank trait op methods
228 // lower rank for conflicting import names
229 if !is_name_already_imported {
232 // lower rank for items that don't need an import
233 if !requires_import {
236 if exact_name_match {
239 score += match postfix_match {
240 Some(CompletionRelevancePostfixMatch::Exact) => 100,
241 Some(CompletionRelevancePostfixMatch::NonExact) => 0,
244 score += match type_match {
245 Some(CompletionRelevanceTypeMatch::Exact) => 8,
246 Some(CompletionRelevanceTypeMatch::CouldUnify) => 3,
249 // slightly prefer locals
253 if is_item_from_trait {
262 /// Returns true when the score for this threshold is above
263 /// some threshold such that we think it is especially likely
265 pub fn is_relevant(&self) -> bool {
270 /// The type of the completion item.
271 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
272 pub enum CompletionItemKind {
273 SymbolKind(SymbolKind),
283 impl_from!(SymbolKind for CompletionItemKind);
285 impl CompletionItemKind {
287 pub(crate) fn tag(&self) -> &'static str {
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",
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 => "??",
328 impl CompletionItem {
330 kind: impl Into<CompletionItemKind>,
331 source_range: TextRange,
332 label: impl Into<SmolStr>,
334 let label = label.into();
347 trigger_call_info: false,
348 relevance: CompletionRelevance::default(),
350 imports_to_add: Default::default(),
354 /// What user sees in pop-up in the UI.
355 pub fn label(&self) -> &str {
358 pub fn source_range(&self) -> TextRange {
362 pub fn text_edit(&self) -> &TextEdit {
365 /// Whether `text_edit` is a snippet (contains `$0` markers).
366 pub fn is_snippet(&self) -> bool {
370 /// Short one-line additional information, like a type
371 pub fn detail(&self) -> Option<&str> {
372 self.detail.as_deref()
375 pub fn documentation(&self) -> Option<Documentation> {
376 self.documentation.clone()
378 /// What string is used for filtering.
379 pub fn lookup(&self) -> &str {
380 self.lookup.as_deref().unwrap_or(&self.label)
383 pub fn kind(&self) -> CompletionItemKind {
387 pub fn deprecated(&self) -> bool {
391 pub fn relevance(&self) -> CompletionRelevance {
395 pub fn trigger_call_info(&self) -> bool {
396 self.trigger_call_info
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);
406 self.ref_match.map(|(mutability, offset)| (mutability, offset, relevance))
409 pub fn imports_to_add(&self) -> &[LocatedImport] {
414 /// A helper to make `CompletionItem`s.
417 pub(crate) struct Builder {
418 source_range: TextRange,
419 imports_to_add: SmallVec<[LocatedImport; 1]>,
420 trait_name: Option<SmolStr>,
422 insert_text: Option<String>,
424 detail: Option<String>,
425 documentation: Option<Documentation>,
426 lookup: Option<SmolStr>,
427 kind: CompletionItemKind,
428 text_edit: Option<TextEdit>,
430 trigger_call_info: bool,
431 relevance: CompletionRelevance,
432 ref_match: Option<(Mutability, TextSize)>,
436 pub(crate) fn from_resolution(
437 ctx: &CompletionContext<'_>,
438 path_ctx: &PathCompletionCtx,
439 local_name: hir::Name,
440 resolution: hir::ScopeDef,
442 render_path_resolution(RenderContext::new(ctx), path_ctx, local_name, resolution)
445 pub(crate) fn build(self) -> CompletionItem {
446 let _p = profile::span("item::Builder::build");
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());
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));
458 } else if let Some(trait_name) = self.trait_name {
459 label = SmolStr::from(format!("{} (as {})", label, trait_name));
462 let text_edit = match self.text_edit {
464 None => TextEdit::replace(self.source_range, insert_text),
468 source_range: self.source_range,
471 is_snippet: self.is_snippet,
473 documentation: self.documentation,
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,
483 pub(crate) fn lookup_by(&mut self, lookup: impl Into<SmolStr>) -> &mut Builder {
484 self.lookup = Some(lookup.into());
487 pub(crate) fn label(&mut self, label: impl Into<SmolStr>) -> &mut Builder {
488 self.label = label.into();
491 pub(crate) fn trait_name(&mut self, trait_name: SmolStr) -> &mut Builder {
492 self.trait_name = Some(trait_name);
495 pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
496 self.insert_text = Some(insert_text.into());
499 pub(crate) fn insert_snippet(
502 snippet: impl Into<String>,
505 self.is_snippet = true;
506 self.insert_text(snippet)
508 pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
509 self.text_edit = Some(edit);
512 pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
513 self.is_snippet = true;
516 pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
517 self.set_detail(Some(detail))
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());
529 pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder {
530 self.set_documentation(Some(docs))
532 pub(crate) fn set_documentation(&mut self, docs: Option<Documentation>) -> &mut Builder {
533 self.documentation = docs.map(Into::into);
536 pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
537 self.deprecated = deprecated;
540 pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
541 self.relevance = relevance;
544 pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
545 self.trigger_call_info = true;
548 pub(crate) fn add_import(&mut self, import_to_add: LocatedImport) -> &mut Builder {
549 self.imports_to_add.push(import_to_add);
552 pub(crate) fn ref_match(&mut self, mutability: Mutability, offset: TextSize) -> &mut Builder {
553 self.ref_match = Some((mutability, offset));
560 use itertools::Itertools;
561 use test_utils::assert_eq_text;
564 CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceTypeMatch,
567 /// Check that these are CompletionRelevance are sorted in ascending order
568 /// by their relevance score.
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
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);
579 let actual_relevance_order = expected_relevance_order
582 .map(|r| (r.score(), r))
583 .sorted_by_key(|(score, _r)| *score)
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);
590 currently_collecting_score = score;
593 (currently_collecting_score, out)
598 let actual = format!("{:#?}", &actual_relevance_order);
600 assert_eq_text!(&expected, &actual);
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![
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 }],
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 }],
622 exact_name_match: true,
623 type_match: Some(CompletionRelevanceTypeMatch::Exact),
627 exact_name_match: true,
628 type_match: Some(CompletionRelevanceTypeMatch::Exact),
632 vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::Exact), ..default }],
635 check_relevance_score_ordered(expected_relevance_order);