Skip to main content

ide_completion/
item.rs

1//! See `CompletionItem` structure.
2
3use std::{fmt, mem};
4
5use hir::Mutability;
6use ide_db::text_edit::TextEdit;
7use ide_db::{
8    RootDatabase, SnippetCap, SymbolKind, documentation::Documentation,
9    imports::import_assets::LocatedImport,
10};
11use itertools::Itertools;
12use macros::UpmapFromRaFixture;
13use smallvec::SmallVec;
14use stdx::{format_to, impl_from, never};
15use syntax::{Edition, SmolStr, TextRange, TextSize, format_smolstr};
16
17use crate::{
18    context::{CompletionContext, PathCompletionCtx},
19    render::{RenderContext, render_path_resolution},
20};
21
22/// `CompletionItem` describes a single completion entity which expands to 1 or more entries in the
23/// editor pop-up.
24///
25/// It is basically a POD with various properties. To construct a [`CompletionItem`],
26/// use the [`Builder`] struct.
27#[derive(Clone, UpmapFromRaFixture)]
28#[non_exhaustive]
29pub struct CompletionItem {
30    /// Label in the completion pop up which identifies completion.
31    pub label: CompletionItemLabel,
32
33    /// Range of identifier that is being completed.
34    ///
35    /// It should be used primarily for UI, but we also use this to convert
36    /// generic TextEdit into LSP's completion edit (see conv.rs).
37    ///
38    /// `source_range` must contain the completion offset. `text_edit` should
39    /// start with what `source_range` points to, or VSCode will filter out the
40    /// completion silently.
41    pub source_range: TextRange,
42    /// What happens when user selects this item.
43    ///
44    /// Typically, replaces `source_range` with new identifier.
45    pub text_edit: TextEdit,
46    pub is_snippet: bool,
47
48    /// What item (struct, function, etc) are we completing.
49    pub kind: CompletionItemKind,
50
51    /// Lookup is used to check if completion item indeed can complete current
52    /// ident.
53    ///
54    /// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
55    /// contains `bar` sub sequence), and `quux` will rejected.
56    pub lookup: SmolStr,
57
58    /// Additional info to show in the UI pop up.
59    pub detail: Option<String>,
60    // FIXME: Make this with `'db` lifetime.
61    pub documentation: Option<Documentation<'static>>,
62
63    /// Whether this item is marked as deprecated
64    ///
65    /// NOTE: this field is used in the LSP protocol. For the use of this information in completion
66    /// scoring, see [`CompletionRelevance::is_deprecated`].
67    pub deprecated: bool,
68
69    /// If completing a function call, ask the editor to show parameter popup
70    /// after completion.
71    pub trigger_call_info: bool,
72
73    /// We use this to sort completion. Relevance records facts like "do the
74    /// types align precisely?". We can't sort by relevances directly, they are
75    /// only partially ordered.
76    ///
77    /// Note that Relevance ignores fuzzy match score. We compute Relevance for
78    /// all possible items, and then separately build an ordered completion list
79    /// based on relevance and fuzzy matching with the already typed identifier.
80    pub relevance: CompletionRelevance,
81
82    /// Indicates that a reference or mutable reference to this variable is a
83    /// possible match.
84    // FIXME: We shouldn't expose Mutability here (that is HIR types at all), its fine for now though
85    // until we have more splitting completions in which case we should think about
86    // generalizing this. See https://github.com/rust-lang/rust-analyzer/issues/12571
87    pub ref_match: Option<(CompletionItemRefMode, TextSize)>,
88
89    /// The import data to add to completion's edits.
90    pub import_to_add: SmallVec<[CompletionItemImport; 1]>,
91}
92
93#[derive(Clone, UpmapFromRaFixture)]
94pub struct CompletionItemImport {
95    /// The path to import.
96    pub path: String,
97    /// Whether to import `as _`.
98    pub as_underscore: bool,
99}
100
101#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
102pub struct CompletionItemLabel {
103    /// The primary label for the completion item.
104    pub primary: SmolStr,
105    /// The left detail for the completion item, usually rendered right next to the primary label.
106    pub detail_left: Option<String>,
107    /// The right detail for the completion item, usually rendered right aligned at the end of the completion item.
108    pub detail_right: Option<String>,
109}
110// We use custom debug for CompletionItem to make snapshot tests more readable.
111impl fmt::Debug for CompletionItem {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        let mut s = f.debug_struct("CompletionItem");
114        s.field("label", &self.label.primary)
115            .field("detail_left", &self.label.detail_left)
116            .field("detail_right", &self.label.detail_right)
117            .field("source_range", &self.source_range);
118        if self.text_edit.len() == 1 {
119            let atom = self.text_edit.iter().next().unwrap();
120            s.field("delete", &atom.delete);
121            s.field("insert", &atom.insert);
122        } else {
123            s.field("text_edit", &self.text_edit);
124        }
125        s.field("kind", &self.kind);
126        if self.lookup() != self.label.primary {
127            s.field("lookup", &self.lookup());
128        }
129        if let Some(detail) = &self.detail {
130            s.field("detail", &detail);
131        }
132        if let Some(documentation) = &self.documentation {
133            s.field("documentation", &documentation);
134        }
135        if self.deprecated {
136            s.field("deprecated", &true);
137        }
138
139        if self.relevance != CompletionRelevance::default() {
140            s.field("relevance", &self.relevance);
141        }
142
143        if let Some((ref_mode, offset)) = self.ref_match {
144            let prefix = match ref_mode {
145                CompletionItemRefMode::Reference(mutability) => match mutability {
146                    Mutability::Shared => "&",
147                    Mutability::Mut => "&mut ",
148                },
149                CompletionItemRefMode::Dereference => "*",
150            };
151            s.field("ref_match", &format!("{prefix}@{offset:?}"));
152        }
153        if self.trigger_call_info {
154            s.field("trigger_call_info", &true);
155        }
156        s.finish()
157    }
158}
159
160#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
161pub struct CompletionRelevance {
162    /// This is set when the identifier being completed matches up with the name that is expected,
163    /// like in a function argument.
164    ///
165    /// ```ignore
166    /// fn f(spam: String) {}
167    /// fn main() {
168    ///     let spam = 92;
169    ///     f($0) // name of local matches the name of param
170    /// }
171    /// ```
172    pub exact_name_match: bool,
173    /// See [`CompletionRelevanceTypeMatch`].
174    pub type_match: Option<CompletionRelevanceTypeMatch>,
175    /// Set for local variables.
176    ///
177    /// ```ignore
178    /// fn foo(a: u32) {
179    ///     let b = 0;
180    ///     $0 // `a` and `b` are local
181    /// }
182    /// ```
183    pub is_local: bool,
184    /// This is missing variant in the patterns.
185    /// Maybe this can also be used for struct fields.
186    pub is_missing: bool,
187    /// Populated when the completion item comes from a trait (impl).
188    pub trait_: Option<CompletionRelevanceTraitInfo>,
189    /// This is set when an import is suggested in a use item whose name is already imported.
190    pub is_name_already_imported: bool,
191    /// This is set for completions that will insert a `use` item.
192    pub requires_import: bool,
193    /// Set for item completions that are private but in the workspace.
194    pub is_private_editable: bool,
195    /// Set for postfix snippet item completions
196    pub postfix_match: Option<CompletionRelevancePostfixMatch>,
197    /// This is set for items that are function (associated or method)
198    pub function: Option<CompletionRelevanceFn>,
199    /// true when there is an `await.method()` or `iter().method()` completion.
200    pub is_skipping_completion: bool,
201    /// if inherent impl already exists in current module, user may not want to implement it again.
202    pub has_local_inherent_impl: bool,
203    /// Set when the completion item is deprecated.
204    ///
205    /// NOTE: This is duplicated from [`CompletionItem::deprecated`] in order to allow using this
206    /// information in the calculation of the relevance score.
207    pub is_deprecated: bool,
208}
209#[derive(Debug, Clone, Copy, Eq, PartialEq)]
210pub struct CompletionRelevanceTraitInfo {
211    /// The trait this item is from is a `#[doc(notable_trait)]`
212    pub notable_trait: bool,
213    /// Set for method completions of the `core::ops` and `core::cmp` family.
214    pub is_op_method: bool,
215}
216
217#[derive(Debug, Clone, Copy, Eq, PartialEq)]
218pub enum CompletionRelevanceTypeMatch {
219    /// This is set in cases like these:
220    ///
221    /// ```ignore
222    /// enum Option<T> { Some(T), None }
223    /// fn f(a: Option<u32>) {}
224    /// fn main {
225    ///     f(Option::N$0) // type `Option<T>` could unify with `Option<u32>`
226    /// }
227    /// ```
228    CouldUnify,
229    /// This is set in cases where the type matches the expected type, like:
230    ///
231    /// ```ignore
232    /// fn f(spam: String) {}
233    /// fn main() {
234    ///     let foo = String::new();
235    ///     f($0) // type of local matches the type of param
236    /// }
237    /// ```
238    Exact,
239}
240
241#[derive(Debug, Clone, Copy, Eq, PartialEq)]
242pub enum CompletionRelevancePostfixMatch {
243    /// Set in cases when item is postfix, but not exact
244    NonExact,
245    /// This is set in cases like these:
246    ///
247    /// ```ignore
248    /// (a > b).not$0
249    /// ```
250    ///
251    /// Basically, we want to guarantee that postfix snippets always takes
252    /// precedence over everything else.
253    Exact,
254}
255
256#[derive(Debug, Clone, Copy, Eq, PartialEq)]
257pub struct CompletionRelevanceFn {
258    pub has_params: bool,
259    pub has_self_param: bool,
260    pub return_type: CompletionRelevanceReturnType,
261}
262
263#[derive(Debug, Clone, Copy, Eq, PartialEq)]
264pub enum CompletionRelevanceReturnType {
265    Other,
266    /// Returns the Self type of the impl/trait
267    DirectConstructor,
268    /// Returns something that indirectly constructs the `Self` type of the impl/trait e.g. `Result<Self, ()>`, `Option<Self>`
269    Constructor,
270    /// Returns a possible builder for the type
271    Builder,
272}
273
274impl CompletionRelevance {
275    /// Provides a relevance score. Higher values are more relevant.
276    ///
277    /// The absolute value of the relevance score is not meaningful, for
278    /// example a value of BASE_SCORE doesn't mean "not relevant", rather
279    /// it means "least relevant". The score value should only be used
280    /// for relative ordering.
281    ///
282    /// See is_relevant if you need to make some judgement about score
283    /// in an absolute sense.
284    const BASE_SCORE: u32 = u32::MAX / 2;
285
286    pub fn score(self) -> u32 {
287        let mut score = Self::BASE_SCORE;
288        let CompletionRelevance {
289            exact_name_match,
290            type_match,
291            is_local,
292            is_missing,
293            is_name_already_imported,
294            requires_import,
295            is_private_editable,
296            postfix_match,
297            trait_,
298            function,
299            is_skipping_completion,
300            has_local_inherent_impl,
301            is_deprecated,
302        } = self;
303
304        // only applicable for completions within use items
305        // lower rank for conflicting import names
306        if is_name_already_imported {
307            score -= 15;
308        }
309        // slightly prefer locals
310        if is_local {
311            score += 2;
312        }
313        if is_missing {
314            score += 2;
315        }
316
317        // lower rank private things
318        if !is_private_editable {
319            score += 10;
320        }
321
322        if let Some(trait_) = trait_ {
323            // lower rank trait methods unless its notable
324            if !trait_.notable_trait {
325                score -= 5;
326            }
327            // lower rank trait op methods
328            if trait_.is_op_method {
329                score -= 5;
330            }
331        }
332
333        // Lower rank for completions that skip `await` and `iter()`.
334        if is_skipping_completion {
335            score -= 7;
336        }
337
338        // lower rank for items that need an import
339        if requires_import {
340            score -= 12;
341        }
342        if exact_name_match {
343            score += 40;
344        }
345        match postfix_match {
346            Some(CompletionRelevancePostfixMatch::Exact) => score += 100,
347            Some(CompletionRelevancePostfixMatch::NonExact) => score -= 5,
348            None => (),
349        };
350        score += match type_match {
351            Some(CompletionRelevanceTypeMatch::Exact) => 35,
352            Some(CompletionRelevanceTypeMatch::CouldUnify) => 15,
353            None => 0,
354        };
355        if let Some(function) = function {
356            let mut fn_score = if requires_import {
357                // Rank constructors that require imports lower than those who don't.
358                match function.return_type {
359                    CompletionRelevanceReturnType::DirectConstructor => 8,
360                    CompletionRelevanceReturnType::Builder => 5,
361                    CompletionRelevanceReturnType::Constructor => 3,
362                    CompletionRelevanceReturnType::Other => 0u32,
363                }
364            } else {
365                match function.return_type {
366                    CompletionRelevanceReturnType::DirectConstructor => 15,
367                    CompletionRelevanceReturnType::Builder => 10,
368                    CompletionRelevanceReturnType::Constructor => 5,
369                    CompletionRelevanceReturnType::Other => 0u32,
370                }
371            };
372
373            // When a fn is bumped due to return type:
374            // Bump Constructor or Builder methods with no arguments,
375            // over them than with self arguments
376            if function.has_params {
377                // bump associated functions
378                fn_score = fn_score.saturating_sub(1);
379            } else if function.has_self_param {
380                // downgrade methods (below Constructor)
381                fn_score = fn_score.min(1);
382            }
383
384            score += fn_score;
385        };
386
387        if has_local_inherent_impl {
388            score -= 8;
389        }
390
391        // lower rank for deprecated items
392        if is_deprecated {
393            score -= 15;
394        }
395
396        score
397    }
398
399    /// Returns true when the score for this threshold is above
400    /// some threshold such that we think it is especially likely
401    /// to be relevant.
402    pub fn is_relevant(&self) -> bool {
403        self.score() > Self::BASE_SCORE
404    }
405}
406
407/// The type of the completion item.
408#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
409pub enum CompletionItemKind {
410    SymbolKind(SymbolKind),
411    Binding,
412    BuiltinType,
413    InferredType,
414    Keyword,
415    Snippet,
416    UnresolvedReference,
417    Expression,
418}
419
420impl_from!(SymbolKind for CompletionItemKind);
421
422impl CompletionItemKind {
423    pub fn tag(self) -> &'static str {
424        match self {
425            CompletionItemKind::SymbolKind(kind) => match kind {
426                SymbolKind::Attribute => "at",
427                SymbolKind::BuiltinAttr => "ba",
428                SymbolKind::Const => "ct",
429                SymbolKind::ConstParam => "cp",
430                SymbolKind::CrateRoot => "cr",
431                SymbolKind::Derive => "de",
432                SymbolKind::DeriveHelper => "dh",
433                SymbolKind::Enum => "en",
434                SymbolKind::Field => "fd",
435                SymbolKind::Function => "fn",
436                SymbolKind::Impl => "im",
437                SymbolKind::InlineAsmRegOrRegClass => "ar",
438                SymbolKind::Label => "lb",
439                SymbolKind::LifetimeParam => "lt",
440                SymbolKind::Local => "lc",
441                SymbolKind::Macro => "ma",
442                SymbolKind::Method => "me",
443                SymbolKind::ProcMacro => "pm",
444                SymbolKind::Module => "md",
445                SymbolKind::SelfParam => "sp",
446                SymbolKind::SelfType => "sy",
447                SymbolKind::Static => "sc",
448                SymbolKind::Struct => "st",
449                SymbolKind::ToolModule => "tm",
450                SymbolKind::Trait => "tt",
451                SymbolKind::TypeAlias => "ta",
452                SymbolKind::TypeParam => "tp",
453                SymbolKind::Union => "un",
454                SymbolKind::ValueParam => "vp",
455                SymbolKind::Variant => "ev",
456            },
457            CompletionItemKind::Binding => "bn",
458            CompletionItemKind::BuiltinType => "bt",
459            CompletionItemKind::InferredType => "it",
460            CompletionItemKind::Keyword => "kw",
461            CompletionItemKind::Snippet => "sn",
462            CompletionItemKind::UnresolvedReference => "??",
463            CompletionItemKind::Expression => "ex",
464        }
465    }
466}
467
468#[derive(Copy, Clone, Debug)]
469pub enum CompletionItemRefMode {
470    Reference(Mutability),
471    Dereference,
472}
473
474impl CompletionItem {
475    pub(crate) fn new(
476        kind: impl Into<CompletionItemKind>,
477        source_range: TextRange,
478        label: impl Into<SmolStr>,
479        edition: Edition,
480    ) -> Builder {
481        let label = label.into();
482        Builder {
483            source_range,
484            label,
485            insert_text: None,
486            is_snippet: false,
487            trait_name: None,
488            detail: None,
489            documentation: None,
490            lookup: None,
491            kind: kind.into(),
492            text_edit: None,
493            deprecated: false,
494            trigger_call_info: false,
495            relevance: CompletionRelevance::default(),
496            ref_match: None,
497            imports_to_add: Default::default(),
498            doc_aliases: vec![],
499            adds_text: None,
500            edition,
501        }
502    }
503
504    /// What string is used for filtering.
505    pub fn lookup(&self) -> &str {
506        self.lookup.as_str()
507    }
508
509    pub fn ref_match(&self) -> Option<(String, ide_db::text_edit::Indel, CompletionRelevance)> {
510        // Relevance of the ref match should be the same as the original
511        // match, but with exact type match set because self.ref_match
512        // is only set if there is an exact type match.
513        let mut relevance = self.relevance;
514        relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
515
516        self.ref_match.map(|(mode, offset)| {
517            let prefix = match mode {
518                CompletionItemRefMode::Reference(Mutability::Shared) => "&",
519                CompletionItemRefMode::Reference(Mutability::Mut) => "&mut ",
520                CompletionItemRefMode::Dereference => "*",
521            };
522            let label = format!("{prefix}{}", self.label.primary);
523            (label, ide_db::text_edit::Indel::insert(offset, String::from(prefix)), relevance)
524        })
525    }
526}
527
528/// A helper to make `CompletionItem`s.
529#[must_use]
530#[derive(Debug, Clone)]
531pub(crate) struct Builder {
532    source_range: TextRange,
533    imports_to_add: SmallVec<[LocatedImport; 1]>,
534    trait_name: Option<SmolStr>,
535    doc_aliases: Vec<SmolStr>,
536    adds_text: Option<SmolStr>,
537    label: SmolStr,
538    insert_text: Option<String>,
539    is_snippet: bool,
540    detail: Option<String>,
541    // FIXME: Make this with `'db` lifetime.
542    documentation: Option<Documentation<'static>>,
543    lookup: Option<SmolStr>,
544    kind: CompletionItemKind,
545    text_edit: Option<TextEdit>,
546    deprecated: bool,
547    trigger_call_info: bool,
548    relevance: CompletionRelevance,
549    ref_match: Option<(CompletionItemRefMode, TextSize)>,
550    edition: Edition,
551}
552
553impl Builder {
554    pub(crate) fn from_resolution(
555        ctx: &CompletionContext<'_, '_>,
556        path_ctx: &PathCompletionCtx<'_>,
557        local_name: hir::Name,
558        resolution: hir::ScopeDef,
559    ) -> Self {
560        let doc_aliases = ctx.doc_aliases_in_scope(resolution);
561        render_path_resolution(
562            RenderContext::new(ctx).doc_aliases(doc_aliases),
563            path_ctx,
564            local_name,
565            resolution,
566        )
567    }
568
569    pub(crate) fn build(self, db: &RootDatabase) -> CompletionItem {
570        let _p = tracing::info_span!("item::Builder::build").entered();
571
572        let label = self.label;
573        let mut lookup = self.lookup.unwrap_or_else(|| label.clone());
574        let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
575
576        let mut detail_left = None;
577        let mut to_detail_left = |args: fmt::Arguments<'_>| {
578            let detail_left = detail_left.get_or_insert_with(String::new);
579            if !detail_left.is_empty() {
580                detail_left.push(' ');
581            }
582            format_to!(detail_left, "{args}")
583        };
584        if !self.doc_aliases.is_empty() {
585            let doc_aliases = self.doc_aliases.iter().join(", ");
586            to_detail_left(format_args!("(alias {doc_aliases})"));
587            let lookup_doc_aliases = self
588                .doc_aliases
589                .iter()
590                // Don't include aliases in `lookup` that aren't valid identifiers as including
591                // them results in weird completion filtering behavior e.g. `Partial>` matching
592                // `PartialOrd` because it has an alias of ">".
593                .filter(|alias| {
594                    let mut chars = alias.chars();
595                    chars.next().is_some_and(char::is_alphabetic)
596                        && chars.all(|c| c.is_alphanumeric() || c == '_')
597                })
598                // Deliberately concatenated without separators as adding separators e.g.
599                // `alias1, alias2` results in LSP clients continuing to display the completion even
600                // after typing a comma or space.
601                .join("");
602            if !lookup_doc_aliases.is_empty() {
603                lookup = format_smolstr!("{lookup}{lookup_doc_aliases}");
604            }
605        }
606        if let Some(adds_text) = self.adds_text {
607            to_detail_left(format_args!("(adds {})", adds_text.trim()));
608        }
609        if let [import_edit] = &*self.imports_to_add {
610            // snippets can have multiple imports, but normal completions only have up to one
611            to_detail_left(format_args!(
612                "(use {})",
613                import_edit.import_path.display(db, self.edition)
614            ));
615        } else if let Some(trait_name) = self.trait_name {
616            to_detail_left(format_args!("(as {trait_name})"));
617        }
618
619        let text_edit = match self.text_edit {
620            Some(it) => it,
621            None => TextEdit::replace(self.source_range, insert_text),
622        };
623
624        // Copy `deprecated` to `self.relevance.is_deprecated`
625        let relevance = CompletionRelevance { is_deprecated: self.deprecated, ..self.relevance };
626
627        let import_to_add = self
628            .imports_to_add
629            .into_iter()
630            .map(|import| {
631                let path = import.import_path.display(db, self.edition).to_string();
632                let as_underscore =
633                    if let hir::ItemInNs::Types(hir::ModuleDef::Trait(trait_to_import)) =
634                        import.item_to_import
635                    {
636                        trait_to_import.prefer_underscore_import(db)
637                    } else {
638                        false
639                    };
640                CompletionItemImport { path, as_underscore }
641            })
642            .collect();
643
644        CompletionItem {
645            source_range: self.source_range,
646            label: CompletionItemLabel {
647                primary: label,
648                detail_left,
649                detail_right: self.detail.clone(),
650            },
651            text_edit,
652            is_snippet: self.is_snippet,
653            detail: self.detail,
654            documentation: self.documentation,
655            lookup,
656            kind: self.kind,
657            deprecated: self.deprecated,
658            trigger_call_info: self.trigger_call_info,
659            relevance,
660            ref_match: self.ref_match,
661            import_to_add,
662        }
663    }
664    pub(crate) fn lookup_by(&mut self, lookup: impl Into<SmolStr>) -> &mut Builder {
665        self.lookup = Some(lookup.into());
666        self
667    }
668    pub(crate) fn label(&mut self, label: impl Into<SmolStr>) -> &mut Builder {
669        self.label = label.into();
670        self
671    }
672    pub(crate) fn trait_name(&mut self, trait_name: SmolStr) -> &mut Builder {
673        self.trait_name = Some(trait_name);
674        self
675    }
676    pub(crate) fn doc_aliases(&mut self, doc_aliases: Vec<SmolStr>) -> &mut Builder {
677        self.doc_aliases = doc_aliases;
678        self
679    }
680    pub(crate) fn adds_text(&mut self, adds_text: SmolStr) -> &mut Builder {
681        self.adds_text = Some(adds_text);
682        self
683    }
684    pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
685        self.insert_text = Some(insert_text.into());
686        self
687    }
688    pub(crate) fn insert_snippet(
689        &mut self,
690        cap: SnippetCap,
691        snippet: impl Into<String>,
692    ) -> &mut Builder {
693        let _ = cap;
694        self.is_snippet = true;
695        self.insert_text(snippet)
696    }
697    pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
698        self.text_edit = Some(edit);
699        self
700    }
701    pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
702        self.is_snippet = true;
703        self.text_edit(edit)
704    }
705    pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
706        self.set_detail(Some(detail))
707    }
708    pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder {
709        self.detail = detail.map(Into::into);
710        if let Some(detail) = &self.detail
711            && never!(detail.contains('\n'), "multiline detail:\n{}", detail)
712        {
713            self.detail = Some(detail.split('\n').next().unwrap().to_owned());
714        }
715        self
716    }
717    #[allow(unused)]
718    pub(crate) fn documentation(&mut self, docs: Documentation<'_>) -> &mut Builder {
719        self.set_documentation(Some(docs))
720    }
721    pub(crate) fn set_documentation(&mut self, docs: Option<Documentation<'_>>) -> &mut Builder {
722        self.documentation = docs.map(Documentation::into_owned);
723        self
724    }
725    pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
726        self.deprecated = deprecated;
727        self
728    }
729    pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
730        // The default value of `CompletionRelevance.is_deprecated` is `false`, so it being `true`
731        // would mean it was set manually. Advise using the other function instead.
732        //
733        // This is technically not necessary, because `deprecated` will get reconciled in
734        // `Builder::build` anyway -- it just helps keep the callers consistent.
735        assert!(
736            !relevance.is_deprecated,
737            "`deprecated` should be set using `Builder::set_deprecated` instead"
738        );
739        self.relevance = relevance;
740        self
741    }
742    pub(crate) fn with_relevance(
743        &mut self,
744        relevance: impl FnOnce(CompletionRelevance) -> CompletionRelevance,
745    ) -> &mut Builder {
746        self.relevance = relevance(mem::take(&mut self.relevance));
747        self
748    }
749    pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
750        self.trigger_call_info = true;
751        self
752    }
753    pub(crate) fn add_import(&mut self, import_to_add: LocatedImport) -> &mut Builder {
754        self.imports_to_add.push(import_to_add);
755        self
756    }
757    pub(crate) fn ref_match(
758        &mut self,
759        ref_mode: CompletionItemRefMode,
760        offset: TextSize,
761    ) -> &mut Builder {
762        self.ref_match = Some((ref_mode, offset));
763        self
764    }
765}
766
767#[cfg(test)]
768mod tests {
769    use itertools::Itertools;
770    use test_utils::assert_eq_text;
771
772    use super::{
773        CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
774        CompletionRelevanceTypeMatch,
775    };
776
777    #[test]
778    fn builder_deprecated_from_set_deprecated() {
779        // setting just `item.deprecated` also sets `item.relevance.is_deprecated`
780        let mut builder = CompletionItem::new(
781            CompletionItemKind::Expression,
782            Default::default(),
783            "",
784            syntax::Edition::DEFAULT,
785        );
786        builder.set_deprecated(true);
787        let item = builder.build(&Default::default());
788        assert!(item.deprecated);
789        assert!(item.relevance.is_deprecated);
790    }
791
792    /// Check that these are CompletionRelevance are sorted in ascending order
793    /// by their relevance score.
794    ///
795    /// We want to avoid making assertions about the absolute score of any
796    /// item, but we do want to assert whether each is >, <, or == to the
797    /// others.
798    ///
799    /// If provided vec![vec![a], vec![b, c], vec![d]], then this will assert:
800    ///     a.score < b.score == c.score < d.score
801    fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) {
802        let expected = format!("{expected_relevance_order:#?}");
803
804        let actual_relevance_order = expected_relevance_order
805            .into_iter()
806            .flatten()
807            .map(|r| (r.score(), r))
808            .sorted_by_key(|(score, _r)| *score)
809            .fold(
810                (u32::MIN, vec![vec![]]),
811                |(mut currently_collecting_score, mut out), (score, r)| {
812                    if currently_collecting_score == score {
813                        out.last_mut().unwrap().push(r);
814                    } else {
815                        currently_collecting_score = score;
816                        out.push(vec![r]);
817                    }
818                    (currently_collecting_score, out)
819                },
820            )
821            .1;
822
823        let actual = format!("{actual_relevance_order:#?}");
824
825        assert_eq_text!(&expected, &actual);
826    }
827
828    #[test]
829    fn relevance_score() {
830        use CompletionRelevance as Cr;
831        let default = Cr::default();
832        // This test asserts that the relevance score for these items is ascending, and
833        // that any items in the same vec have the same score.
834        let expected_relevance_order = vec![
835            vec![],
836            vec![Cr {
837                trait_: Some(crate::item::CompletionRelevanceTraitInfo {
838                    notable_trait: false,
839                    is_op_method: true,
840                }),
841                is_private_editable: true,
842                ..default
843            }],
844            vec![
845                Cr {
846                    trait_: Some(crate::item::CompletionRelevanceTraitInfo {
847                        notable_trait: false,
848                        is_op_method: true,
849                    }),
850                    ..default
851                },
852                Cr { is_private_editable: true, ..default },
853            ],
854            vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::NonExact), ..default }],
855            vec![default],
856            vec![Cr { is_local: true, ..default }],
857            vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::CouldUnify), ..default }],
858            vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::Exact), ..default }],
859            vec![Cr { exact_name_match: true, ..default }],
860            vec![Cr { exact_name_match: true, is_local: true, ..default }],
861            vec![Cr {
862                exact_name_match: true,
863                type_match: Some(CompletionRelevanceTypeMatch::Exact),
864                ..default
865            }],
866            vec![Cr {
867                exact_name_match: true,
868                type_match: Some(CompletionRelevanceTypeMatch::Exact),
869                is_local: true,
870                ..default
871            }],
872            vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::Exact), ..default }],
873        ];
874
875        check_relevance_score_ordered(expected_relevance_order);
876    }
877}