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
!("{label} (use {original_path})"));
458 } else if let Some(trait_name
) = self.trait_name
{
459 label
= SmolStr
::from(format
!("{label} (as {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
);