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 smallvec::SmallVec;
13use stdx::{format_to, impl_from, never};
14use syntax::{Edition, SmolStr, TextRange, TextSize, format_smolstr};
15
16use crate::{
17 context::{CompletionContext, PathCompletionCtx},
18 render::{RenderContext, render_path_resolution},
19};
20
21#[derive(Clone)]
27#[non_exhaustive]
28pub struct CompletionItem {
29 pub label: CompletionItemLabel,
31
32 pub source_range: TextRange,
41 pub text_edit: TextEdit,
45 pub is_snippet: bool,
46
47 pub kind: CompletionItemKind,
49
50 pub lookup: SmolStr,
56
57 pub detail: Option<String>,
59 pub documentation: Option<Documentation>,
60
61 pub deprecated: bool,
63
64 pub trigger_call_info: bool,
67
68 pub relevance: CompletionRelevance,
76
77 pub ref_match: Option<(CompletionItemRefMode, TextSize)>,
83
84 pub import_to_add: SmallVec<[String; 1]>,
86}
87
88#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
89pub struct CompletionItemLabel {
90 pub primary: SmolStr,
92 pub detail_left: Option<String>,
94 pub detail_right: Option<String>,
96}
97impl fmt::Debug for CompletionItem {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 let mut s = f.debug_struct("CompletionItem");
101 s.field("label", &self.label.primary)
102 .field("detail_left", &self.label.detail_left)
103 .field("detail_right", &self.label.detail_right)
104 .field("source_range", &self.source_range);
105 if self.text_edit.len() == 1 {
106 let atom = self.text_edit.iter().next().unwrap();
107 s.field("delete", &atom.delete);
108 s.field("insert", &atom.insert);
109 } else {
110 s.field("text_edit", &self.text_edit);
111 }
112 s.field("kind", &self.kind);
113 if self.lookup() != self.label.primary {
114 s.field("lookup", &self.lookup());
115 }
116 if let Some(detail) = &self.detail {
117 s.field("detail", &detail);
118 }
119 if let Some(documentation) = &self.documentation {
120 s.field("documentation", &documentation);
121 }
122 if self.deprecated {
123 s.field("deprecated", &true);
124 }
125
126 if self.relevance != CompletionRelevance::default() {
127 s.field("relevance", &self.relevance);
128 }
129
130 if let Some((ref_mode, offset)) = self.ref_match {
131 let prefix = match ref_mode {
132 CompletionItemRefMode::Reference(mutability) => match mutability {
133 Mutability::Shared => "&",
134 Mutability::Mut => "&mut ",
135 },
136 CompletionItemRefMode::Dereference => "*",
137 };
138 s.field("ref_match", &format!("{prefix}@{offset:?}"));
139 }
140 if self.trigger_call_info {
141 s.field("trigger_call_info", &true);
142 }
143 s.finish()
144 }
145}
146
147#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
148pub struct CompletionRelevance {
149 pub exact_name_match: bool,
160 pub type_match: Option<CompletionRelevanceTypeMatch>,
162 pub is_local: bool,
171 pub trait_: Option<CompletionRelevanceTraitInfo>,
173 pub is_name_already_imported: bool,
175 pub requires_import: bool,
177 pub is_private_editable: bool,
179 pub postfix_match: Option<CompletionRelevancePostfixMatch>,
181 pub function: Option<CompletionRelevanceFn>,
183 pub is_skipping_completion: bool,
185}
186#[derive(Debug, Clone, Copy, Eq, PartialEq)]
187pub struct CompletionRelevanceTraitInfo {
188 pub notable_trait: bool,
190 pub is_op_method: bool,
192}
193
194#[derive(Debug, Clone, Copy, Eq, PartialEq)]
195pub enum CompletionRelevanceTypeMatch {
196 CouldUnify,
206 Exact,
216}
217
218#[derive(Debug, Clone, Copy, Eq, PartialEq)]
219pub enum CompletionRelevancePostfixMatch {
220 NonExact,
222 Exact,
231}
232
233#[derive(Debug, Clone, Copy, Eq, PartialEq)]
234pub struct CompletionRelevanceFn {
235 pub has_params: bool,
236 pub has_self_param: bool,
237 pub return_type: CompletionRelevanceReturnType,
238}
239
240#[derive(Debug, Clone, Copy, Eq, PartialEq)]
241pub enum CompletionRelevanceReturnType {
242 Other,
243 DirectConstructor,
245 Constructor,
247 Builder,
249}
250
251impl CompletionRelevance {
252 const BASE_SCORE: u32 = u32::MAX / 2;
262
263 pub fn score(self) -> u32 {
264 let mut score = Self::BASE_SCORE;
265 let CompletionRelevance {
266 exact_name_match,
267 type_match,
268 is_local,
269 is_name_already_imported,
270 requires_import,
271 is_private_editable,
272 postfix_match,
273 trait_,
274 function,
275 is_skipping_completion,
276 } = self;
277
278 if is_name_already_imported {
281 score -= 1;
282 }
283 if is_local {
285 score += 1;
286 }
287
288 if !is_private_editable {
290 score += 1;
291 }
292
293 if let Some(trait_) = trait_ {
294 if !trait_.notable_trait {
296 score -= 5;
297 }
298 if trait_.is_op_method {
300 score -= 5;
301 }
302 }
303
304 if is_skipping_completion {
306 score -= 7;
307 }
308
309 if requires_import {
311 score -= 1;
312 }
313 if exact_name_match {
314 score += 20;
315 }
316 match postfix_match {
317 Some(CompletionRelevancePostfixMatch::Exact) => score += 100,
318 Some(CompletionRelevancePostfixMatch::NonExact) => score -= 5,
319 None => (),
320 };
321 score += match type_match {
322 Some(CompletionRelevanceTypeMatch::Exact) => 18,
323 Some(CompletionRelevanceTypeMatch::CouldUnify) => 5,
324 None => 0,
325 };
326 if let Some(function) = function {
327 let mut fn_score = match function.return_type {
328 CompletionRelevanceReturnType::DirectConstructor => 15,
329 CompletionRelevanceReturnType::Builder => 10,
330 CompletionRelevanceReturnType::Constructor => 5,
331 CompletionRelevanceReturnType::Other => 0u32,
332 };
333
334 if function.has_params {
338 fn_score = fn_score.saturating_sub(1);
340 } else if function.has_self_param {
341 fn_score = fn_score.min(1);
343 }
344
345 score += fn_score;
346 };
347
348 score
349 }
350
351 pub fn is_relevant(&self) -> bool {
355 self.score() > Self::BASE_SCORE
356 }
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
361pub enum CompletionItemKind {
362 SymbolKind(SymbolKind),
363 Binding,
364 BuiltinType,
365 InferredType,
366 Keyword,
367 Snippet,
368 UnresolvedReference,
369 Expression,
370}
371
372impl_from!(SymbolKind for CompletionItemKind);
373
374impl CompletionItemKind {
375 pub fn tag(self) -> &'static str {
376 match self {
377 CompletionItemKind::SymbolKind(kind) => match kind {
378 SymbolKind::Attribute => "at",
379 SymbolKind::BuiltinAttr => "ba",
380 SymbolKind::Const => "ct",
381 SymbolKind::ConstParam => "cp",
382 SymbolKind::Derive => "de",
383 SymbolKind::DeriveHelper => "dh",
384 SymbolKind::Enum => "en",
385 SymbolKind::Field => "fd",
386 SymbolKind::Function => "fn",
387 SymbolKind::Impl => "im",
388 SymbolKind::InlineAsmRegOrRegClass => "ar",
389 SymbolKind::Label => "lb",
390 SymbolKind::LifetimeParam => "lt",
391 SymbolKind::Local => "lc",
392 SymbolKind::Macro => "ma",
393 SymbolKind::Method => "me",
394 SymbolKind::ProcMacro => "pm",
395 SymbolKind::Module => "md",
396 SymbolKind::SelfParam => "sp",
397 SymbolKind::SelfType => "sy",
398 SymbolKind::Static => "sc",
399 SymbolKind::Struct => "st",
400 SymbolKind::ToolModule => "tm",
401 SymbolKind::Trait => "tt",
402 SymbolKind::TraitAlias => "tr",
403 SymbolKind::TypeAlias => "ta",
404 SymbolKind::TypeParam => "tp",
405 SymbolKind::Union => "un",
406 SymbolKind::ValueParam => "vp",
407 SymbolKind::Variant => "ev",
408 },
409 CompletionItemKind::Binding => "bn",
410 CompletionItemKind::BuiltinType => "bt",
411 CompletionItemKind::InferredType => "it",
412 CompletionItemKind::Keyword => "kw",
413 CompletionItemKind::Snippet => "sn",
414 CompletionItemKind::UnresolvedReference => "??",
415 CompletionItemKind::Expression => "ex",
416 }
417 }
418}
419
420#[derive(Copy, Clone, Debug)]
421pub enum CompletionItemRefMode {
422 Reference(Mutability),
423 Dereference,
424}
425
426impl CompletionItem {
427 pub(crate) fn new(
428 kind: impl Into<CompletionItemKind>,
429 source_range: TextRange,
430 label: impl Into<SmolStr>,
431 edition: Edition,
432 ) -> Builder {
433 let label = label.into();
434 Builder {
435 source_range,
436 label,
437 insert_text: None,
438 is_snippet: false,
439 trait_name: None,
440 detail: None,
441 documentation: None,
442 lookup: None,
443 kind: kind.into(),
444 text_edit: None,
445 deprecated: false,
446 trigger_call_info: false,
447 relevance: CompletionRelevance::default(),
448 ref_match: None,
449 imports_to_add: Default::default(),
450 doc_aliases: vec![],
451 edition,
452 }
453 }
454
455 pub fn lookup(&self) -> &str {
457 self.lookup.as_str()
458 }
459
460 pub fn ref_match(&self) -> Option<(String, ide_db::text_edit::Indel, CompletionRelevance)> {
461 let mut relevance = self.relevance;
465 relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
466
467 self.ref_match.map(|(mode, offset)| {
468 let prefix = match mode {
469 CompletionItemRefMode::Reference(Mutability::Shared) => "&",
470 CompletionItemRefMode::Reference(Mutability::Mut) => "&mut ",
471 CompletionItemRefMode::Dereference => "*",
472 };
473 let label = format!("{prefix}{}", self.label.primary);
474 (label, ide_db::text_edit::Indel::insert(offset, String::from(prefix)), relevance)
475 })
476 }
477}
478
479#[must_use]
481#[derive(Clone)]
482pub(crate) struct Builder {
483 source_range: TextRange,
484 imports_to_add: SmallVec<[LocatedImport; 1]>,
485 trait_name: Option<SmolStr>,
486 doc_aliases: Vec<SmolStr>,
487 label: SmolStr,
488 insert_text: Option<String>,
489 is_snippet: bool,
490 detail: Option<String>,
491 documentation: Option<Documentation>,
492 lookup: Option<SmolStr>,
493 kind: CompletionItemKind,
494 text_edit: Option<TextEdit>,
495 deprecated: bool,
496 trigger_call_info: bool,
497 relevance: CompletionRelevance,
498 ref_match: Option<(CompletionItemRefMode, TextSize)>,
499 edition: Edition,
500}
501
502impl Builder {
503 pub(crate) fn from_resolution(
504 ctx: &CompletionContext<'_>,
505 path_ctx: &PathCompletionCtx,
506 local_name: hir::Name,
507 resolution: hir::ScopeDef,
508 ) -> Self {
509 let doc_aliases = ctx.doc_aliases_in_scope(resolution);
510 render_path_resolution(
511 RenderContext::new(ctx).doc_aliases(doc_aliases),
512 path_ctx,
513 local_name,
514 resolution,
515 )
516 }
517
518 pub(crate) fn build(self, db: &RootDatabase) -> CompletionItem {
519 let _p = tracing::info_span!("item::Builder::build").entered();
520
521 let label = self.label;
522 let mut lookup = self.lookup.unwrap_or_else(|| label.clone());
523 let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
524
525 let mut detail_left = None;
526 if !self.doc_aliases.is_empty() {
527 let doc_aliases = self.doc_aliases.iter().join(", ");
528 detail_left = Some(format!("(alias {doc_aliases})"));
529 let lookup_doc_aliases = self
530 .doc_aliases
531 .iter()
532 .filter(|alias| {
536 let mut chars = alias.chars();
537 chars.next().is_some_and(char::is_alphabetic)
538 && chars.all(|c| c.is_alphanumeric() || c == '_')
539 })
540 .join("");
544 if !lookup_doc_aliases.is_empty() {
545 lookup = format_smolstr!("{lookup}{lookup_doc_aliases}");
546 }
547 }
548 if let [import_edit] = &*self.imports_to_add {
549 let detail_left = detail_left.get_or_insert_with(String::new);
551 format_to!(
552 detail_left,
553 "{}(use {})",
554 if detail_left.is_empty() { "" } else { " " },
555 import_edit.import_path.display(db, self.edition)
556 );
557 } else if let Some(trait_name) = self.trait_name {
558 let detail_left = detail_left.get_or_insert_with(String::new);
559 format_to!(
560 detail_left,
561 "{}(as {trait_name})",
562 if detail_left.is_empty() { "" } else { " " },
563 );
564 }
565
566 let text_edit = match self.text_edit {
567 Some(it) => it,
568 None => TextEdit::replace(self.source_range, insert_text),
569 };
570
571 let import_to_add = self
572 .imports_to_add
573 .into_iter()
574 .map(|import| import.import_path.display(db, self.edition).to_string())
575 .collect();
576
577 CompletionItem {
578 source_range: self.source_range,
579 label: CompletionItemLabel {
580 primary: label,
581 detail_left,
582 detail_right: self.detail.clone(),
583 },
584 text_edit,
585 is_snippet: self.is_snippet,
586 detail: self.detail,
587 documentation: self.documentation,
588 lookup,
589 kind: self.kind,
590 deprecated: self.deprecated,
591 trigger_call_info: self.trigger_call_info,
592 relevance: self.relevance,
593 ref_match: self.ref_match,
594 import_to_add,
595 }
596 }
597 pub(crate) fn lookup_by(&mut self, lookup: impl Into<SmolStr>) -> &mut Builder {
598 self.lookup = Some(lookup.into());
599 self
600 }
601 pub(crate) fn label(&mut self, label: impl Into<SmolStr>) -> &mut Builder {
602 self.label = label.into();
603 self
604 }
605 pub(crate) fn trait_name(&mut self, trait_name: SmolStr) -> &mut Builder {
606 self.trait_name = Some(trait_name);
607 self
608 }
609 pub(crate) fn doc_aliases(&mut self, doc_aliases: Vec<SmolStr>) -> &mut Builder {
610 self.doc_aliases = doc_aliases;
611 self
612 }
613 pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
614 self.insert_text = Some(insert_text.into());
615 self
616 }
617 pub(crate) fn insert_snippet(
618 &mut self,
619 cap: SnippetCap,
620 snippet: impl Into<String>,
621 ) -> &mut Builder {
622 let _ = cap;
623 self.is_snippet = true;
624 self.insert_text(snippet)
625 }
626 pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
627 self.text_edit = Some(edit);
628 self
629 }
630 pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
631 self.is_snippet = true;
632 self.text_edit(edit)
633 }
634 pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
635 self.set_detail(Some(detail))
636 }
637 pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder {
638 self.detail = detail.map(Into::into);
639 if let Some(detail) = &self.detail {
640 if never!(detail.contains('\n'), "multiline detail:\n{}", detail) {
641 self.detail = Some(detail.split('\n').next().unwrap().to_owned());
642 }
643 }
644 self
645 }
646 #[allow(unused)]
647 pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder {
648 self.set_documentation(Some(docs))
649 }
650 pub(crate) fn set_documentation(&mut self, docs: Option<Documentation>) -> &mut Builder {
651 self.documentation = docs;
652 self
653 }
654 pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
655 self.deprecated = deprecated;
656 self
657 }
658 pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
659 self.relevance = relevance;
660 self
661 }
662 pub(crate) fn with_relevance(
663 &mut self,
664 relevance: impl FnOnce(CompletionRelevance) -> CompletionRelevance,
665 ) -> &mut Builder {
666 self.relevance = relevance(mem::take(&mut self.relevance));
667 self
668 }
669 pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
670 self.trigger_call_info = true;
671 self
672 }
673 pub(crate) fn add_import(&mut self, import_to_add: LocatedImport) -> &mut Builder {
674 self.imports_to_add.push(import_to_add);
675 self
676 }
677 pub(crate) fn ref_match(
678 &mut self,
679 ref_mode: CompletionItemRefMode,
680 offset: TextSize,
681 ) -> &mut Builder {
682 self.ref_match = Some((ref_mode, offset));
683 self
684 }
685}
686
687#[cfg(test)]
688mod tests {
689 use itertools::Itertools;
690 use test_utils::assert_eq_text;
691
692 use super::{
693 CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceTypeMatch,
694 };
695
696 fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) {
706 let expected = format!("{expected_relevance_order:#?}");
707
708 let actual_relevance_order = expected_relevance_order
709 .into_iter()
710 .flatten()
711 .map(|r| (r.score(), r))
712 .sorted_by_key(|(score, _r)| *score)
713 .fold(
714 (u32::MIN, vec![vec![]]),
715 |(mut currently_collecting_score, mut out), (score, r)| {
716 if currently_collecting_score == score {
717 out.last_mut().unwrap().push(r);
718 } else {
719 currently_collecting_score = score;
720 out.push(vec![r]);
721 }
722 (currently_collecting_score, out)
723 },
724 )
725 .1;
726
727 let actual = format!("{actual_relevance_order:#?}");
728
729 assert_eq_text!(&expected, &actual);
730 }
731
732 #[test]
733 fn relevance_score() {
734 use CompletionRelevance as Cr;
735 let default = Cr::default();
736 let expected_relevance_order = vec![
739 vec![],
740 vec![Cr {
741 trait_: Some(crate::item::CompletionRelevanceTraitInfo {
742 notable_trait: false,
743 is_op_method: true,
744 }),
745 is_private_editable: true,
746 ..default
747 }],
748 vec![Cr {
749 trait_: Some(crate::item::CompletionRelevanceTraitInfo {
750 notable_trait: false,
751 is_op_method: true,
752 }),
753 ..default
754 }],
755 vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::NonExact), ..default }],
756 vec![Cr { is_private_editable: true, ..default }],
757 vec![default],
758 vec![Cr { is_local: true, ..default }],
759 vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::CouldUnify), ..default }],
760 vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::Exact), ..default }],
761 vec![Cr { exact_name_match: true, ..default }],
762 vec![Cr { exact_name_match: true, is_local: true, ..default }],
763 vec![Cr {
764 exact_name_match: true,
765 type_match: Some(CompletionRelevanceTypeMatch::Exact),
766 ..default
767 }],
768 vec![Cr {
769 exact_name_match: true,
770 type_match: Some(CompletionRelevanceTypeMatch::Exact),
771 is_local: true,
772 ..default
773 }],
774 vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::Exact), ..default }],
775 ];
776
777 check_relevance_score_ordered(expected_relevance_order);
778 }
779}