diff options
Diffstat (limited to 'crates/ide_completion/src/item.rs')
-rw-r--r-- | crates/ide_completion/src/item.rs | 188 |
1 files changed, 153 insertions, 35 deletions
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs index 14afec603..9a4b5217a 100644 --- a/crates/ide_completion/src/item.rs +++ b/crates/ide_completion/src/item.rs | |||
@@ -70,7 +70,7 @@ pub struct CompletionItem { | |||
70 | /// Note that Relevance ignores fuzzy match score. We compute Relevance for | 70 | /// Note that Relevance ignores fuzzy match score. We compute Relevance for |
71 | /// all possible items, and then separately build an ordered completion list | 71 | /// all possible items, and then separately build an ordered completion list |
72 | /// based on relevance and fuzzy matching with the already typed identifier. | 72 | /// based on relevance and fuzzy matching with the already typed identifier. |
73 | relevance: Relevance, | 73 | relevance: CompletionRelevance, |
74 | 74 | ||
75 | /// 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 |
76 | /// possible match. | 76 | /// possible match. |
@@ -107,9 +107,11 @@ impl fmt::Debug for CompletionItem { | |||
107 | if self.deprecated { | 107 | if self.deprecated { |
108 | s.field("deprecated", &true); | 108 | s.field("deprecated", &true); |
109 | } | 109 | } |
110 | if self.relevance.is_relevant() { | 110 | |
111 | if self.relevance != CompletionRelevance::default() { | ||
111 | s.field("relevance", &self.relevance); | 112 | s.field("relevance", &self.relevance); |
112 | } | 113 | } |
114 | |||
113 | if let Some(mutability) = &self.ref_match { | 115 | if let Some(mutability) = &self.ref_match { |
114 | s.field("ref_match", &format!("&{}", mutability.as_keyword_for_ref())); | 116 | s.field("ref_match", &format!("&{}", mutability.as_keyword_for_ref())); |
115 | } | 117 | } |
@@ -120,16 +122,8 @@ impl fmt::Debug for CompletionItem { | |||
120 | } | 122 | } |
121 | } | 123 | } |
122 | 124 | ||
123 | #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] | ||
124 | pub enum CompletionScore { | ||
125 | /// If only type match | ||
126 | TypeMatch, | ||
127 | /// If type and name match | ||
128 | TypeAndNameMatch, | ||
129 | } | ||
130 | |||
131 | #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)] | 125 | #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)] |
132 | pub struct Relevance { | 126 | pub struct CompletionRelevance { |
133 | /// This is set in cases like these: | 127 | /// This is set in cases like these: |
134 | /// | 128 | /// |
135 | /// ``` | 129 | /// ``` |
@@ -150,11 +144,54 @@ pub struct Relevance { | |||
150 | /// } | 144 | /// } |
151 | /// ``` | 145 | /// ``` |
152 | pub exact_type_match: bool, | 146 | pub exact_type_match: bool, |
147 | /// This is set in cases like these: | ||
148 | /// | ||
149 | /// ``` | ||
150 | /// fn foo(bar: u32) { | ||
151 | /// $0 // `bar` is local | ||
152 | /// } | ||
153 | /// ``` | ||
154 | /// | ||
155 | /// ``` | ||
156 | /// fn foo() { | ||
157 | /// let bar = 0; | ||
158 | /// $0 // `bar` is local | ||
159 | /// } | ||
160 | /// ``` | ||
161 | pub is_local: bool, | ||
153 | } | 162 | } |
154 | 163 | ||
155 | impl Relevance { | 164 | impl CompletionRelevance { |
165 | /// Provides a relevance score. Higher values are more relevant. | ||
166 | /// | ||
167 | /// The absolute value of the relevance score is not meaningful, for | ||
168 | /// example a value of 0 doesn't mean "not relevant", rather | ||
169 | /// it means "least relevant". The score value should only be used | ||
170 | /// for relative ordering. | ||
171 | /// | ||
172 | /// See is_relevant if you need to make some judgement about score | ||
173 | /// in an absolute sense. | ||
174 | pub fn score(&self) -> u32 { | ||
175 | let mut score = 0; | ||
176 | |||
177 | if self.exact_name_match { | ||
178 | score += 1; | ||
179 | } | ||
180 | if self.exact_type_match { | ||
181 | score += 3; | ||
182 | } | ||
183 | if self.is_local { | ||
184 | score += 1; | ||
185 | } | ||
186 | |||
187 | score | ||
188 | } | ||
189 | |||
190 | /// Returns true when the score for this threshold is above | ||
191 | /// some threshold such that we think it is especially likely | ||
192 | /// to be relevant. | ||
156 | pub fn is_relevant(&self) -> bool { | 193 | pub fn is_relevant(&self) -> bool { |
157 | self != &Relevance::default() | 194 | self.score() > 0 |
158 | } | 195 | } |
159 | } | 196 | } |
160 | 197 | ||
@@ -249,7 +286,7 @@ impl CompletionItem { | |||
249 | text_edit: None, | 286 | text_edit: None, |
250 | deprecated: false, | 287 | deprecated: false, |
251 | trigger_call_info: None, | 288 | trigger_call_info: None, |
252 | relevance: Relevance::default(), | 289 | relevance: CompletionRelevance::default(), |
253 | ref_match: None, | 290 | ref_match: None, |
254 | import_to_add: None, | 291 | import_to_add: None, |
255 | } | 292 | } |
@@ -292,7 +329,7 @@ impl CompletionItem { | |||
292 | self.deprecated | 329 | self.deprecated |
293 | } | 330 | } |
294 | 331 | ||
295 | pub fn relevance(&self) -> Relevance { | 332 | pub fn relevance(&self) -> CompletionRelevance { |
296 | self.relevance | 333 | self.relevance |
297 | } | 334 | } |
298 | 335 | ||
@@ -300,8 +337,14 @@ impl CompletionItem { | |||
300 | self.trigger_call_info | 337 | self.trigger_call_info |
301 | } | 338 | } |
302 | 339 | ||
303 | pub fn ref_match(&self) -> Option<Mutability> { | 340 | pub fn ref_match(&self) -> Option<(Mutability, CompletionRelevance)> { |
304 | self.ref_match | 341 | // Relevance of the ref match should be the same as the original |
342 | // match, but with exact type match set because self.ref_match | ||
343 | // is only set if there is an exact type match. | ||
344 | let mut relevance = self.relevance; | ||
345 | relevance.exact_type_match = true; | ||
346 | |||
347 | self.ref_match.map(|mutability| (mutability, relevance)) | ||
305 | } | 348 | } |
306 | 349 | ||
307 | pub fn import_to_add(&self) -> Option<&ImportEdit> { | 350 | pub fn import_to_add(&self) -> Option<&ImportEdit> { |
@@ -349,7 +392,7 @@ pub(crate) struct Builder { | |||
349 | text_edit: Option<TextEdit>, | 392 | text_edit: Option<TextEdit>, |
350 | deprecated: bool, | 393 | deprecated: bool, |
351 | trigger_call_info: Option<bool>, | 394 | trigger_call_info: Option<bool>, |
352 | relevance: Relevance, | 395 | relevance: CompletionRelevance, |
353 | ref_match: Option<Mutability>, | 396 | ref_match: Option<Mutability>, |
354 | } | 397 | } |
355 | 398 | ||
@@ -401,42 +444,42 @@ impl Builder { | |||
401 | import_to_add: self.import_to_add, | 444 | import_to_add: self.import_to_add, |
402 | } | 445 | } |
403 | } | 446 | } |
404 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | 447 | pub(crate) fn lookup_by(&mut self, lookup: impl Into<String>) -> &mut Builder { |
405 | self.lookup = Some(lookup.into()); | 448 | self.lookup = Some(lookup.into()); |
406 | self | 449 | self |
407 | } | 450 | } |
408 | pub(crate) fn label(mut self, label: impl Into<String>) -> Builder { | 451 | pub(crate) fn label(&mut self, label: impl Into<String>) -> &mut Builder { |
409 | self.label = label.into(); | 452 | self.label = label.into(); |
410 | self | 453 | self |
411 | } | 454 | } |
412 | pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder { | 455 | pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder { |
413 | self.insert_text = Some(insert_text.into()); | 456 | self.insert_text = Some(insert_text.into()); |
414 | self | 457 | self |
415 | } | 458 | } |
416 | pub(crate) fn insert_snippet( | 459 | pub(crate) fn insert_snippet( |
417 | mut self, | 460 | &mut self, |
418 | _cap: SnippetCap, | 461 | _cap: SnippetCap, |
419 | snippet: impl Into<String>, | 462 | snippet: impl Into<String>, |
420 | ) -> Builder { | 463 | ) -> &mut Builder { |
421 | self.insert_text_format = InsertTextFormat::Snippet; | 464 | self.insert_text_format = InsertTextFormat::Snippet; |
422 | self.insert_text(snippet) | 465 | self.insert_text(snippet) |
423 | } | 466 | } |
424 | pub(crate) fn kind(mut self, kind: impl Into<CompletionItemKind>) -> Builder { | 467 | pub(crate) fn kind(&mut self, kind: impl Into<CompletionItemKind>) -> &mut Builder { |
425 | self.kind = Some(kind.into()); | 468 | self.kind = Some(kind.into()); |
426 | self | 469 | self |
427 | } | 470 | } |
428 | pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder { | 471 | pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder { |
429 | self.text_edit = Some(edit); | 472 | self.text_edit = Some(edit); |
430 | self | 473 | self |
431 | } | 474 | } |
432 | pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder { | 475 | pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder { |
433 | self.insert_text_format = InsertTextFormat::Snippet; | 476 | self.insert_text_format = InsertTextFormat::Snippet; |
434 | self.text_edit(edit) | 477 | self.text_edit(edit) |
435 | } | 478 | } |
436 | pub(crate) fn detail(self, detail: impl Into<String>) -> Builder { | 479 | pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder { |
437 | self.set_detail(Some(detail)) | 480 | self.set_detail(Some(detail)) |
438 | } | 481 | } |
439 | pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder { | 482 | pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder { |
440 | self.detail = detail.map(Into::into); | 483 | self.detail = detail.map(Into::into); |
441 | if let Some(detail) = &self.detail { | 484 | if let Some(detail) = &self.detail { |
442 | if never!(detail.contains('\n'), "multiline detail:\n{}", detail) { | 485 | if never!(detail.contains('\n'), "multiline detail:\n{}", detail) { |
@@ -446,31 +489,106 @@ impl Builder { | |||
446 | self | 489 | self |
447 | } | 490 | } |
448 | #[allow(unused)] | 491 | #[allow(unused)] |
449 | pub(crate) fn documentation(self, docs: Documentation) -> Builder { | 492 | pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder { |
450 | self.set_documentation(Some(docs)) | 493 | self.set_documentation(Some(docs)) |
451 | } | 494 | } |
452 | pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder { | 495 | pub(crate) fn set_documentation(&mut self, docs: Option<Documentation>) -> &mut Builder { |
453 | self.documentation = docs.map(Into::into); | 496 | self.documentation = docs.map(Into::into); |
454 | self | 497 | self |
455 | } | 498 | } |
456 | pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { | 499 | pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder { |
457 | self.deprecated = deprecated; | 500 | self.deprecated = deprecated; |
458 | self | 501 | self |
459 | } | 502 | } |
460 | pub(crate) fn set_relevance(mut self, relevance: Relevance) -> Builder { | 503 | pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder { |
461 | self.relevance = relevance; | 504 | self.relevance = relevance; |
462 | self | 505 | self |
463 | } | 506 | } |
464 | pub(crate) fn trigger_call_info(mut self) -> Builder { | 507 | pub(crate) fn trigger_call_info(&mut self) -> &mut Builder { |
465 | self.trigger_call_info = Some(true); | 508 | self.trigger_call_info = Some(true); |
466 | self | 509 | self |
467 | } | 510 | } |
468 | pub(crate) fn add_import(mut self, import_to_add: Option<ImportEdit>) -> Builder { | 511 | pub(crate) fn add_import(&mut self, import_to_add: Option<ImportEdit>) -> &mut Builder { |
469 | self.import_to_add = import_to_add; | 512 | self.import_to_add = import_to_add; |
470 | self | 513 | self |
471 | } | 514 | } |
472 | pub(crate) fn ref_match(mut self, mutability: Mutability) -> Builder { | 515 | pub(crate) fn ref_match(&mut self, mutability: Mutability) -> &mut Builder { |
473 | self.ref_match = Some(mutability); | 516 | self.ref_match = Some(mutability); |
474 | self | 517 | self |
475 | } | 518 | } |
476 | } | 519 | } |
520 | |||
521 | #[cfg(test)] | ||
522 | mod tests { | ||
523 | use itertools::Itertools; | ||
524 | use test_utils::assert_eq_text; | ||
525 | |||
526 | use super::CompletionRelevance; | ||
527 | |||
528 | /// Check that these are CompletionRelevance are sorted in ascending order | ||
529 | /// by their relevance score. | ||
530 | /// | ||
531 | /// We want to avoid making assertions about the absolute score of any | ||
532 | /// item, but we do want to assert whether each is >, <, or == to the | ||
533 | /// others. | ||
534 | /// | ||
535 | /// If provided vec![vec![a], vec![b, c], vec![d]], then this will assert: | ||
536 | /// a.score < b.score == c.score < d.score | ||
537 | fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) { | ||
538 | let expected = format!("{:#?}", &expected_relevance_order); | ||
539 | |||
540 | let actual_relevance_order = expected_relevance_order | ||
541 | .into_iter() | ||
542 | .flatten() | ||
543 | .map(|r| (r.score(), r)) | ||
544 | .sorted_by_key(|(score, _r)| *score) | ||
545 | .fold( | ||
546 | (u32::MIN, vec![vec![]]), | ||
547 | |(mut currently_collecting_score, mut out), (score, r)| { | ||
548 | if currently_collecting_score == score { | ||
549 | out.last_mut().unwrap().push(r); | ||
550 | } else { | ||
551 | currently_collecting_score = score; | ||
552 | out.push(vec![r]); | ||
553 | } | ||
554 | (currently_collecting_score, out) | ||
555 | }, | ||
556 | ) | ||
557 | .1; | ||
558 | |||
559 | let actual = format!("{:#?}", &actual_relevance_order); | ||
560 | |||
561 | assert_eq_text!(&expected, &actual); | ||
562 | } | ||
563 | |||
564 | #[test] | ||
565 | fn relevance_score() { | ||
566 | // This test asserts that the relevance score for these items is ascending, and | ||
567 | // that any items in the same vec have the same score. | ||
568 | let expected_relevance_order = vec![ | ||
569 | vec![CompletionRelevance::default()], | ||
570 | vec![ | ||
571 | CompletionRelevance { exact_name_match: true, ..CompletionRelevance::default() }, | ||
572 | CompletionRelevance { is_local: true, ..CompletionRelevance::default() }, | ||
573 | ], | ||
574 | vec![CompletionRelevance { | ||
575 | exact_name_match: true, | ||
576 | is_local: true, | ||
577 | ..CompletionRelevance::default() | ||
578 | }], | ||
579 | vec![CompletionRelevance { exact_type_match: true, ..CompletionRelevance::default() }], | ||
580 | vec![CompletionRelevance { | ||
581 | exact_name_match: true, | ||
582 | exact_type_match: true, | ||
583 | ..CompletionRelevance::default() | ||
584 | }], | ||
585 | vec![CompletionRelevance { | ||
586 | exact_name_match: true, | ||
587 | exact_type_match: true, | ||
588 | is_local: true, | ||
589 | }], | ||
590 | ]; | ||
591 | |||
592 | check_relevance_score_ordered(expected_relevance_order); | ||
593 | } | ||
594 | } | ||