Skip to main content

ide_completion/
item.rs

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