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 is_missing: bool,
187 pub trait_: Option<CompletionRelevanceTraitInfo>,
189 pub is_name_already_imported: bool,
191 pub requires_import: bool,
193 pub is_private_editable: bool,
195 pub postfix_match: Option<CompletionRelevancePostfixMatch>,
197 pub function: Option<CompletionRelevanceFn>,
199 pub is_skipping_completion: bool,
201 pub has_local_inherent_impl: bool,
203 pub is_deprecated: bool,
208}
209#[derive(Debug, Clone, Copy, Eq, PartialEq)]
210pub struct CompletionRelevanceTraitInfo {
211 pub notable_trait: bool,
213 pub is_op_method: bool,
215}
216
217#[derive(Debug, Clone, Copy, Eq, PartialEq)]
218pub enum CompletionRelevanceTypeMatch {
219 CouldUnify,
229 Exact,
239}
240
241#[derive(Debug, Clone, Copy, Eq, PartialEq)]
242pub enum CompletionRelevancePostfixMatch {
243 NonExact,
245 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 DirectConstructor,
268 Constructor,
270 Builder,
272}
273
274impl CompletionRelevance {
275 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 if is_name_already_imported {
307 score -= 15;
308 }
309 if is_local {
311 score += 2;
312 }
313 if is_missing {
314 score += 2;
315 }
316
317 if !is_private_editable {
319 score += 10;
320 }
321
322 if let Some(trait_) = trait_ {
323 if !trait_.notable_trait {
325 score -= 5;
326 }
327 if trait_.is_op_method {
329 score -= 5;
330 }
331 }
332
333 if is_skipping_completion {
335 score -= 7;
336 }
337
338 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 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 if function.has_params {
377 fn_score = fn_score.saturating_sub(1);
379 } else if function.has_self_param {
380 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 if is_deprecated {
393 score -= 15;
394 }
395
396 score
397 }
398
399 pub fn is_relevant(&self) -> bool {
403 self.score() > Self::BASE_SCORE
404 }
405}
406
407#[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 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 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#[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 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 .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 .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 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 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 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 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 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 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}