1use std::{fmt, mem};
4
5use hir::Mutability;
6use ide_db::text_edit::TextEdit;
7use ide_db::{
8 documentation::Documentation, imports::import_assets::LocatedImport, RootDatabase, SnippetCap,
9 SymbolKind,
10};
11use itertools::Itertools;
12use smallvec::SmallVec;
13use stdx::{format_to, impl_from, never};
14use syntax::{format_smolstr, Edition, SmolStr, TextRange, TextSize};
15
16use crate::{
17 context::{CompletionContext, PathCompletionCtx},
18 render::{render_path_resolution, RenderContext},
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!("{}@{offset:?}", prefix));
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 pub fn score(self) -> u32 {
262 let mut score = !0 / 2;
263 let CompletionRelevance {
264 exact_name_match,
265 type_match,
266 is_local,
267 is_name_already_imported,
268 requires_import,
269 is_private_editable,
270 postfix_match,
271 trait_,
272 function,
273 is_skipping_completion,
274 } = self;
275
276 if is_name_already_imported {
279 score -= 1;
280 }
281 if is_local {
283 score += 1;
284 }
285
286 if !is_private_editable {
288 score += 1;
289 }
290
291 if let Some(trait_) = trait_ {
292 if !trait_.notable_trait {
294 score -= 5;
295 }
296 if trait_.is_op_method {
298 score -= 5;
299 }
300 }
301
302 if is_skipping_completion {
304 score -= 7;
305 }
306
307 if requires_import {
309 score -= 1;
310 }
311 if exact_name_match {
312 score += 20;
313 }
314 match postfix_match {
315 Some(CompletionRelevancePostfixMatch::Exact) => score += 100,
316 Some(CompletionRelevancePostfixMatch::NonExact) => score -= 5,
317 None => (),
318 };
319 score += match type_match {
320 Some(CompletionRelevanceTypeMatch::Exact) => 18,
321 Some(CompletionRelevanceTypeMatch::CouldUnify) => 5,
322 None => 0,
323 };
324 if let Some(function) = function {
325 let mut fn_score = match function.return_type {
326 CompletionRelevanceReturnType::DirectConstructor => 15,
327 CompletionRelevanceReturnType::Builder => 10,
328 CompletionRelevanceReturnType::Constructor => 5,
329 CompletionRelevanceReturnType::Other => 0u32,
330 };
331
332 if function.has_params {
336 fn_score = fn_score.saturating_sub(1);
338 } else if function.has_self_param {
339 fn_score = fn_score.min(1);
341 }
342
343 score += fn_score;
344 };
345
346 score
347 }
348
349 pub fn is_relevant(&self) -> bool {
353 self.score() > 0
354 }
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
359pub enum CompletionItemKind {
360 SymbolKind(SymbolKind),
361 Binding,
362 BuiltinType,
363 InferredType,
364 Keyword,
365 Snippet,
366 UnresolvedReference,
367 Expression,
368}
369
370impl_from!(SymbolKind for CompletionItemKind);
371
372impl CompletionItemKind {
373 pub fn tag(self) -> &'static str {
374 match self {
375 CompletionItemKind::SymbolKind(kind) => match kind {
376 SymbolKind::Attribute => "at",
377 SymbolKind::BuiltinAttr => "ba",
378 SymbolKind::Const => "ct",
379 SymbolKind::ConstParam => "cp",
380 SymbolKind::Derive => "de",
381 SymbolKind::DeriveHelper => "dh",
382 SymbolKind::Enum => "en",
383 SymbolKind::Field => "fd",
384 SymbolKind::Function => "fn",
385 SymbolKind::Impl => "im",
386 SymbolKind::InlineAsmRegOrRegClass => "ar",
387 SymbolKind::Label => "lb",
388 SymbolKind::LifetimeParam => "lt",
389 SymbolKind::Local => "lc",
390 SymbolKind::Macro => "ma",
391 SymbolKind::Method => "me",
392 SymbolKind::ProcMacro => "pm",
393 SymbolKind::Module => "md",
394 SymbolKind::SelfParam => "sp",
395 SymbolKind::SelfType => "sy",
396 SymbolKind::Static => "sc",
397 SymbolKind::Struct => "st",
398 SymbolKind::ToolModule => "tm",
399 SymbolKind::Trait => "tt",
400 SymbolKind::TraitAlias => "tr",
401 SymbolKind::TypeAlias => "ta",
402 SymbolKind::TypeParam => "tp",
403 SymbolKind::Union => "un",
404 SymbolKind::ValueParam => "vp",
405 SymbolKind::Variant => "ev",
406 },
407 CompletionItemKind::Binding => "bn",
408 CompletionItemKind::BuiltinType => "bt",
409 CompletionItemKind::InferredType => "it",
410 CompletionItemKind::Keyword => "kw",
411 CompletionItemKind::Snippet => "sn",
412 CompletionItemKind::UnresolvedReference => "??",
413 CompletionItemKind::Expression => "ex",
414 }
415 }
416}
417
418#[derive(Copy, Clone, Debug)]
419pub enum CompletionItemRefMode {
420 Reference(Mutability),
421 Dereference,
422}
423
424impl CompletionItem {
425 pub(crate) fn new(
426 kind: impl Into<CompletionItemKind>,
427 source_range: TextRange,
428 label: impl Into<SmolStr>,
429 edition: Edition,
430 ) -> Builder {
431 let label = label.into();
432 Builder {
433 source_range,
434 label,
435 insert_text: None,
436 is_snippet: false,
437 trait_name: None,
438 detail: None,
439 documentation: None,
440 lookup: None,
441 kind: kind.into(),
442 text_edit: None,
443 deprecated: false,
444 trigger_call_info: false,
445 relevance: CompletionRelevance::default(),
446 ref_match: None,
447 imports_to_add: Default::default(),
448 doc_aliases: vec![],
449 edition,
450 }
451 }
452
453 pub fn lookup(&self) -> &str {
455 self.lookup.as_str()
456 }
457
458 pub fn ref_match(&self) -> Option<(String, ide_db::text_edit::Indel, CompletionRelevance)> {
459 let mut relevance = self.relevance;
463 relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
464
465 self.ref_match.map(|(mode, offset)| {
466 let prefix = match mode {
467 CompletionItemRefMode::Reference(Mutability::Shared) => "&",
468 CompletionItemRefMode::Reference(Mutability::Mut) => "&mut ",
469 CompletionItemRefMode::Dereference => "*",
470 };
471 let label = format!("{prefix}{}", self.label.primary);
472 (label, ide_db::text_edit::Indel::insert(offset, String::from(prefix)), relevance)
473 })
474 }
475}
476
477#[must_use]
479#[derive(Clone)]
480pub(crate) struct Builder {
481 source_range: TextRange,
482 imports_to_add: SmallVec<[LocatedImport; 1]>,
483 trait_name: Option<SmolStr>,
484 doc_aliases: Vec<SmolStr>,
485 label: SmolStr,
486 insert_text: Option<String>,
487 is_snippet: bool,
488 detail: Option<String>,
489 documentation: Option<Documentation>,
490 lookup: Option<SmolStr>,
491 kind: CompletionItemKind,
492 text_edit: Option<TextEdit>,
493 deprecated: bool,
494 trigger_call_info: bool,
495 relevance: CompletionRelevance,
496 ref_match: Option<(CompletionItemRefMode, TextSize)>,
497 edition: Edition,
498}
499
500impl Builder {
501 pub(crate) fn from_resolution(
502 ctx: &CompletionContext<'_>,
503 path_ctx: &PathCompletionCtx,
504 local_name: hir::Name,
505 resolution: hir::ScopeDef,
506 ) -> Self {
507 let doc_aliases = ctx.doc_aliases_in_scope(resolution);
508 render_path_resolution(
509 RenderContext::new(ctx).doc_aliases(doc_aliases),
510 path_ctx,
511 local_name,
512 resolution,
513 )
514 }
515
516 pub(crate) fn build(self, db: &RootDatabase) -> CompletionItem {
517 let _p = tracing::info_span!("item::Builder::build").entered();
518
519 let label = self.label;
520 let mut lookup = self.lookup.unwrap_or_else(|| label.clone());
521 let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
522
523 let mut detail_left = None;
524 if !self.doc_aliases.is_empty() {
525 let doc_aliases = self.doc_aliases.iter().join(", ");
526 detail_left = Some(format!("(alias {doc_aliases})"));
527 let lookup_doc_aliases = self
528 .doc_aliases
529 .iter()
530 .filter(|alias| {
534 let mut chars = alias.chars();
535 chars.next().is_some_and(char::is_alphabetic)
536 && chars.all(|c| c.is_alphanumeric() || c == '_')
537 })
538 .join("");
542 if !lookup_doc_aliases.is_empty() {
543 lookup = format_smolstr!("{lookup}{lookup_doc_aliases}");
544 }
545 }
546 if let [import_edit] = &*self.imports_to_add {
547 let detail_left = detail_left.get_or_insert_with(String::new);
549 format_to!(
550 detail_left,
551 "{}(use {})",
552 if detail_left.is_empty() { "" } else { " " },
553 import_edit.import_path.display(db, self.edition)
554 );
555 } else if let Some(trait_name) = self.trait_name {
556 let detail_left = detail_left.get_or_insert_with(String::new);
557 format_to!(
558 detail_left,
559 "{}(as {trait_name})",
560 if detail_left.is_empty() { "" } else { " " },
561 );
562 }
563
564 let text_edit = match self.text_edit {
565 Some(it) => it,
566 None => TextEdit::replace(self.source_range, insert_text),
567 };
568
569 let import_to_add = self
570 .imports_to_add
571 .into_iter()
572 .map(|import| import.import_path.display(db, self.edition).to_string())
573 .collect();
574
575 CompletionItem {
576 source_range: self.source_range,
577 label: CompletionItemLabel {
578 primary: label,
579 detail_left,
580 detail_right: self.detail.clone(),
581 },
582 text_edit,
583 is_snippet: self.is_snippet,
584 detail: self.detail,
585 documentation: self.documentation,
586 lookup,
587 kind: self.kind,
588 deprecated: self.deprecated,
589 trigger_call_info: self.trigger_call_info,
590 relevance: self.relevance,
591 ref_match: self.ref_match,
592 import_to_add,
593 }
594 }
595 pub(crate) fn lookup_by(&mut self, lookup: impl Into<SmolStr>) -> &mut Builder {
596 self.lookup = Some(lookup.into());
597 self
598 }
599 pub(crate) fn label(&mut self, label: impl Into<SmolStr>) -> &mut Builder {
600 self.label = label.into();
601 self
602 }
603 pub(crate) fn trait_name(&mut self, trait_name: SmolStr) -> &mut Builder {
604 self.trait_name = Some(trait_name);
605 self
606 }
607 pub(crate) fn doc_aliases(&mut self, doc_aliases: Vec<SmolStr>) -> &mut Builder {
608 self.doc_aliases = doc_aliases;
609 self
610 }
611 pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
612 self.insert_text = Some(insert_text.into());
613 self
614 }
615 pub(crate) fn insert_snippet(
616 &mut self,
617 cap: SnippetCap,
618 snippet: impl Into<String>,
619 ) -> &mut Builder {
620 let _ = cap;
621 self.is_snippet = true;
622 self.insert_text(snippet)
623 }
624 pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
625 self.text_edit = Some(edit);
626 self
627 }
628 pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
629 self.is_snippet = true;
630 self.text_edit(edit)
631 }
632 pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
633 self.set_detail(Some(detail))
634 }
635 pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder {
636 self.detail = detail.map(Into::into);
637 if let Some(detail) = &self.detail {
638 if never!(detail.contains('\n'), "multiline detail:\n{}", detail) {
639 self.detail = Some(detail.split('\n').next().unwrap().to_owned());
640 }
641 }
642 self
643 }
644 #[allow(unused)]
645 pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder {
646 self.set_documentation(Some(docs))
647 }
648 pub(crate) fn set_documentation(&mut self, docs: Option<Documentation>) -> &mut Builder {
649 self.documentation = docs;
650 self
651 }
652 pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
653 self.deprecated = deprecated;
654 self
655 }
656 pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
657 self.relevance = relevance;
658 self
659 }
660 pub(crate) fn with_relevance(
661 &mut self,
662 relevance: impl FnOnce(CompletionRelevance) -> CompletionRelevance,
663 ) -> &mut Builder {
664 self.relevance = relevance(mem::take(&mut self.relevance));
665 self
666 }
667 pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
668 self.trigger_call_info = true;
669 self
670 }
671 pub(crate) fn add_import(&mut self, import_to_add: LocatedImport) -> &mut Builder {
672 self.imports_to_add.push(import_to_add);
673 self
674 }
675 pub(crate) fn ref_match(
676 &mut self,
677 ref_mode: CompletionItemRefMode,
678 offset: TextSize,
679 ) -> &mut Builder {
680 self.ref_match = Some((ref_mode, offset));
681 self
682 }
683}
684
685#[cfg(test)]
686mod tests {
687 use itertools::Itertools;
688 use test_utils::assert_eq_text;
689
690 use super::{
691 CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceTypeMatch,
692 };
693
694 fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) {
704 let expected = format!("{expected_relevance_order:#?}");
705
706 let actual_relevance_order = expected_relevance_order
707 .into_iter()
708 .flatten()
709 .map(|r| (r.score(), r))
710 .sorted_by_key(|(score, _r)| *score)
711 .fold(
712 (u32::MIN, vec![vec![]]),
713 |(mut currently_collecting_score, mut out), (score, r)| {
714 if currently_collecting_score == score {
715 out.last_mut().unwrap().push(r);
716 } else {
717 currently_collecting_score = score;
718 out.push(vec![r]);
719 }
720 (currently_collecting_score, out)
721 },
722 )
723 .1;
724
725 let actual = format!("{actual_relevance_order:#?}");
726
727 assert_eq_text!(&expected, &actual);
728 }
729
730 #[test]
731 fn relevance_score() {
732 use CompletionRelevance as Cr;
733 let default = Cr::default();
734 let expected_relevance_order = vec![
737 vec![],
738 vec![Cr {
739 trait_: Some(crate::item::CompletionRelevanceTraitInfo {
740 notable_trait: false,
741 is_op_method: true,
742 }),
743 is_private_editable: true,
744 ..default
745 }],
746 vec![Cr {
747 trait_: Some(crate::item::CompletionRelevanceTraitInfo {
748 notable_trait: false,
749 is_op_method: true,
750 }),
751 ..default
752 }],
753 vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::NonExact), ..default }],
754 vec![Cr { is_private_editable: true, ..default }],
755 vec![default],
756 vec![Cr { is_local: true, ..default }],
757 vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::CouldUnify), ..default }],
758 vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::Exact), ..default }],
759 vec![Cr { exact_name_match: true, ..default }],
760 vec![Cr { exact_name_match: true, is_local: true, ..default }],
761 vec![Cr {
762 exact_name_match: true,
763 type_match: Some(CompletionRelevanceTypeMatch::Exact),
764 ..default
765 }],
766 vec![Cr {
767 exact_name_match: true,
768 type_match: Some(CompletionRelevanceTypeMatch::Exact),
769 is_local: true,
770 ..default
771 }],
772 vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::Exact), ..default }],
773 ];
774
775 check_relevance_score_ordered(expected_relevance_order);
776 }
777}