1use 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#[derive(Clone, UpmapFromRaFixture)]
28#[non_exhaustive]
29pub struct CompletionItem {
30 pub label: CompletionItemLabel,
32
33 pub source_range: TextRange,
42 pub text_edit: TextEdit,
46 pub is_snippet: bool,
47
48 pub kind: CompletionItemKind,
50
51 pub lookup: SmolStr,
57
58 pub detail: Option<String>,
60 pub documentation: Option<Documentation<'static>>,
62
63 pub deprecated: bool,
68
69 pub trigger_call_info: bool,
72
73 pub relevance: CompletionRelevance,
81
82 pub ref_match: Option<(CompletionItemRefMode, TextSize)>,
88
89 pub import_to_add: SmallVec<[CompletionItemImport; 1]>,
91}
92
93#[derive(Clone, UpmapFromRaFixture)]
94pub struct CompletionItemImport {
95 pub path: String,
97 pub as_underscore: bool,
99}
100
101#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
102pub struct CompletionItemLabel {
103 pub primary: SmolStr,
105 pub detail_left: Option<String>,
107 pub detail_right: Option<String>,
109}
110impl 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 pub exact_name_match: bool,
173 pub type_match: Option<CompletionRelevanceTypeMatch>,
175 pub is_local: bool,
184 pub trait_: Option<CompletionRelevanceTraitInfo>,
186 pub is_name_already_imported: bool,
188 pub requires_import: bool,
190 pub is_private_editable: bool,
192 pub postfix_match: Option<CompletionRelevancePostfixMatch>,
194 pub function: Option<CompletionRelevanceFn>,
196 pub is_skipping_completion: bool,
198 pub has_local_inherent_impl: bool,
200 pub is_deprecated: bool,
205}
206#[derive(Debug, Clone, Copy, Eq, PartialEq)]
207pub struct CompletionRelevanceTraitInfo {
208 pub notable_trait: bool,
210 pub is_op_method: bool,
212}
213
214#[derive(Debug, Clone, Copy, Eq, PartialEq)]
215pub enum CompletionRelevanceTypeMatch {
216 CouldUnify,
226 Exact,
236}
237
238#[derive(Debug, Clone, Copy, Eq, PartialEq)]
239pub enum CompletionRelevancePostfixMatch {
240 NonExact,
242 Exact,
251}
252
253#[derive(Debug, Clone, Copy, Eq, PartialEq)]
254pub struct CompletionRelevanceFn {
255 pub has_params: bool,
256 pub has_self_param: bool,
257 pub return_type: CompletionRelevanceReturnType,
258}
259
260#[derive(Debug, Clone, Copy, Eq, PartialEq)]
261pub enum CompletionRelevanceReturnType {
262 Other,
263 DirectConstructor,
265 Constructor,
267 Builder,
269}
270
271impl CompletionRelevance {
272 const BASE_SCORE: u32 = u32::MAX / 2;
282
283 pub fn score(self) -> u32 {
284 let mut score = Self::BASE_SCORE;
285 let CompletionRelevance {
286 exact_name_match,
287 type_match,
288 is_local,
289 is_name_already_imported,
290 requires_import,
291 is_private_editable,
292 postfix_match,
293 trait_,
294 function,
295 is_skipping_completion,
296 has_local_inherent_impl,
297 is_deprecated,
298 } = self;
299
300 if is_name_already_imported {
303 score -= 1;
304 }
305 if is_local {
307 score += 1;
308 }
309
310 if !is_private_editable {
312 score += 1;
313 }
314
315 if let Some(trait_) = trait_ {
316 if !trait_.notable_trait {
318 score -= 5;
319 }
320 if trait_.is_op_method {
322 score -= 5;
323 }
324 }
325
326 if is_skipping_completion {
328 score -= 7;
329 }
330
331 if requires_import {
333 score -= 1;
334 }
335 if exact_name_match {
336 score += 20;
337 }
338 match postfix_match {
339 Some(CompletionRelevancePostfixMatch::Exact) => score += 100,
340 Some(CompletionRelevancePostfixMatch::NonExact) => score -= 5,
341 None => (),
342 };
343 score += match type_match {
344 Some(CompletionRelevanceTypeMatch::Exact) => 18,
345 Some(CompletionRelevanceTypeMatch::CouldUnify) => 5,
346 None => 0,
347 };
348 if let Some(function) = function {
349 let mut fn_score = match function.return_type {
350 CompletionRelevanceReturnType::DirectConstructor => 15,
351 CompletionRelevanceReturnType::Builder => 10,
352 CompletionRelevanceReturnType::Constructor => 5,
353 CompletionRelevanceReturnType::Other => 0u32,
354 };
355
356 if function.has_params {
360 fn_score = fn_score.saturating_sub(1);
362 } else if function.has_self_param {
363 fn_score = fn_score.min(1);
365 }
366
367 score += fn_score;
368 };
369
370 if has_local_inherent_impl {
371 score -= 5;
372 }
373
374 if is_deprecated {
376 score -= 5;
377 }
378
379 score
380 }
381
382 pub fn is_relevant(&self) -> bool {
386 self.score() > Self::BASE_SCORE
387 }
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
392pub enum CompletionItemKind {
393 SymbolKind(SymbolKind),
394 Binding,
395 BuiltinType,
396 InferredType,
397 Keyword,
398 Snippet,
399 UnresolvedReference,
400 Expression,
401}
402
403impl_from!(SymbolKind for CompletionItemKind);
404
405impl CompletionItemKind {
406 pub fn tag(self) -> &'static str {
407 match self {
408 CompletionItemKind::SymbolKind(kind) => match kind {
409 SymbolKind::Attribute => "at",
410 SymbolKind::BuiltinAttr => "ba",
411 SymbolKind::Const => "ct",
412 SymbolKind::ConstParam => "cp",
413 SymbolKind::CrateRoot => "cr",
414 SymbolKind::Derive => "de",
415 SymbolKind::DeriveHelper => "dh",
416 SymbolKind::Enum => "en",
417 SymbolKind::Field => "fd",
418 SymbolKind::Function => "fn",
419 SymbolKind::Impl => "im",
420 SymbolKind::InlineAsmRegOrRegClass => "ar",
421 SymbolKind::Label => "lb",
422 SymbolKind::LifetimeParam => "lt",
423 SymbolKind::Local => "lc",
424 SymbolKind::Macro => "ma",
425 SymbolKind::Method => "me",
426 SymbolKind::ProcMacro => "pm",
427 SymbolKind::Module => "md",
428 SymbolKind::SelfParam => "sp",
429 SymbolKind::SelfType => "sy",
430 SymbolKind::Static => "sc",
431 SymbolKind::Struct => "st",
432 SymbolKind::ToolModule => "tm",
433 SymbolKind::Trait => "tt",
434 SymbolKind::TypeAlias => "ta",
435 SymbolKind::TypeParam => "tp",
436 SymbolKind::Union => "un",
437 SymbolKind::ValueParam => "vp",
438 SymbolKind::Variant => "ev",
439 },
440 CompletionItemKind::Binding => "bn",
441 CompletionItemKind::BuiltinType => "bt",
442 CompletionItemKind::InferredType => "it",
443 CompletionItemKind::Keyword => "kw",
444 CompletionItemKind::Snippet => "sn",
445 CompletionItemKind::UnresolvedReference => "??",
446 CompletionItemKind::Expression => "ex",
447 }
448 }
449}
450
451#[derive(Copy, Clone, Debug)]
452pub enum CompletionItemRefMode {
453 Reference(Mutability),
454 Dereference,
455}
456
457impl CompletionItem {
458 pub(crate) fn new(
459 kind: impl Into<CompletionItemKind>,
460 source_range: TextRange,
461 label: impl Into<SmolStr>,
462 edition: Edition,
463 ) -> Builder {
464 let label = label.into();
465 Builder {
466 source_range,
467 label,
468 insert_text: None,
469 is_snippet: false,
470 trait_name: None,
471 detail: None,
472 documentation: None,
473 lookup: None,
474 kind: kind.into(),
475 text_edit: None,
476 deprecated: false,
477 trigger_call_info: false,
478 relevance: CompletionRelevance::default(),
479 ref_match: None,
480 imports_to_add: Default::default(),
481 doc_aliases: vec![],
482 adds_text: None,
483 edition,
484 }
485 }
486
487 pub fn lookup(&self) -> &str {
489 self.lookup.as_str()
490 }
491
492 pub fn ref_match(&self) -> Option<(String, ide_db::text_edit::Indel, CompletionRelevance)> {
493 let mut relevance = self.relevance;
497 relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
498
499 self.ref_match.map(|(mode, offset)| {
500 let prefix = match mode {
501 CompletionItemRefMode::Reference(Mutability::Shared) => "&",
502 CompletionItemRefMode::Reference(Mutability::Mut) => "&mut ",
503 CompletionItemRefMode::Dereference => "*",
504 };
505 let label = format!("{prefix}{}", self.label.primary);
506 (label, ide_db::text_edit::Indel::insert(offset, String::from(prefix)), relevance)
507 })
508 }
509}
510
511#[must_use]
513#[derive(Debug, Clone)]
514pub(crate) struct Builder {
515 source_range: TextRange,
516 imports_to_add: SmallVec<[LocatedImport; 1]>,
517 trait_name: Option<SmolStr>,
518 doc_aliases: Vec<SmolStr>,
519 adds_text: Option<SmolStr>,
520 label: SmolStr,
521 insert_text: Option<String>,
522 is_snippet: bool,
523 detail: Option<String>,
524 documentation: Option<Documentation<'static>>,
526 lookup: Option<SmolStr>,
527 kind: CompletionItemKind,
528 text_edit: Option<TextEdit>,
529 deprecated: bool,
530 trigger_call_info: bool,
531 relevance: CompletionRelevance,
532 ref_match: Option<(CompletionItemRefMode, TextSize)>,
533 edition: Edition,
534}
535
536impl Builder {
537 pub(crate) fn from_resolution(
538 ctx: &CompletionContext<'_, '_>,
539 path_ctx: &PathCompletionCtx<'_>,
540 local_name: hir::Name,
541 resolution: hir::ScopeDef,
542 ) -> Self {
543 let doc_aliases = ctx.doc_aliases_in_scope(resolution);
544 render_path_resolution(
545 RenderContext::new(ctx).doc_aliases(doc_aliases),
546 path_ctx,
547 local_name,
548 resolution,
549 )
550 }
551
552 pub(crate) fn build(self, db: &RootDatabase) -> CompletionItem {
553 let _p = tracing::info_span!("item::Builder::build").entered();
554
555 let label = self.label;
556 let mut lookup = self.lookup.unwrap_or_else(|| label.clone());
557 let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
558
559 let mut detail_left = None;
560 let mut to_detail_left = |args: fmt::Arguments<'_>| {
561 let detail_left = detail_left.get_or_insert_with(String::new);
562 if !detail_left.is_empty() {
563 detail_left.push(' ');
564 }
565 format_to!(detail_left, "{args}")
566 };
567 if !self.doc_aliases.is_empty() {
568 let doc_aliases = self.doc_aliases.iter().join(", ");
569 to_detail_left(format_args!("(alias {doc_aliases})"));
570 let lookup_doc_aliases = self
571 .doc_aliases
572 .iter()
573 .filter(|alias| {
577 let mut chars = alias.chars();
578 chars.next().is_some_and(char::is_alphabetic)
579 && chars.all(|c| c.is_alphanumeric() || c == '_')
580 })
581 .join("");
585 if !lookup_doc_aliases.is_empty() {
586 lookup = format_smolstr!("{lookup}{lookup_doc_aliases}");
587 }
588 }
589 if let Some(adds_text) = self.adds_text {
590 to_detail_left(format_args!("(adds {})", adds_text.trim()));
591 }
592 if let [import_edit] = &*self.imports_to_add {
593 to_detail_left(format_args!(
595 "(use {})",
596 import_edit.import_path.display(db, self.edition)
597 ));
598 } else if let Some(trait_name) = self.trait_name {
599 to_detail_left(format_args!("(as {trait_name})"));
600 }
601
602 let text_edit = match self.text_edit {
603 Some(it) => it,
604 None => TextEdit::replace(self.source_range, insert_text),
605 };
606
607 let relevance = CompletionRelevance { is_deprecated: self.deprecated, ..self.relevance };
609
610 let import_to_add = self
611 .imports_to_add
612 .into_iter()
613 .map(|import| {
614 let path = import.import_path.display(db, self.edition).to_string();
615 let as_underscore =
616 if let hir::ItemInNs::Types(hir::ModuleDef::Trait(trait_to_import)) =
617 import.item_to_import
618 {
619 trait_to_import.prefer_underscore_import(db)
620 } else {
621 false
622 };
623 CompletionItemImport { path, as_underscore }
624 })
625 .collect();
626
627 CompletionItem {
628 source_range: self.source_range,
629 label: CompletionItemLabel {
630 primary: label,
631 detail_left,
632 detail_right: self.detail.clone(),
633 },
634 text_edit,
635 is_snippet: self.is_snippet,
636 detail: self.detail,
637 documentation: self.documentation,
638 lookup,
639 kind: self.kind,
640 deprecated: self.deprecated,
641 trigger_call_info: self.trigger_call_info,
642 relevance,
643 ref_match: self.ref_match,
644 import_to_add,
645 }
646 }
647 pub(crate) fn lookup_by(&mut self, lookup: impl Into<SmolStr>) -> &mut Builder {
648 self.lookup = Some(lookup.into());
649 self
650 }
651 pub(crate) fn label(&mut self, label: impl Into<SmolStr>) -> &mut Builder {
652 self.label = label.into();
653 self
654 }
655 pub(crate) fn trait_name(&mut self, trait_name: SmolStr) -> &mut Builder {
656 self.trait_name = Some(trait_name);
657 self
658 }
659 pub(crate) fn doc_aliases(&mut self, doc_aliases: Vec<SmolStr>) -> &mut Builder {
660 self.doc_aliases = doc_aliases;
661 self
662 }
663 pub(crate) fn adds_text(&mut self, adds_text: SmolStr) -> &mut Builder {
664 self.adds_text = Some(adds_text);
665 self
666 }
667 pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
668 self.insert_text = Some(insert_text.into());
669 self
670 }
671 pub(crate) fn insert_snippet(
672 &mut self,
673 cap: SnippetCap,
674 snippet: impl Into<String>,
675 ) -> &mut Builder {
676 let _ = cap;
677 self.is_snippet = true;
678 self.insert_text(snippet)
679 }
680 pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
681 self.text_edit = Some(edit);
682 self
683 }
684 pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
685 self.is_snippet = true;
686 self.text_edit(edit)
687 }
688 pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
689 self.set_detail(Some(detail))
690 }
691 pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder {
692 self.detail = detail.map(Into::into);
693 if let Some(detail) = &self.detail
694 && never!(detail.contains('\n'), "multiline detail:\n{}", detail)
695 {
696 self.detail = Some(detail.split('\n').next().unwrap().to_owned());
697 }
698 self
699 }
700 #[allow(unused)]
701 pub(crate) fn documentation(&mut self, docs: Documentation<'_>) -> &mut Builder {
702 self.set_documentation(Some(docs))
703 }
704 pub(crate) fn set_documentation(&mut self, docs: Option<Documentation<'_>>) -> &mut Builder {
705 self.documentation = docs.map(Documentation::into_owned);
706 self
707 }
708 pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
709 self.deprecated = deprecated;
710 self
711 }
712 pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
713 assert!(
719 !relevance.is_deprecated,
720 "`deprecated` should be set using `Builder::set_deprecated` instead"
721 );
722 self.relevance = relevance;
723 self
724 }
725 pub(crate) fn with_relevance(
726 &mut self,
727 relevance: impl FnOnce(CompletionRelevance) -> CompletionRelevance,
728 ) -> &mut Builder {
729 self.relevance = relevance(mem::take(&mut self.relevance));
730 self
731 }
732 pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
733 self.trigger_call_info = true;
734 self
735 }
736 pub(crate) fn add_import(&mut self, import_to_add: LocatedImport) -> &mut Builder {
737 self.imports_to_add.push(import_to_add);
738 self
739 }
740 pub(crate) fn ref_match(
741 &mut self,
742 ref_mode: CompletionItemRefMode,
743 offset: TextSize,
744 ) -> &mut Builder {
745 self.ref_match = Some((ref_mode, offset));
746 self
747 }
748}
749
750#[cfg(test)]
751mod tests {
752 use itertools::Itertools;
753 use test_utils::assert_eq_text;
754
755 use super::{
756 CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
757 CompletionRelevanceTypeMatch,
758 };
759
760 #[test]
761 fn builder_deprecated_from_set_deprecated() {
762 let mut builder = CompletionItem::new(
764 CompletionItemKind::Expression,
765 Default::default(),
766 "",
767 syntax::Edition::DEFAULT,
768 );
769 builder.set_deprecated(true);
770 let item = builder.build(&Default::default());
771 assert!(item.deprecated);
772 assert!(item.relevance.is_deprecated);
773 }
774
775 fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) {
785 let expected = format!("{expected_relevance_order:#?}");
786
787 let actual_relevance_order = expected_relevance_order
788 .into_iter()
789 .flatten()
790 .map(|r| (r.score(), r))
791 .sorted_by_key(|(score, _r)| *score)
792 .fold(
793 (u32::MIN, vec![vec![]]),
794 |(mut currently_collecting_score, mut out), (score, r)| {
795 if currently_collecting_score == score {
796 out.last_mut().unwrap().push(r);
797 } else {
798 currently_collecting_score = score;
799 out.push(vec![r]);
800 }
801 (currently_collecting_score, out)
802 },
803 )
804 .1;
805
806 let actual = format!("{actual_relevance_order:#?}");
807
808 assert_eq_text!(&expected, &actual);
809 }
810
811 #[test]
812 fn relevance_score() {
813 use CompletionRelevance as Cr;
814 let default = Cr::default();
815 let expected_relevance_order = vec![
818 vec![],
819 vec![Cr {
820 trait_: Some(crate::item::CompletionRelevanceTraitInfo {
821 notable_trait: false,
822 is_op_method: true,
823 }),
824 is_private_editable: true,
825 ..default
826 }],
827 vec![Cr {
828 trait_: Some(crate::item::CompletionRelevanceTraitInfo {
829 notable_trait: false,
830 is_op_method: true,
831 }),
832 ..default
833 }],
834 vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::NonExact), ..default }],
835 vec![Cr { is_private_editable: true, ..default }],
836 vec![default],
837 vec![Cr { is_local: true, ..default }],
838 vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::CouldUnify), ..default }],
839 vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::Exact), ..default }],
840 vec![Cr { exact_name_match: true, ..default }],
841 vec![Cr { exact_name_match: true, is_local: true, ..default }],
842 vec![Cr {
843 exact_name_match: true,
844 type_match: Some(CompletionRelevanceTypeMatch::Exact),
845 ..default
846 }],
847 vec![Cr {
848 exact_name_match: true,
849 type_match: Some(CompletionRelevanceTypeMatch::Exact),
850 is_local: true,
851 ..default
852 }],
853 vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::Exact), ..default }],
854 ];
855
856 check_relevance_score_ordered(expected_relevance_order);
857 }
858}