aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion/src/item.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_completion/src/item.rs')
-rw-r--r--crates/ide_completion/src/item.rs257
1 files changed, 184 insertions, 73 deletions
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs
index 884711f11..3febab32b 100644
--- a/crates/ide_completion/src/item.rs
+++ b/crates/ide_completion/src/item.rs
@@ -2,15 +2,16 @@
2 2
3use std::fmt; 3use std::fmt;
4 4
5use hir::{Documentation, ModPath, Mutability}; 5use hir::{Documentation, Mutability};
6use ide_db::{ 6use ide_db::{
7 helpers::{ 7 helpers::{
8 insert_use::{self, ImportScope, MergeBehavior}, 8 import_assets::LocatedImport,
9 insert_use::{self, ImportScope, InsertUseConfig},
9 mod_path_to_ast, SnippetCap, 10 mod_path_to_ast, SnippetCap,
10 }, 11 },
11 SymbolKind, 12 SymbolKind,
12}; 13};
13use stdx::{impl_from, never}; 14use stdx::{format_to, impl_from, never};
14use syntax::{algo, TextRange}; 15use syntax::{algo, TextRange};
15use text_edit::TextEdit; 16use text_edit::TextEdit;
16 17
@@ -62,12 +63,18 @@ pub struct CompletionItem {
62 /// after completion. 63 /// after completion.
63 trigger_call_info: bool, 64 trigger_call_info: bool,
64 65
65 /// Score is useful to pre select or display in better order completion items 66 /// We use this to sort completion. Relevance records facts like "do the
66 score: Option<CompletionScore>, 67 /// types align precisely?". We can't sort by relevances directly, they are
68 /// only partially ordered.
69 ///
70 /// Note that Relevance ignores fuzzy match score. We compute Relevance for
71 /// all possible items, and then separately build an ordered completion list
72 /// based on relevance and fuzzy matching with the already typed identifier.
73 relevance: CompletionRelevance,
67 74
68 /// Indicates that a reference or mutable reference to this variable is a 75 /// Indicates that a reference or mutable reference to this variable is a
69 /// possible match. 76 /// possible match.
70 ref_match: Option<(Mutability, CompletionScore)>, 77 ref_match: Option<Mutability>,
71 78
72 /// The import data to add to completion's edits. 79 /// The import data to add to completion's edits.
73 import_to_add: Option<ImportEdit>, 80 import_to_add: Option<ImportEdit>,
@@ -100,8 +107,13 @@ impl fmt::Debug for CompletionItem {
100 if self.deprecated { 107 if self.deprecated {
101 s.field("deprecated", &true); 108 s.field("deprecated", &true);
102 } 109 }
103 if let Some(score) = &self.score { 110
104 s.field("score", score); 111 if self.relevance != CompletionRelevance::default() {
112 s.field("relevance", &self.relevance);
113 }
114
115 if let Some(mutability) = &self.ref_match {
116 s.field("ref_match", &format!("&{}", mutability.as_keyword_for_ref()));
105 } 117 }
106 if self.trigger_call_info { 118 if self.trigger_call_info {
107 s.field("trigger_call_info", &true); 119 s.field("trigger_call_info", &true);
@@ -110,12 +122,59 @@ impl fmt::Debug for CompletionItem {
110 } 122 }
111} 123}
112 124
113#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] 125#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)]
114pub enum CompletionScore { 126pub struct CompletionRelevance {
115 /// If only type match 127 /// This is set in cases like these:
116 TypeMatch, 128 ///
117 /// If type and name match 129 /// ```
118 TypeAndNameMatch, 130 /// fn f(spam: String) {}
131 /// fn main {
132 /// let spam = 92;
133 /// f($0) // name of local matches the name of param
134 /// }
135 /// ```
136 pub exact_name_match: bool,
137 /// This is set in cases like these:
138 ///
139 /// ```
140 /// fn f(spam: String) {}
141 /// fn main {
142 /// let foo = String::new();
143 /// f($0) // type of local matches the type of param
144 /// }
145 /// ```
146 pub exact_type_match: bool,
147}
148
149impl CompletionRelevance {
150 /// Provides a relevance score. Higher values are more relevant.
151 ///
152 /// The absolute value of the relevance score is not meaningful, for
153 /// example a value of 0 doesn't mean "not relevant", rather
154 /// it means "least relevant". The score value should only be used
155 /// for relative ordering.
156 ///
157 /// See is_relevant if you need to make some judgement about score
158 /// in an absolute sense.
159 pub fn score(&self) -> u32 {
160 let mut score = 0;
161
162 if self.exact_name_match {
163 score += 1;
164 }
165 if self.exact_type_match {
166 score += 1;
167 }
168
169 score
170 }
171
172 /// Returns true when the score for this threshold is above
173 /// some threshold such that we think it is especially likely
174 /// to be relevant.
175 pub fn is_relevant(&self) -> bool {
176 self.score() > 0
177 }
119} 178}
120 179
121#[derive(Debug, Clone, Copy, PartialEq, Eq)] 180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -207,9 +266,9 @@ impl CompletionItem {
207 lookup: None, 266 lookup: None,
208 kind: None, 267 kind: None,
209 text_edit: None, 268 text_edit: None,
210 deprecated: None, 269 deprecated: false,
211 trigger_call_info: None, 270 trigger_call_info: None,
212 score: None, 271 relevance: CompletionRelevance::default(),
213 ref_match: None, 272 ref_match: None,
214 import_to_add: None, 273 import_to_add: None,
215 } 274 }
@@ -252,16 +311,22 @@ impl CompletionItem {
252 self.deprecated 311 self.deprecated
253 } 312 }
254 313
255 pub fn score(&self) -> Option<CompletionScore> { 314 pub fn relevance(&self) -> CompletionRelevance {
256 self.score 315 self.relevance
257 } 316 }
258 317
259 pub fn trigger_call_info(&self) -> bool { 318 pub fn trigger_call_info(&self) -> bool {
260 self.trigger_call_info 319 self.trigger_call_info
261 } 320 }
262 321
263 pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> { 322 pub fn ref_match(&self) -> Option<(Mutability, CompletionRelevance)> {
264 self.ref_match 323 // Relevance of the ref match should be the same as the original
324 // match, but with exact type match set because self.ref_match
325 // is only set if there is an exact type match.
326 let mut relevance = self.relevance;
327 relevance.exact_type_match = true;
328
329 self.ref_match.map(|mutability| (mutability, relevance))
265 } 330 }
266 331
267 pub fn import_to_add(&self) -> Option<&ImportEdit> { 332 pub fn import_to_add(&self) -> Option<&ImportEdit> {
@@ -272,22 +337,18 @@ impl CompletionItem {
272/// An extra import to add after the completion is applied. 337/// An extra import to add after the completion is applied.
273#[derive(Debug, Clone)] 338#[derive(Debug, Clone)]
274pub struct ImportEdit { 339pub struct ImportEdit {
275 pub import_path: ModPath, 340 pub import: LocatedImport,
276 pub import_scope: ImportScope, 341 pub scope: ImportScope,
277 pub import_for_trait_assoc_item: bool,
278} 342}
279 343
280impl ImportEdit { 344impl ImportEdit {
281 /// Attempts to insert the import to the given scope, producing a text edit. 345 /// Attempts to insert the import to the given scope, producing a text edit.
282 /// May return no edit in edge cases, such as scope already containing the import. 346 /// May return no edit in edge cases, such as scope already containing the import.
283 pub fn to_text_edit(&self, merge_behavior: Option<MergeBehavior>) -> Option<TextEdit> { 347 pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> {
284 let _p = profile::span("ImportEdit::to_text_edit"); 348 let _p = profile::span("ImportEdit::to_text_edit");
285 349
286 let rewriter = insert_use::insert_use( 350 let rewriter =
287 &self.import_scope, 351 insert_use::insert_use(&self.scope, mod_path_to_ast(&self.import.import_path), cfg);
288 mod_path_to_ast(&self.import_path),
289 merge_behavior,
290 );
291 let old_ast = rewriter.rewrite_root()?; 352 let old_ast = rewriter.rewrite_root()?;
292 let mut import_insert = TextEdit::builder(); 353 let mut import_insert = TextEdit::builder();
293 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); 354 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
@@ -311,10 +372,10 @@ pub(crate) struct Builder {
311 lookup: Option<String>, 372 lookup: Option<String>,
312 kind: Option<CompletionItemKind>, 373 kind: Option<CompletionItemKind>,
313 text_edit: Option<TextEdit>, 374 text_edit: Option<TextEdit>,
314 deprecated: Option<bool>, 375 deprecated: bool,
315 trigger_call_info: Option<bool>, 376 trigger_call_info: Option<bool>,
316 score: Option<CompletionScore>, 377 relevance: CompletionRelevance,
317 ref_match: Option<(Mutability, CompletionScore)>, 378 ref_match: Option<Mutability>,
318} 379}
319 380
320impl Builder { 381impl Builder {
@@ -325,20 +386,19 @@ impl Builder {
325 let mut lookup = self.lookup; 386 let mut lookup = self.lookup;
326 let mut insert_text = self.insert_text; 387 let mut insert_text = self.insert_text;
327 388
328 if let Some(import_to_add) = self.import_to_add.as_ref() { 389 if let Some(original_path) = self
329 if import_to_add.import_for_trait_assoc_item { 390 .import_to_add
330 lookup = lookup.or_else(|| Some(label.clone())); 391 .as_ref()
331 insert_text = insert_text.or_else(|| Some(label.clone())); 392 .and_then(|import_edit| import_edit.import.original_path.as_ref())
332 label = format!("{} ({})", label, import_to_add.import_path); 393 {
394 lookup = lookup.or_else(|| Some(label.clone()));
395 insert_text = insert_text.or_else(|| Some(label.clone()));
396
397 let original_path_label = original_path.to_string();
398 if original_path_label.ends_with(&label) {
399 label = original_path_label;
333 } else { 400 } else {
334 let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); 401 format_to!(label, " ({})", original_path)
335 let _ = import_path_without_last_segment.pop_segment();
336
337 if !import_path_without_last_segment.segments().is_empty() {
338 lookup = lookup.or_else(|| Some(label.clone()));
339 insert_text = insert_text.or_else(|| Some(label.clone()));
340 label = format!("{}::{}", import_path_without_last_segment, label);
341 }
342 } 402 }
343 } 403 }
344 404
@@ -359,49 +419,49 @@ impl Builder {
359 lookup, 419 lookup,
360 kind: self.kind, 420 kind: self.kind,
361 completion_kind: self.completion_kind, 421 completion_kind: self.completion_kind,
362 deprecated: self.deprecated.unwrap_or(false), 422 deprecated: self.deprecated,
363 trigger_call_info: self.trigger_call_info.unwrap_or(false), 423 trigger_call_info: self.trigger_call_info.unwrap_or(false),
364 score: self.score, 424 relevance: self.relevance,
365 ref_match: self.ref_match, 425 ref_match: self.ref_match,
366 import_to_add: self.import_to_add, 426 import_to_add: self.import_to_add,
367 } 427 }
368 } 428 }
369 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { 429 pub(crate) fn lookup_by(&mut self, lookup: impl Into<String>) -> &mut Builder {
370 self.lookup = Some(lookup.into()); 430 self.lookup = Some(lookup.into());
371 self 431 self
372 } 432 }
373 pub(crate) fn label(mut self, label: impl Into<String>) -> Builder { 433 pub(crate) fn label(&mut self, label: impl Into<String>) -> &mut Builder {
374 self.label = label.into(); 434 self.label = label.into();
375 self 435 self
376 } 436 }
377 pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder { 437 pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
378 self.insert_text = Some(insert_text.into()); 438 self.insert_text = Some(insert_text.into());
379 self 439 self
380 } 440 }
381 pub(crate) fn insert_snippet( 441 pub(crate) fn insert_snippet(
382 mut self, 442 &mut self,
383 _cap: SnippetCap, 443 _cap: SnippetCap,
384 snippet: impl Into<String>, 444 snippet: impl Into<String>,
385 ) -> Builder { 445 ) -> &mut Builder {
386 self.insert_text_format = InsertTextFormat::Snippet; 446 self.insert_text_format = InsertTextFormat::Snippet;
387 self.insert_text(snippet) 447 self.insert_text(snippet)
388 } 448 }
389 pub(crate) fn kind(mut self, kind: impl Into<CompletionItemKind>) -> Builder { 449 pub(crate) fn kind(&mut self, kind: impl Into<CompletionItemKind>) -> &mut Builder {
390 self.kind = Some(kind.into()); 450 self.kind = Some(kind.into());
391 self 451 self
392 } 452 }
393 pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder { 453 pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
394 self.text_edit = Some(edit); 454 self.text_edit = Some(edit);
395 self 455 self
396 } 456 }
397 pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder { 457 pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
398 self.insert_text_format = InsertTextFormat::Snippet; 458 self.insert_text_format = InsertTextFormat::Snippet;
399 self.text_edit(edit) 459 self.text_edit(edit)
400 } 460 }
401 pub(crate) fn detail(self, detail: impl Into<String>) -> Builder { 461 pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
402 self.set_detail(Some(detail)) 462 self.set_detail(Some(detail))
403 } 463 }
404 pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder { 464 pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder {
405 self.detail = detail.map(Into::into); 465 self.detail = detail.map(Into::into);
406 if let Some(detail) = &self.detail { 466 if let Some(detail) = &self.detail {
407 if never!(detail.contains('\n'), "multiline detail:\n{}", detail) { 467 if never!(detail.contains('\n'), "multiline detail:\n{}", detail) {
@@ -411,40 +471,91 @@ impl Builder {
411 self 471 self
412 } 472 }
413 #[allow(unused)] 473 #[allow(unused)]
414 pub(crate) fn documentation(self, docs: Documentation) -> Builder { 474 pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder {
415 self.set_documentation(Some(docs)) 475 self.set_documentation(Some(docs))
416 } 476 }
417 pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder { 477 pub(crate) fn set_documentation(&mut self, docs: Option<Documentation>) -> &mut Builder {
418 self.documentation = docs.map(Into::into); 478 self.documentation = docs.map(Into::into);
419 self 479 self
420 } 480 }
421 pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { 481 pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
422 self.deprecated = Some(deprecated); 482 self.deprecated = deprecated;
423 self 483 self
424 } 484 }
425 pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder { 485 pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
426 self.score = Some(score); 486 self.relevance = relevance;
427 self 487 self
428 } 488 }
429 pub(crate) fn trigger_call_info(mut self) -> Builder { 489 pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
430 self.trigger_call_info = Some(true); 490 self.trigger_call_info = Some(true);
431 self 491 self
432 } 492 }
433 pub(crate) fn add_import(mut self, import_to_add: Option<ImportEdit>) -> Builder { 493 pub(crate) fn add_import(&mut self, import_to_add: Option<ImportEdit>) -> &mut Builder {
434 self.import_to_add = import_to_add; 494 self.import_to_add = import_to_add;
435 self 495 self
436 } 496 }
437 pub(crate) fn set_ref_match( 497 pub(crate) fn ref_match(&mut self, mutability: Mutability) -> &mut Builder {
438 mut self, 498 self.ref_match = Some(mutability);
439 ref_match: Option<(Mutability, CompletionScore)>,
440 ) -> Builder {
441 self.ref_match = ref_match;
442 self 499 self
443 } 500 }
444} 501}
445 502
446impl<'a> Into<CompletionItem> for Builder { 503#[cfg(test)]
447 fn into(self) -> CompletionItem { 504mod tests {
448 self.build() 505 use itertools::Itertools;
506 use test_utils::assert_eq_text;
507
508 use super::CompletionRelevance;
509
510 /// Check that these are CompletionRelevance are sorted in ascending order
511 /// by their relevance score.
512 ///
513 /// We want to avoid making assertions about the absolute score of any
514 /// item, but we do want to assert whether each is >, <, or == to the
515 /// others.
516 ///
517 /// If provided vec![vec![a], vec![b, c], vec![d]], then this will assert:
518 /// a.score < b.score == c.score < d.score
519 fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) {
520 let expected = format!("{:#?}", &expected_relevance_order);
521
522 let actual_relevance_order = expected_relevance_order
523 .into_iter()
524 .flatten()
525 .map(|r| (r.score(), r))
526 .sorted_by_key(|(score, _r)| *score)
527 .fold(
528 (u32::MIN, vec![vec![]]),
529 |(mut currently_collecting_score, mut out), (score, r)| {
530 if currently_collecting_score == score {
531 out.last_mut().unwrap().push(r);
532 } else {
533 currently_collecting_score = score;
534 out.push(vec![r]);
535 }
536 (currently_collecting_score, out)
537 },
538 )
539 .1;
540
541 let actual = format!("{:#?}", &actual_relevance_order);
542
543 assert_eq_text!(&expected, &actual);
544 }
545
546 #[test]
547 fn relevance_score() {
548 // This test asserts that the relevance score for these items is ascending, and
549 // that any items in the same vec have the same score.
550 let expected_relevance_order = vec![
551 vec![CompletionRelevance::default()],
552 vec![
553 CompletionRelevance { exact_name_match: true, ..CompletionRelevance::default() },
554 CompletionRelevance { exact_type_match: true, ..CompletionRelevance::default() },
555 ],
556 vec![CompletionRelevance { exact_name_match: true, exact_type_match: true }],
557 ];
558
559 check_relevance_score_ordered(expected_relevance_order);
449 } 560 }
450} 561}