1use 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#[derive(Clone, UpmapFromRaFixture)]
29#[non_exhaustive]
30pub struct CompletionItem {
31 pub label: CompletionItemLabel,
33
34 pub source_range: TextRange,
43 pub text_edit: TextEdit,
47 pub is_snippet: bool,
48
49 pub kind: CompletionItemKind,
51
52 pub lookup: SmolStr,
58
59 pub detail: Option<String>,
61 pub documentation: Option<Documentation<'static>>,
63
64 pub deprecated: bool,
69
70 pub trigger_call_info: bool,
73
74 pub relevance: CompletionRelevance,
82
83 pub ref_match: Option<(CompletionItemRefMode, TextSize)>,
89
90 pub import_to_add: SmallVec<[CompletionItemImport; 1]>,
92}
93
94#[derive(Clone, UpmapFromRaFixture)]
95pub struct CompletionItemImport {
96 pub path: String,
98 pub as_underscore: bool,
100}
101
102#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
103pub struct CompletionItemLabel {
104 pub primary: SmolStr,
106 pub detail_left: Option<String>,
108 pub detail_right: Option<String>,
110}
111impl 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 pub exact_name_match: bool,
174 pub type_match: Option<CompletionRelevanceTypeMatch>,
176 pub is_local: bool,
185 pub is_missing: bool,
188 pub trait_: Option<CompletionRelevanceTraitInfo>,
190 pub is_name_already_imported: bool,
192 pub requires_import: bool,
194 pub is_private_editable: bool,
196 pub postfix_match: Option<CompletionRelevancePostfixMatch>,
198 pub function: Option<CompletionRelevanceFn>,
200 pub is_skipping_completion: bool,
202 pub has_local_inherent_impl: bool,
204 pub is_deprecated: bool,
209}
210#[derive(Debug, Clone, Copy, Eq, PartialEq)]
211pub struct CompletionRelevanceTraitInfo {
212 pub notable_trait: bool,
214 pub is_op_method: bool,
216}
217
218#[derive(Debug, Clone, Copy, Eq, PartialEq)]
219pub enum CompletionRelevanceTypeMatch {
220 CouldUnify,
230 Exact,
240}
241
242#[derive(Debug, Clone, Copy, Eq, PartialEq)]
243pub enum CompletionRelevancePostfixMatch {
244 NonExact,
246 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 DirectConstructor,
269 Constructor,
271 Builder,
273}
274
275impl CompletionRelevance {
276 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 if is_name_already_imported {
308 score -= 15;
309 }
310 if is_local {
312 score += 2;
313 }
314 if is_missing {
315 score += 2;
316 }
317
318 if !is_private_editable {
320 score += 10;
321 }
322
323 if let Some(trait_) = trait_ {
324 if !trait_.notable_trait {
326 score -= 5;
327 }
328 if trait_.is_op_method {
330 score -= 5;
331 }
332 }
333
334 if is_skipping_completion {
336 score -= 7;
337 }
338
339 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 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 if function.has_params {
378 fn_score = fn_score.saturating_sub(1);
380 } else if function.has_self_param {
381 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 if is_deprecated {
394 score -= 15;
395 }
396
397 score
398 }
399
400 pub fn is_relevant(&self) -> bool {
404 self.score() > Self::BASE_SCORE
405 }
406}
407
408#[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 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 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#[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 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 .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 .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 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 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 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 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 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 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}