diff options
Diffstat (limited to 'crates/ide_completion/src/item.rs')
-rw-r--r-- | crates/ide_completion/src/item.rs | 211 |
1 files changed, 166 insertions, 45 deletions
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs index 9b039e3e5..3febab32b 100644 --- a/crates/ide_completion/src/item.rs +++ b/crates/ide_completion/src/item.rs | |||
@@ -63,12 +63,18 @@ pub struct CompletionItem { | |||
63 | /// after completion. | 63 | /// after completion. |
64 | trigger_call_info: bool, | 64 | trigger_call_info: bool, |
65 | 65 | ||
66 | /// 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 |
67 | 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, | ||
68 | 74 | ||
69 | /// 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 |
70 | /// possible match. | 76 | /// possible match. |
71 | ref_match: Option<(Mutability, CompletionScore)>, | 77 | ref_match: Option<Mutability>, |
72 | 78 | ||
73 | /// The import data to add to completion's edits. | 79 | /// The import data to add to completion's edits. |
74 | import_to_add: Option<ImportEdit>, | 80 | import_to_add: Option<ImportEdit>, |
@@ -101,8 +107,13 @@ impl fmt::Debug for CompletionItem { | |||
101 | if self.deprecated { | 107 | if self.deprecated { |
102 | s.field("deprecated", &true); | 108 | s.field("deprecated", &true); |
103 | } | 109 | } |
104 | if let Some(score) = &self.score { | 110 | |
105 | 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())); | ||
106 | } | 117 | } |
107 | if self.trigger_call_info { | 118 | if self.trigger_call_info { |
108 | s.field("trigger_call_info", &true); | 119 | s.field("trigger_call_info", &true); |
@@ -111,12 +122,59 @@ impl fmt::Debug for CompletionItem { | |||
111 | } | 122 | } |
112 | } | 123 | } |
113 | 124 | ||
114 | #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] | 125 | #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)] |
115 | pub enum CompletionScore { | 126 | pub struct CompletionRelevance { |
116 | /// If only type match | 127 | /// This is set in cases like these: |
117 | TypeMatch, | 128 | /// |
118 | /// If type and name match | 129 | /// ``` |
119 | 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 | |||
149 | impl 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 | } | ||
120 | } | 178 | } |
121 | 179 | ||
122 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 180 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
@@ -208,9 +266,9 @@ impl CompletionItem { | |||
208 | lookup: None, | 266 | lookup: None, |
209 | kind: None, | 267 | kind: None, |
210 | text_edit: None, | 268 | text_edit: None, |
211 | deprecated: None, | 269 | deprecated: false, |
212 | trigger_call_info: None, | 270 | trigger_call_info: None, |
213 | score: None, | 271 | relevance: CompletionRelevance::default(), |
214 | ref_match: None, | 272 | ref_match: None, |
215 | import_to_add: None, | 273 | import_to_add: None, |
216 | } | 274 | } |
@@ -253,16 +311,22 @@ impl CompletionItem { | |||
253 | self.deprecated | 311 | self.deprecated |
254 | } | 312 | } |
255 | 313 | ||
256 | pub fn score(&self) -> Option<CompletionScore> { | 314 | pub fn relevance(&self) -> CompletionRelevance { |
257 | self.score | 315 | self.relevance |
258 | } | 316 | } |
259 | 317 | ||
260 | pub fn trigger_call_info(&self) -> bool { | 318 | pub fn trigger_call_info(&self) -> bool { |
261 | self.trigger_call_info | 319 | self.trigger_call_info |
262 | } | 320 | } |
263 | 321 | ||
264 | pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> { | 322 | pub fn ref_match(&self) -> Option<(Mutability, CompletionRelevance)> { |
265 | 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)) | ||
266 | } | 330 | } |
267 | 331 | ||
268 | pub fn import_to_add(&self) -> Option<&ImportEdit> { | 332 | pub fn import_to_add(&self) -> Option<&ImportEdit> { |
@@ -308,10 +372,10 @@ pub(crate) struct Builder { | |||
308 | lookup: Option<String>, | 372 | lookup: Option<String>, |
309 | kind: Option<CompletionItemKind>, | 373 | kind: Option<CompletionItemKind>, |
310 | text_edit: Option<TextEdit>, | 374 | text_edit: Option<TextEdit>, |
311 | deprecated: Option<bool>, | 375 | deprecated: bool, |
312 | trigger_call_info: Option<bool>, | 376 | trigger_call_info: Option<bool>, |
313 | score: Option<CompletionScore>, | 377 | relevance: CompletionRelevance, |
314 | ref_match: Option<(Mutability, CompletionScore)>, | 378 | ref_match: Option<Mutability>, |
315 | } | 379 | } |
316 | 380 | ||
317 | impl Builder { | 381 | impl Builder { |
@@ -355,49 +419,49 @@ impl Builder { | |||
355 | lookup, | 419 | lookup, |
356 | kind: self.kind, | 420 | kind: self.kind, |
357 | completion_kind: self.completion_kind, | 421 | completion_kind: self.completion_kind, |
358 | deprecated: self.deprecated.unwrap_or(false), | 422 | deprecated: self.deprecated, |
359 | trigger_call_info: self.trigger_call_info.unwrap_or(false), | 423 | trigger_call_info: self.trigger_call_info.unwrap_or(false), |
360 | score: self.score, | 424 | relevance: self.relevance, |
361 | ref_match: self.ref_match, | 425 | ref_match: self.ref_match, |
362 | import_to_add: self.import_to_add, | 426 | import_to_add: self.import_to_add, |
363 | } | 427 | } |
364 | } | 428 | } |
365 | 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 { |
366 | self.lookup = Some(lookup.into()); | 430 | self.lookup = Some(lookup.into()); |
367 | self | 431 | self |
368 | } | 432 | } |
369 | pub(crate) fn label(mut self, label: impl Into<String>) -> Builder { | 433 | pub(crate) fn label(&mut self, label: impl Into<String>) -> &mut Builder { |
370 | self.label = label.into(); | 434 | self.label = label.into(); |
371 | self | 435 | self |
372 | } | 436 | } |
373 | 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 { |
374 | self.insert_text = Some(insert_text.into()); | 438 | self.insert_text = Some(insert_text.into()); |
375 | self | 439 | self |
376 | } | 440 | } |
377 | pub(crate) fn insert_snippet( | 441 | pub(crate) fn insert_snippet( |
378 | mut self, | 442 | &mut self, |
379 | _cap: SnippetCap, | 443 | _cap: SnippetCap, |
380 | snippet: impl Into<String>, | 444 | snippet: impl Into<String>, |
381 | ) -> Builder { | 445 | ) -> &mut Builder { |
382 | self.insert_text_format = InsertTextFormat::Snippet; | 446 | self.insert_text_format = InsertTextFormat::Snippet; |
383 | self.insert_text(snippet) | 447 | self.insert_text(snippet) |
384 | } | 448 | } |
385 | pub(crate) fn kind(mut self, kind: impl Into<CompletionItemKind>) -> Builder { | 449 | pub(crate) fn kind(&mut self, kind: impl Into<CompletionItemKind>) -> &mut Builder { |
386 | self.kind = Some(kind.into()); | 450 | self.kind = Some(kind.into()); |
387 | self | 451 | self |
388 | } | 452 | } |
389 | pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder { | 453 | pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder { |
390 | self.text_edit = Some(edit); | 454 | self.text_edit = Some(edit); |
391 | self | 455 | self |
392 | } | 456 | } |
393 | 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 { |
394 | self.insert_text_format = InsertTextFormat::Snippet; | 458 | self.insert_text_format = InsertTextFormat::Snippet; |
395 | self.text_edit(edit) | 459 | self.text_edit(edit) |
396 | } | 460 | } |
397 | pub(crate) fn detail(self, detail: impl Into<String>) -> Builder { | 461 | pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder { |
398 | self.set_detail(Some(detail)) | 462 | self.set_detail(Some(detail)) |
399 | } | 463 | } |
400 | 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 { |
401 | self.detail = detail.map(Into::into); | 465 | self.detail = detail.map(Into::into); |
402 | if let Some(detail) = &self.detail { | 466 | if let Some(detail) = &self.detail { |
403 | if never!(detail.contains('\n'), "multiline detail:\n{}", detail) { | 467 | if never!(detail.contains('\n'), "multiline detail:\n{}", detail) { |
@@ -407,34 +471,91 @@ impl Builder { | |||
407 | self | 471 | self |
408 | } | 472 | } |
409 | #[allow(unused)] | 473 | #[allow(unused)] |
410 | pub(crate) fn documentation(self, docs: Documentation) -> Builder { | 474 | pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder { |
411 | self.set_documentation(Some(docs)) | 475 | self.set_documentation(Some(docs)) |
412 | } | 476 | } |
413 | pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder { | 477 | pub(crate) fn set_documentation(&mut self, docs: Option<Documentation>) -> &mut Builder { |
414 | self.documentation = docs.map(Into::into); | 478 | self.documentation = docs.map(Into::into); |
415 | self | 479 | self |
416 | } | 480 | } |
417 | pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { | 481 | pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder { |
418 | self.deprecated = Some(deprecated); | 482 | self.deprecated = deprecated; |
419 | self | 483 | self |
420 | } | 484 | } |
421 | pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder { | 485 | pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder { |
422 | self.score = Some(score); | 486 | self.relevance = relevance; |
423 | self | 487 | self |
424 | } | 488 | } |
425 | pub(crate) fn trigger_call_info(mut self) -> Builder { | 489 | pub(crate) fn trigger_call_info(&mut self) -> &mut Builder { |
426 | self.trigger_call_info = Some(true); | 490 | self.trigger_call_info = Some(true); |
427 | self | 491 | self |
428 | } | 492 | } |
429 | 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 { |
430 | self.import_to_add = import_to_add; | 494 | self.import_to_add = import_to_add; |
431 | self | 495 | self |
432 | } | 496 | } |
433 | pub(crate) fn set_ref_match( | 497 | pub(crate) fn ref_match(&mut self, mutability: Mutability) -> &mut Builder { |
434 | mut self, | 498 | self.ref_match = Some(mutability); |
435 | ref_match: Option<(Mutability, CompletionScore)>, | ||
436 | ) -> Builder { | ||
437 | self.ref_match = ref_match; | ||
438 | self | 499 | self |
439 | } | 500 | } |
440 | } | 501 | } |
502 | |||
503 | #[cfg(test)] | ||
504 | mod tests { | ||
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); | ||
560 | } | ||
561 | } | ||