aboutsummaryrefslogtreecommitdiff
path: root/crates/completion
diff options
context:
space:
mode:
authorKirill Bulatov <[email protected]>2021-01-05 08:34:03 +0000
committerKirill Bulatov <[email protected]>2021-01-16 18:44:12 +0000
commitdb335a1bbf1d1bea2c761f67efb4b49831738e31 (patch)
tree910963c004c460d2f0c322a0e643947aaf7132b8 /crates/completion
parent9a349f280ff1c6d0b57df80aa3d6720474e4b00a (diff)
Add flyimport completion for trait assoc items
Diffstat (limited to 'crates/completion')
-rw-r--r--crates/completion/src/completions/flyimport.rs300
-rw-r--r--crates/completion/src/config.rs2
-rw-r--r--crates/completion/src/item.rs23
-rw-r--r--crates/completion/src/lib.rs3
-rw-r--r--crates/completion/src/render.rs22
-rw-r--r--crates/completion/src/test_utils.rs2
6 files changed, 296 insertions, 56 deletions
diff --git a/crates/completion/src/completions/flyimport.rs b/crates/completion/src/completions/flyimport.rs
index 222809638..9101e405c 100644
--- a/crates/completion/src/completions/flyimport.rs
+++ b/crates/completion/src/completions/flyimport.rs
@@ -45,9 +45,8 @@
45//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding 45//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
46//! capability enabled. 46//! capability enabled.
47 47
48use either::Either;
49use hir::{ModPath, ScopeDef}; 48use hir::{ModPath, ScopeDef};
50use ide_db::{helpers::insert_use::ImportScope, imports_locator}; 49use ide_db::helpers::{import_assets::ImportAssets, insert_use::ImportScope};
51use syntax::AstNode; 50use syntax::AstNode;
52use test_utils::mark; 51use test_utils::mark;
53 52
@@ -60,7 +59,7 @@ use crate::{
60use super::Completions; 59use super::Completions;
61 60
62pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 61pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
63 if !ctx.config.enable_autoimport_completions { 62 if !ctx.config.enable_imports_on_the_fly {
64 return None; 63 return None;
65 } 64 }
66 if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() { 65 if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
@@ -72,46 +71,56 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
72 } 71 }
73 let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string()); 72 let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
74 73
75 let current_module = ctx.scope.module()?; 74 let import_scope =
76 let anchor = ctx.name_ref_syntax.as_ref()?; 75 ImportScope::find_insert_use_container(ctx.name_ref_syntax.as_ref()?.syntax(), &ctx.sema)?;
77 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
78
79 let user_input_lowercased = potential_import_name.to_lowercase(); 76 let user_input_lowercased = potential_import_name.to_lowercase();
80 let mut all_mod_paths = imports_locator::find_similar_imports( 77 let mut all_mod_paths = import_assets(ctx, potential_import_name)?
81 &ctx.sema, 78 .search_for_relative_paths(&ctx.sema)
82 ctx.krate?, 79 .into_iter()
83 Some(40), 80 .map(|(mod_path, item_in_ns)| {
84 potential_import_name, 81 let scope_item = match item_in_ns {
85 true, 82 hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
86 true, 83 hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
87 ) 84 hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
88 .filter_map(|import_candidate| { 85 };
89 Some(match import_candidate { 86 (mod_path, scope_item)
90 Either::Left(module_def) => {
91 (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def))
92 }
93 Either::Right(macro_def) => {
94 (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
95 }
96 }) 87 })
97 }) 88 .collect::<Vec<_>>();
98 .filter(|(mod_path, _)| mod_path.len() > 1)
99 .collect::<Vec<_>>();
100
101 all_mod_paths.sort_by_cached_key(|(mod_path, _)| { 89 all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
102 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased) 90 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
103 }); 91 });
104 92
105 acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| { 93 acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
106 render_resolution_with_import( 94 let import_for_trait_assoc_item = match definition {
107 RenderContext::new(ctx), 95 ScopeDef::ModuleDef(module_def) => module_def
108 ImportEdit { import_path, import_scope: import_scope.clone() }, 96 .as_assoc_item(ctx.db)
109 &definition, 97 .and_then(|assoc| assoc.containing_trait(ctx.db))
110 ) 98 .is_some(),
99 _ => false,
100 };
101 let import_edit = ImportEdit {
102 import_path,
103 import_scope: import_scope.clone(),
104 import_for_trait_assoc_item,
105 };
106 render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
111 })); 107 }));
112 Some(()) 108 Some(())
113} 109}
114 110
111fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
112 let current_module = ctx.scope.module()?;
113 if let Some(dot_receiver) = &ctx.dot_receiver {
114 ImportAssets::for_fuzzy_method_call(
115 current_module,
116 ctx.sema.type_of_expr(dot_receiver)?,
117 fuzzy_name,
118 )
119 } else {
120 ImportAssets::for_fuzzy_path(current_module, ctx.path_qual.clone(), fuzzy_name, &ctx.sema)
121 }
122}
123
115fn compute_fuzzy_completion_order_key( 124fn compute_fuzzy_completion_order_key(
116 proposed_mod_path: &ModPath, 125 proposed_mod_path: &ModPath,
117 user_input_lowercased: &str, 126 user_input_lowercased: &str,
@@ -259,6 +268,176 @@ fn main() {
259 } 268 }
260 269
261 #[test] 270 #[test]
271 fn trait_function_fuzzy_completion() {
272 let fixture = r#"
273 //- /lib.rs crate:dep
274 pub mod test_mod {
275 pub trait TestTrait {
276 const SPECIAL_CONST: u8;
277 type HumbleType;
278 fn weird_function();
279 fn random_method(&self);
280 }
281 pub struct TestStruct {}
282 impl TestTrait for TestStruct {
283 const SPECIAL_CONST: u8 = 42;
284 type HumbleType = ();
285 fn weird_function() {}
286 fn random_method(&self) {}
287 }
288 }
289
290 //- /main.rs crate:main deps:dep
291 fn main() {
292 dep::test_mod::TestStruct::wei$0
293 }
294 "#;
295
296 check(
297 fixture,
298 expect![[r#"
299 fn weird_function() (dep::test_mod::TestTrait) fn weird_function()
300 "#]],
301 );
302
303 check_edit(
304 "weird_function",
305 fixture,
306 r#"
307use dep::test_mod::TestTrait;
308
309fn main() {
310 dep::test_mod::TestStruct::weird_function()$0
311}
312"#,
313 );
314 }
315
316 #[test]
317 fn trait_const_fuzzy_completion() {
318 let fixture = r#"
319 //- /lib.rs crate:dep
320 pub mod test_mod {
321 pub trait TestTrait {
322 const SPECIAL_CONST: u8;
323 type HumbleType;
324 fn weird_function();
325 fn random_method(&self);
326 }
327 pub struct TestStruct {}
328 impl TestTrait for TestStruct {
329 const SPECIAL_CONST: u8 = 42;
330 type HumbleType = ();
331 fn weird_function() {}
332 fn random_method(&self) {}
333 }
334 }
335
336 //- /main.rs crate:main deps:dep
337 fn main() {
338 dep::test_mod::TestStruct::spe$0
339 }
340 "#;
341
342 check(
343 fixture,
344 expect![[r#"
345 ct SPECIAL_CONST (dep::test_mod::TestTrait)
346 "#]],
347 );
348
349 check_edit(
350 "SPECIAL_CONST",
351 fixture,
352 r#"
353use dep::test_mod::TestTrait;
354
355fn main() {
356 dep::test_mod::TestStruct::SPECIAL_CONST
357}
358"#,
359 );
360 }
361
362 #[test]
363 fn trait_method_fuzzy_completion() {
364 let fixture = r#"
365 //- /lib.rs crate:dep
366 pub mod test_mod {
367 pub trait TestTrait {
368 const SPECIAL_CONST: u8;
369 type HumbleType;
370 fn weird_function();
371 fn random_method(&self);
372 }
373 pub struct TestStruct {}
374 impl TestTrait for TestStruct {
375 const SPECIAL_CONST: u8 = 42;
376 type HumbleType = ();
377 fn weird_function() {}
378 fn random_method(&self) {}
379 }
380 }
381
382 //- /main.rs crate:main deps:dep
383 fn main() {
384 let test_struct = dep::test_mod::TestStruct {};
385 test_struct.ran$0
386 }
387 "#;
388
389 check(
390 fixture,
391 expect![[r#"
392 me random_method() (dep::test_mod::TestTrait) fn random_method(&self)
393 "#]],
394 );
395
396 check_edit(
397 "random_method",
398 fixture,
399 r#"
400use dep::test_mod::TestTrait;
401
402fn main() {
403 let test_struct = dep::test_mod::TestStruct {};
404 test_struct.random_method()$0
405}
406"#,
407 );
408 }
409
410 #[test]
411 fn no_trait_type_fuzzy_completion() {
412 check(
413 r#"
414//- /lib.rs crate:dep
415pub mod test_mod {
416 pub trait TestTrait {
417 const SPECIAL_CONST: u8;
418 type HumbleType;
419 fn weird_function();
420 fn random_method(&self);
421 }
422 pub struct TestStruct {}
423 impl TestTrait for TestStruct {
424 const SPECIAL_CONST: u8 = 42;
425 type HumbleType = ();
426 fn weird_function() {}
427 fn random_method(&self) {}
428 }
429}
430
431//- /main.rs crate:main deps:dep
432fn main() {
433 dep::test_mod::TestStruct::hum$0
434}
435"#,
436 expect![[r#""#]],
437 );
438 }
439
440 #[test]
262 fn does_not_propose_names_in_scope() { 441 fn does_not_propose_names_in_scope() {
263 check( 442 check(
264 r#" 443 r#"
@@ -288,4 +467,61 @@ fn main() {
288 expect![[r#""#]], 467 expect![[r#""#]],
289 ); 468 );
290 } 469 }
470
471 #[test]
472 fn does_not_propose_traits_in_scope() {
473 check(
474 r#"
475//- /lib.rs crate:dep
476pub mod test_mod {
477 pub trait TestTrait {
478 const SPECIAL_CONST: u8;
479 type HumbleType;
480 fn weird_function();
481 fn random_method(&self);
482 }
483 pub struct TestStruct {}
484 impl TestTrait for TestStruct {
485 const SPECIAL_CONST: u8 = 42;
486 type HumbleType = ();
487 fn weird_function() {}
488 fn random_method(&self) {}
489 }
490}
491
492//- /main.rs crate:main deps:dep
493use dep::test_mod::{TestStruct, TestTrait};
494fn main() {
495 dep::test_mod::TestStruct::hum$0
496}
497"#,
498 expect![[r#""#]],
499 );
500 }
501
502 #[test]
503 fn blanket_trait_impl_import() {
504 check(
505 r#"
506//- /lib.rs crate:dep
507pub mod test_mod {
508 pub struct TestStruct {}
509 pub trait TestTrait {
510 fn another_function();
511 }
512 impl<T> TestTrait for T {
513 fn another_function() {}
514 }
515}
516
517//- /main.rs crate:main deps:dep
518fn main() {
519 dep::test_mod::TestStruct::ano$0
520}
521"#,
522 expect![[r#"
523 fn another_function() (dep::test_mod::TestTrait) fn another_function()
524 "#]],
525 );
526 }
291} 527}
diff --git a/crates/completion/src/config.rs b/crates/completion/src/config.rs
index 58fc700f3..d70ed6c1c 100644
--- a/crates/completion/src/config.rs
+++ b/crates/completion/src/config.rs
@@ -9,7 +9,7 @@ use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
9#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct CompletionConfig { 10pub struct CompletionConfig {
11 pub enable_postfix_completions: bool, 11 pub enable_postfix_completions: bool,
12 pub enable_autoimport_completions: bool, 12 pub enable_imports_on_the_fly: bool,
13 pub add_call_parenthesis: bool, 13 pub add_call_parenthesis: bool,
14 pub add_call_argument_snippets: bool, 14 pub add_call_argument_snippets: bool,
15 pub snippet_cap: Option<SnippetCap>, 15 pub snippet_cap: Option<SnippetCap>,
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs
index 0134ff219..378bd2c70 100644
--- a/crates/completion/src/item.rs
+++ b/crates/completion/src/item.rs
@@ -270,6 +270,7 @@ impl CompletionItem {
270pub struct ImportEdit { 270pub struct ImportEdit {
271 pub import_path: ModPath, 271 pub import_path: ModPath,
272 pub import_scope: ImportScope, 272 pub import_scope: ImportScope,
273 pub import_for_trait_assoc_item: bool,
273} 274}
274 275
275impl ImportEdit { 276impl ImportEdit {
@@ -321,17 +322,19 @@ impl Builder {
321 let mut insert_text = self.insert_text; 322 let mut insert_text = self.insert_text;
322 323
323 if let Some(import_to_add) = self.import_to_add.as_ref() { 324 if let Some(import_to_add) = self.import_to_add.as_ref() {
324 let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); 325 if import_to_add.import_for_trait_assoc_item {
325 let _ = import_path_without_last_segment.segments.pop(); 326 lookup = lookup.or_else(|| Some(label.clone()));
326 327 insert_text = insert_text.or_else(|| Some(label.clone()));
327 if !import_path_without_last_segment.segments.is_empty() { 328 label = format!("{} ({})", label, import_to_add.import_path);
328 if lookup.is_none() { 329 } else {
329 lookup = Some(label.clone()); 330 let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
330 } 331 let _ = import_path_without_last_segment.segments.pop();
331 if insert_text.is_none() { 332
332 insert_text = Some(label.clone()); 333 if !import_path_without_last_segment.segments.is_empty() {
334 lookup = lookup.or_else(|| Some(label.clone()));
335 insert_text = insert_text.or_else(|| Some(label.clone()));
336 label = format!("{}::{}", import_path_without_last_segment, label);
333 } 337 }
334 label = format!("{}::{}", import_path_without_last_segment, label);
335 } 338 }
336 } 339 }
337 340
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index ee1b822e7..56ec13e8c 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -139,6 +139,7 @@ pub fn resolve_completion_edits(
139 position: FilePosition, 139 position: FilePosition,
140 full_import_path: &str, 140 full_import_path: &str,
141 imported_name: String, 141 imported_name: String,
142 import_for_trait_assoc_item: bool,
142) -> Option<Vec<TextEdit>> { 143) -> Option<Vec<TextEdit>> {
143 let ctx = CompletionContext::new(db, position, config)?; 144 let ctx = CompletionContext::new(db, position, config)?;
144 let anchor = ctx.name_ref_syntax.as_ref()?; 145 let anchor = ctx.name_ref_syntax.as_ref()?;
@@ -154,7 +155,7 @@ pub fn resolve_completion_edits(
154 }) 155 })
155 .find(|mod_path| mod_path.to_string() == full_import_path)?; 156 .find(|mod_path| mod_path.to_string() == full_import_path)?;
156 157
157 ImportEdit { import_path, import_scope } 158 ImportEdit { import_path, import_scope, import_for_trait_assoc_item }
158 .to_text_edit(config.insert_use.merge) 159 .to_text_edit(config.insert_use.merge)
159 .map(|edit| vec![edit]) 160 .map(|edit| vec![edit])
160} 161}
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs
index 820dd01d1..4b3c9702a 100644
--- a/crates/completion/src/render.rs
+++ b/crates/completion/src/render.rs
@@ -10,7 +10,7 @@ pub(crate) mod type_alias;
10 10
11mod builder_ext; 11mod builder_ext;
12 12
13use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type}; 13use hir::{Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type};
14use ide_db::{helpers::SnippetCap, RootDatabase}; 14use ide_db::{helpers::SnippetCap, RootDatabase};
15use syntax::TextRange; 15use syntax::TextRange;
16use test_utils::mark; 16use test_utils::mark;
@@ -51,16 +51,16 @@ pub(crate) fn render_resolution_with_import<'a>(
51 import_edit: ImportEdit, 51 import_edit: ImportEdit,
52 resolution: &ScopeDef, 52 resolution: &ScopeDef,
53) -> Option<CompletionItem> { 53) -> Option<CompletionItem> {
54 Render::new(ctx) 54 let local_name = match resolution {
55 .render_resolution( 55 ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(),
56 import_edit.import_path.segments.last()?.to_string(), 56 ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(),
57 Some(import_edit), 57 ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(),
58 resolution, 58 _ => import_edit.import_path.segments.last()?.to_string(),
59 ) 59 };
60 .map(|mut item| { 60 Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| {
61 item.completion_kind = CompletionKind::Magic; 61 item.completion_kind = CompletionKind::Magic;
62 item 62 item
63 }) 63 })
64} 64}
65 65
66/// Interface for data and methods required for items rendering. 66/// Interface for data and methods required for items rendering.
diff --git a/crates/completion/src/test_utils.rs b/crates/completion/src/test_utils.rs
index 6ea6da989..3faf861b9 100644
--- a/crates/completion/src/test_utils.rs
+++ b/crates/completion/src/test_utils.rs
@@ -18,7 +18,7 @@ use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
18 18
19pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { 19pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
20 enable_postfix_completions: true, 20 enable_postfix_completions: true,
21 enable_autoimport_completions: true, 21 enable_imports_on_the_fly: true,
22 add_call_parenthesis: true, 22 add_call_parenthesis: true,
23 add_call_argument_snippets: true, 23 add_call_argument_snippets: true,
24 snippet_cap: SnippetCap::new(true), 24 snippet_cap: SnippetCap::new(true),