diff options
Diffstat (limited to 'crates/ide_completion/src/item.rs')
-rw-r--r-- | crates/ide_completion/src/item.rs | 257 |
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 | ||
3 | use std::fmt; | 3 | use std::fmt; |
4 | 4 | ||
5 | use hir::{Documentation, ModPath, Mutability}; | 5 | use hir::{Documentation, Mutability}; |
6 | use ide_db::{ | 6 | use 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 | }; |
13 | use stdx::{impl_from, never}; | 14 | use stdx::{format_to, impl_from, never}; |
14 | use syntax::{algo, TextRange}; | 15 | use syntax::{algo, TextRange}; |
15 | use text_edit::TextEdit; | 16 | use 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)] |
114 | pub enum CompletionScore { | 126 | pub 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 | |||
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 | } | ||
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)] |
274 | pub struct ImportEdit { | 339 | pub 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 | ||
280 | impl ImportEdit { | 344 | impl 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 | ||
320 | impl Builder { | 381 | impl 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 | ||
446 | impl<'a> Into<CompletionItem> for Builder { | 503 | #[cfg(test)] |
447 | fn into(self) -> CompletionItem { | 504 | mod 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 | } |