]> git.proxmox.com Git - rustc.git/blob - src/tools/rust-analyzer/crates/ide-completion/src/item.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide-completion / src / item.rs
1 //! See `CompletionItem` structure.
2
3 use std::fmt;
4
5 use hir::{Documentation, Mutability};
6 use ide_db::{imports::import_assets::LocatedImport, SnippetCap, SymbolKind};
7 use smallvec::SmallVec;
8 use stdx::{impl_from, never};
9 use syntax::{SmolStr, TextRange, TextSize};
10 use text_edit::TextEdit;
11
12 use crate::{
13 context::{CompletionContext, PathCompletionCtx},
14 render::{render_path_resolution, RenderContext},
15 };
16
17 /// `CompletionItem` describes a single completion variant in the editor pop-up.
18 /// It is basically a POD with various properties. To construct a
19 /// `CompletionItem`, use `new` method and the `Builder` struct.
20 #[derive(Clone)]
21 pub struct CompletionItem {
22 /// Label in the completion pop up which identifies completion.
23 label: SmolStr,
24 /// Range of identifier that is being completed.
25 ///
26 /// It should be used primarily for UI, but we also use this to convert
27 /// generic TextEdit into LSP's completion edit (see conv.rs).
28 ///
29 /// `source_range` must contain the completion offset. `text_edit` should
30 /// start with what `source_range` points to, or VSCode will filter out the
31 /// completion silently.
32 source_range: TextRange,
33 /// What happens when user selects this item.
34 ///
35 /// Typically, replaces `source_range` with new identifier.
36 text_edit: TextEdit,
37 is_snippet: bool,
38
39 /// What item (struct, function, etc) are we completing.
40 kind: CompletionItemKind,
41
42 /// Lookup is used to check if completion item indeed can complete current
43 /// ident.
44 ///
45 /// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
46 /// contains `bar` sub sequence), and `quux` will rejected.
47 lookup: Option<SmolStr>,
48
49 /// Additional info to show in the UI pop up.
50 detail: Option<String>,
51 documentation: Option<Documentation>,
52
53 /// Whether this item is marked as deprecated
54 deprecated: bool,
55
56 /// If completing a function call, ask the editor to show parameter popup
57 /// after completion.
58 trigger_call_info: bool,
59
60 /// We use this to sort completion. Relevance records facts like "do the
61 /// types align precisely?". We can't sort by relevances directly, they are
62 /// only partially ordered.
63 ///
64 /// Note that Relevance ignores fuzzy match score. We compute Relevance for
65 /// all possible items, and then separately build an ordered completion list
66 /// based on relevance and fuzzy matching with the already typed identifier.
67 relevance: CompletionRelevance,
68
69 /// Indicates that a reference or mutable reference to this variable is a
70 /// possible match.
71 ref_match: Option<(Mutability, TextSize)>,
72
73 /// The import data to add to completion's edits.
74 import_to_add: SmallVec<[LocatedImport; 1]>,
75 }
76
77 // We use custom debug for CompletionItem to make snapshot tests more readable.
78 impl fmt::Debug for CompletionItem {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 let mut s = f.debug_struct("CompletionItem");
81 s.field("label", &self.label()).field("source_range", &self.source_range());
82 if self.text_edit().len() == 1 {
83 let atom = &self.text_edit().iter().next().unwrap();
84 s.field("delete", &atom.delete);
85 s.field("insert", &atom.insert);
86 } else {
87 s.field("text_edit", &self.text_edit);
88 }
89 s.field("kind", &self.kind());
90 if self.lookup() != self.label() {
91 s.field("lookup", &self.lookup());
92 }
93 if let Some(detail) = self.detail() {
94 s.field("detail", &detail);
95 }
96 if let Some(documentation) = self.documentation() {
97 s.field("documentation", &documentation);
98 }
99 if self.deprecated {
100 s.field("deprecated", &true);
101 }
102
103 if self.relevance != CompletionRelevance::default() {
104 s.field("relevance", &self.relevance);
105 }
106
107 if let Some((mutability, offset)) = &self.ref_match {
108 s.field("ref_match", &format!("&{}@{offset:?}", mutability.as_keyword_for_ref()));
109 }
110 if self.trigger_call_info {
111 s.field("trigger_call_info", &true);
112 }
113 s.finish()
114 }
115 }
116
117 #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
118 pub struct CompletionRelevance {
119 /// This is set in cases like these:
120 ///
121 /// ```
122 /// fn f(spam: String) {}
123 /// fn main {
124 /// let spam = 92;
125 /// f($0) // name of local matches the name of param
126 /// }
127 /// ```
128 pub exact_name_match: bool,
129 /// See CompletionRelevanceTypeMatch doc comments for cases where this is set.
130 pub type_match: Option<CompletionRelevanceTypeMatch>,
131 /// This is set in cases like these:
132 ///
133 /// ```
134 /// fn foo(a: u32) {
135 /// let b = 0;
136 /// $0 // `a` and `b` are local
137 /// }
138 /// ```
139 pub is_local: bool,
140 /// This is set when trait items are completed in an impl of that trait.
141 pub is_item_from_trait: bool,
142 /// This is set when an import is suggested whose name is already imported.
143 pub is_name_already_imported: bool,
144 /// This is set for completions that will insert a `use` item.
145 pub requires_import: bool,
146 /// Set for method completions of the `core::ops` and `core::cmp` family.
147 pub is_op_method: bool,
148 /// Set for item completions that are private but in the workspace.
149 pub is_private_editable: bool,
150 /// Set for postfix snippet item completions
151 pub postfix_match: Option<CompletionRelevancePostfixMatch>,
152 /// This is set for type inference results
153 pub is_definite: bool,
154 }
155
156 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
157 pub enum CompletionRelevanceTypeMatch {
158 /// This is set in cases like these:
159 ///
160 /// ```
161 /// enum Option<T> { Some(T), None }
162 /// fn f(a: Option<u32>) {}
163 /// fn main {
164 /// f(Option::N$0) // type `Option<T>` could unify with `Option<u32>`
165 /// }
166 /// ```
167 CouldUnify,
168 /// This is set in cases like these:
169 ///
170 /// ```
171 /// fn f(spam: String) {}
172 /// fn main {
173 /// let foo = String::new();
174 /// f($0) // type of local matches the type of param
175 /// }
176 /// ```
177 Exact,
178 }
179
180 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
181 pub enum CompletionRelevancePostfixMatch {
182 /// Set in cases when item is postfix, but not exact
183 NonExact,
184 /// This is set in cases like these:
185 ///
186 /// ```
187 /// (a > b).not$0
188 /// ```
189 ///
190 /// Basically, we want to guarantee that postfix snippets always takes
191 /// precedence over everything else.
192 Exact,
193 }
194
195 impl CompletionRelevance {
196 /// Provides a relevance score. Higher values are more relevant.
197 ///
198 /// The absolute value of the relevance score is not meaningful, for
199 /// example a value of 0 doesn't mean "not relevant", rather
200 /// it means "least relevant". The score value should only be used
201 /// for relative ordering.
202 ///
203 /// See is_relevant if you need to make some judgement about score
204 /// in an absolute sense.
205 pub fn score(self) -> u32 {
206 let mut score = 0;
207 let CompletionRelevance {
208 exact_name_match,
209 type_match,
210 is_local,
211 is_item_from_trait,
212 is_name_already_imported,
213 requires_import,
214 is_op_method,
215 is_private_editable,
216 postfix_match,
217 is_definite,
218 } = self;
219
220 // lower rank private things
221 if !is_private_editable {
222 score += 1;
223 }
224 // lower rank trait op methods
225 if !is_op_method {
226 score += 10;
227 }
228 // lower rank for conflicting import names
229 if !is_name_already_imported {
230 score += 1;
231 }
232 // lower rank for items that don't need an import
233 if !requires_import {
234 score += 1;
235 }
236 if exact_name_match {
237 score += 10;
238 }
239 score += match postfix_match {
240 Some(CompletionRelevancePostfixMatch::Exact) => 100,
241 Some(CompletionRelevancePostfixMatch::NonExact) => 0,
242 None => 3,
243 };
244 score += match type_match {
245 Some(CompletionRelevanceTypeMatch::Exact) => 8,
246 Some(CompletionRelevanceTypeMatch::CouldUnify) => 3,
247 None => 0,
248 };
249 // slightly prefer locals
250 if is_local {
251 score += 1;
252 }
253 if is_item_from_trait {
254 score += 1;
255 }
256 if is_definite {
257 score += 10;
258 }
259 score
260 }
261
262 /// Returns true when the score for this threshold is above
263 /// some threshold such that we think it is especially likely
264 /// to be relevant.
265 pub fn is_relevant(&self) -> bool {
266 self.score() > 0
267 }
268 }
269
270 /// The type of the completion item.
271 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
272 pub enum CompletionItemKind {
273 SymbolKind(SymbolKind),
274 Binding,
275 BuiltinType,
276 InferredType,
277 Keyword,
278 Method,
279 Snippet,
280 UnresolvedReference,
281 }
282
283 impl_from!(SymbolKind for CompletionItemKind);
284
285 impl CompletionItemKind {
286 #[cfg(test)]
287 pub(crate) fn tag(&self) -> &'static str {
288 match self {
289 CompletionItemKind::SymbolKind(kind) => match kind {
290 SymbolKind::Attribute => "at",
291 SymbolKind::BuiltinAttr => "ba",
292 SymbolKind::Const => "ct",
293 SymbolKind::ConstParam => "cp",
294 SymbolKind::Derive => "de",
295 SymbolKind::DeriveHelper => "dh",
296 SymbolKind::Enum => "en",
297 SymbolKind::Field => "fd",
298 SymbolKind::Function => "fn",
299 SymbolKind::Impl => "im",
300 SymbolKind::Label => "lb",
301 SymbolKind::LifetimeParam => "lt",
302 SymbolKind::Local => "lc",
303 SymbolKind::Macro => "ma",
304 SymbolKind::Module => "md",
305 SymbolKind::SelfParam => "sp",
306 SymbolKind::SelfType => "sy",
307 SymbolKind::Static => "sc",
308 SymbolKind::Struct => "st",
309 SymbolKind::ToolModule => "tm",
310 SymbolKind::Trait => "tt",
311 SymbolKind::TypeAlias => "ta",
312 SymbolKind::TypeParam => "tp",
313 SymbolKind::Union => "un",
314 SymbolKind::ValueParam => "vp",
315 SymbolKind::Variant => "ev",
316 },
317 CompletionItemKind::Binding => "bn",
318 CompletionItemKind::BuiltinType => "bt",
319 CompletionItemKind::InferredType => "it",
320 CompletionItemKind::Keyword => "kw",
321 CompletionItemKind::Method => "me",
322 CompletionItemKind::Snippet => "sn",
323 CompletionItemKind::UnresolvedReference => "??",
324 }
325 }
326 }
327
328 impl CompletionItem {
329 pub(crate) fn new(
330 kind: impl Into<CompletionItemKind>,
331 source_range: TextRange,
332 label: impl Into<SmolStr>,
333 ) -> Builder {
334 let label = label.into();
335 Builder {
336 source_range,
337 label,
338 insert_text: None,
339 is_snippet: false,
340 trait_name: None,
341 detail: None,
342 documentation: None,
343 lookup: None,
344 kind: kind.into(),
345 text_edit: None,
346 deprecated: false,
347 trigger_call_info: false,
348 relevance: CompletionRelevance::default(),
349 ref_match: None,
350 imports_to_add: Default::default(),
351 }
352 }
353
354 /// What user sees in pop-up in the UI.
355 pub fn label(&self) -> &str {
356 &self.label
357 }
358 pub fn source_range(&self) -> TextRange {
359 self.source_range
360 }
361
362 pub fn text_edit(&self) -> &TextEdit {
363 &self.text_edit
364 }
365 /// Whether `text_edit` is a snippet (contains `$0` markers).
366 pub fn is_snippet(&self) -> bool {
367 self.is_snippet
368 }
369
370 /// Short one-line additional information, like a type
371 pub fn detail(&self) -> Option<&str> {
372 self.detail.as_deref()
373 }
374 /// A doc-comment
375 pub fn documentation(&self) -> Option<Documentation> {
376 self.documentation.clone()
377 }
378 /// What string is used for filtering.
379 pub fn lookup(&self) -> &str {
380 self.lookup.as_deref().unwrap_or(&self.label)
381 }
382
383 pub fn kind(&self) -> CompletionItemKind {
384 self.kind
385 }
386
387 pub fn deprecated(&self) -> bool {
388 self.deprecated
389 }
390
391 pub fn relevance(&self) -> CompletionRelevance {
392 self.relevance
393 }
394
395 pub fn trigger_call_info(&self) -> bool {
396 self.trigger_call_info
397 }
398
399 pub fn ref_match(&self) -> Option<(Mutability, TextSize, CompletionRelevance)> {
400 // Relevance of the ref match should be the same as the original
401 // match, but with exact type match set because self.ref_match
402 // is only set if there is an exact type match.
403 let mut relevance = self.relevance;
404 relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
405
406 self.ref_match.map(|(mutability, offset)| (mutability, offset, relevance))
407 }
408
409 pub fn imports_to_add(&self) -> &[LocatedImport] {
410 &self.import_to_add
411 }
412 }
413
414 /// A helper to make `CompletionItem`s.
415 #[must_use]
416 #[derive(Clone)]
417 pub(crate) struct Builder {
418 source_range: TextRange,
419 imports_to_add: SmallVec<[LocatedImport; 1]>,
420 trait_name: Option<SmolStr>,
421 label: SmolStr,
422 insert_text: Option<String>,
423 is_snippet: bool,
424 detail: Option<String>,
425 documentation: Option<Documentation>,
426 lookup: Option<SmolStr>,
427 kind: CompletionItemKind,
428 text_edit: Option<TextEdit>,
429 deprecated: bool,
430 trigger_call_info: bool,
431 relevance: CompletionRelevance,
432 ref_match: Option<(Mutability, TextSize)>,
433 }
434
435 impl Builder {
436 pub(crate) fn from_resolution(
437 ctx: &CompletionContext<'_>,
438 path_ctx: &PathCompletionCtx,
439 local_name: hir::Name,
440 resolution: hir::ScopeDef,
441 ) -> Self {
442 render_path_resolution(RenderContext::new(ctx), path_ctx, local_name, resolution)
443 }
444
445 pub(crate) fn build(self) -> CompletionItem {
446 let _p = profile::span("item::Builder::build");
447
448 let mut label = self.label;
449 let mut lookup = self.lookup;
450 let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
451
452 if let [import_edit] = &*self.imports_to_add {
453 // snippets can have multiple imports, but normal completions only have up to one
454 if let Some(original_path) = import_edit.original_path.as_ref() {
455 lookup = lookup.or_else(|| Some(label.clone()));
456 label = SmolStr::from(format!("{label} (use {original_path})"));
457 }
458 } else if let Some(trait_name) = self.trait_name {
459 label = SmolStr::from(format!("{label} (as {trait_name})"));
460 }
461
462 let text_edit = match self.text_edit {
463 Some(it) => it,
464 None => TextEdit::replace(self.source_range, insert_text),
465 };
466
467 CompletionItem {
468 source_range: self.source_range,
469 label,
470 text_edit,
471 is_snippet: self.is_snippet,
472 detail: self.detail,
473 documentation: self.documentation,
474 lookup,
475 kind: self.kind,
476 deprecated: self.deprecated,
477 trigger_call_info: self.trigger_call_info,
478 relevance: self.relevance,
479 ref_match: self.ref_match,
480 import_to_add: self.imports_to_add,
481 }
482 }
483 pub(crate) fn lookup_by(&mut self, lookup: impl Into<SmolStr>) -> &mut Builder {
484 self.lookup = Some(lookup.into());
485 self
486 }
487 pub(crate) fn label(&mut self, label: impl Into<SmolStr>) -> &mut Builder {
488 self.label = label.into();
489 self
490 }
491 pub(crate) fn trait_name(&mut self, trait_name: SmolStr) -> &mut Builder {
492 self.trait_name = Some(trait_name);
493 self
494 }
495 pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
496 self.insert_text = Some(insert_text.into());
497 self
498 }
499 pub(crate) fn insert_snippet(
500 &mut self,
501 cap: SnippetCap,
502 snippet: impl Into<String>,
503 ) -> &mut Builder {
504 let _ = cap;
505 self.is_snippet = true;
506 self.insert_text(snippet)
507 }
508 pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
509 self.text_edit = Some(edit);
510 self
511 }
512 pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
513 self.is_snippet = true;
514 self.text_edit(edit)
515 }
516 pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
517 self.set_detail(Some(detail))
518 }
519 pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder {
520 self.detail = detail.map(Into::into);
521 if let Some(detail) = &self.detail {
522 if never!(detail.contains('\n'), "multiline detail:\n{}", detail) {
523 self.detail = Some(detail.splitn(2, '\n').next().unwrap().to_string());
524 }
525 }
526 self
527 }
528 #[allow(unused)]
529 pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder {
530 self.set_documentation(Some(docs))
531 }
532 pub(crate) fn set_documentation(&mut self, docs: Option<Documentation>) -> &mut Builder {
533 self.documentation = docs.map(Into::into);
534 self
535 }
536 pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
537 self.deprecated = deprecated;
538 self
539 }
540 pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
541 self.relevance = relevance;
542 self
543 }
544 pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
545 self.trigger_call_info = true;
546 self
547 }
548 pub(crate) fn add_import(&mut self, import_to_add: LocatedImport) -> &mut Builder {
549 self.imports_to_add.push(import_to_add);
550 self
551 }
552 pub(crate) fn ref_match(&mut self, mutability: Mutability, offset: TextSize) -> &mut Builder {
553 self.ref_match = Some((mutability, offset));
554 self
555 }
556 }
557
558 #[cfg(test)]
559 mod tests {
560 use itertools::Itertools;
561 use test_utils::assert_eq_text;
562
563 use super::{
564 CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceTypeMatch,
565 };
566
567 /// Check that these are CompletionRelevance are sorted in ascending order
568 /// by their relevance score.
569 ///
570 /// We want to avoid making assertions about the absolute score of any
571 /// item, but we do want to assert whether each is >, <, or == to the
572 /// others.
573 ///
574 /// If provided vec![vec![a], vec![b, c], vec![d]], then this will assert:
575 /// a.score < b.score == c.score < d.score
576 fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) {
577 let expected = format!("{:#?}", &expected_relevance_order);
578
579 let actual_relevance_order = expected_relevance_order
580 .into_iter()
581 .flatten()
582 .map(|r| (r.score(), r))
583 .sorted_by_key(|(score, _r)| *score)
584 .fold(
585 (u32::MIN, vec![vec![]]),
586 |(mut currently_collecting_score, mut out), (score, r)| {
587 if currently_collecting_score == score {
588 out.last_mut().unwrap().push(r);
589 } else {
590 currently_collecting_score = score;
591 out.push(vec![r]);
592 }
593 (currently_collecting_score, out)
594 },
595 )
596 .1;
597
598 let actual = format!("{:#?}", &actual_relevance_order);
599
600 assert_eq_text!(&expected, &actual);
601 }
602
603 #[test]
604 fn relevance_score() {
605 use CompletionRelevance as Cr;
606 let default = Cr::default();
607 // This test asserts that the relevance score for these items is ascending, and
608 // that any items in the same vec have the same score.
609 let expected_relevance_order = vec![
610 vec![],
611 vec![Cr { is_op_method: true, is_private_editable: true, ..default }],
612 vec![Cr { is_op_method: true, ..default }],
613 vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::NonExact), ..default }],
614 vec![Cr { is_private_editable: true, ..default }],
615 vec![default],
616 vec![Cr { is_local: true, ..default }],
617 vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::CouldUnify), ..default }],
618 vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::Exact), ..default }],
619 vec![Cr { exact_name_match: true, ..default }],
620 vec![Cr { exact_name_match: true, is_local: true, ..default }],
621 vec![Cr {
622 exact_name_match: true,
623 type_match: Some(CompletionRelevanceTypeMatch::Exact),
624 ..default
625 }],
626 vec![Cr {
627 exact_name_match: true,
628 type_match: Some(CompletionRelevanceTypeMatch::Exact),
629 is_local: true,
630 ..default
631 }],
632 vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::Exact), ..default }],
633 ];
634
635 check_relevance_score_ordered(expected_relevance_order);
636 }
637 }