aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion/src/completions/flyimport.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-02-17 14:53:31 +0000
committerAleksey Kladov <[email protected]>2021-02-17 14:53:31 +0000
commit3db64a400c78bbd2708e67ddc07df1001fff3f29 (patch)
tree5386aab9c452981be09bc3e4362643a34e6e3617 /crates/ide_completion/src/completions/flyimport.rs
parent6334ce866ab095215381c4b72692b20a84d26e96 (diff)
rename completion -> ide_completion
We don't have completion-related PRs in flight, so lets do it
Diffstat (limited to 'crates/ide_completion/src/completions/flyimport.rs')
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs688
1 files changed, 688 insertions, 0 deletions
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
new file mode 100644
index 000000000..c9f928483
--- /dev/null
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -0,0 +1,688 @@
1//! Feature: completion with imports-on-the-fly
2//!
3//! When completing names in the current scope, proposes additional imports from other modules or crates,
4//! if they can be qualified in the scope and their name contains all symbols from the completion input
5//! (case-insensitive, in any order or places).
6//!
7//! ```
8//! fn main() {
9//! pda$0
10//! }
11//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
12//! ```
13//! ->
14//! ```
15//! use std::marker::PhantomData;
16//!
17//! fn main() {
18//! PhantomData
19//! }
20//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
21//! ```
22//!
23//! Also completes associated items, that require trait imports.
24//!
25//! .Fuzzy search details
26//!
27//! To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
28//! (i.e. in `HashMap` in the `std::collections::HashMap` path).
29//! For the same reasons, avoids searching for any path imports for inputs with their length less that 2 symbols
30//! (but shows all associated items for any input length).
31//!
32//! .Import configuration
33//!
34//! It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
35//! Mimics the corresponding behavior of the `Auto Import` feature.
36//!
37//! .LSP and performance implications
38//!
39//! The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
40//! (case sensitive) resolve client capability in its client capabilities.
41//! This way the server is able to defer the costly computations, doing them for a selected completion item only.
42//! For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
43//! which might be slow ergo the feature is automatically disabled.
44//!
45//! .Feature toggle
46//!
47//! The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
48//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
49//! capability enabled.
50
51use hir::{AsAssocItem, ModPath, ScopeDef};
52use ide_db::helpers::{
53 import_assets::{ImportAssets, ImportCandidate},
54 insert_use::ImportScope,
55};
56use syntax::{AstNode, SyntaxNode, T};
57use test_utils::mark;
58
59use crate::{
60 context::CompletionContext,
61 render::{render_resolution_with_import, RenderContext},
62 ImportEdit,
63};
64
65use super::Completions;
66
67pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
68 if !ctx.config.enable_imports_on_the_fly {
69 return None;
70 }
71 if ctx.use_item_syntax.is_some()
72 || ctx.attribute_under_caret.is_some()
73 || ctx.mod_declaration_under_caret.is_some()
74 {
75 return None;
76 }
77 let potential_import_name = {
78 let token_kind = ctx.token.kind();
79 if matches!(token_kind, T![.] | T![::]) {
80 String::new()
81 } else {
82 ctx.token.to_string()
83 }
84 };
85
86 let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
87
88 let user_input_lowercased = potential_import_name.to_lowercase();
89 let import_assets = import_assets(ctx, potential_import_name)?;
90 let import_scope = ImportScope::find_insert_use_container(
91 position_for_import(ctx, Some(import_assets.import_candidate()))?,
92 &ctx.sema,
93 )?;
94 let mut all_mod_paths = import_assets
95 .search_for_relative_paths(&ctx.sema)
96 .into_iter()
97 .map(|(mod_path, item_in_ns)| {
98 let scope_item = match item_in_ns {
99 hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
100 hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
101 hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
102 };
103 (mod_path, scope_item)
104 })
105 .collect::<Vec<_>>();
106 all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
107 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
108 });
109
110 acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
111 let import_for_trait_assoc_item = match definition {
112 ScopeDef::ModuleDef(module_def) => module_def
113 .as_assoc_item(ctx.db)
114 .and_then(|assoc| assoc.containing_trait(ctx.db))
115 .is_some(),
116 _ => false,
117 };
118 let import_edit = ImportEdit {
119 import_path,
120 import_scope: import_scope.clone(),
121 import_for_trait_assoc_item,
122 };
123 render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
124 }));
125 Some(())
126}
127
128pub(crate) fn position_for_import<'a>(
129 ctx: &'a CompletionContext,
130 import_candidate: Option<&ImportCandidate>,
131) -> Option<&'a SyntaxNode> {
132 Some(match import_candidate {
133 Some(ImportCandidate::Path(_)) => ctx.name_ref_syntax.as_ref()?.syntax(),
134 Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual.as_ref()?.syntax(),
135 Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver.as_ref()?.syntax(),
136 None => ctx
137 .name_ref_syntax
138 .as_ref()
139 .map(|name_ref| name_ref.syntax())
140 .or_else(|| ctx.path_qual.as_ref().map(|path| path.syntax()))
141 .or_else(|| ctx.dot_receiver.as_ref().map(|expr| expr.syntax()))?,
142 })
143}
144
145fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
146 let current_module = ctx.scope.module()?;
147 if let Some(dot_receiver) = &ctx.dot_receiver {
148 ImportAssets::for_fuzzy_method_call(
149 current_module,
150 ctx.sema.type_of_expr(dot_receiver)?,
151 fuzzy_name,
152 )
153 } else {
154 let fuzzy_name_length = fuzzy_name.len();
155 let assets_for_path = ImportAssets::for_fuzzy_path(
156 current_module,
157 ctx.path_qual.clone(),
158 fuzzy_name,
159 &ctx.sema,
160 );
161
162 if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_))
163 && fuzzy_name_length < 2
164 {
165 mark::hit!(ignore_short_input_for_path);
166 None
167 } else {
168 assets_for_path
169 }
170 }
171}
172
173fn compute_fuzzy_completion_order_key(
174 proposed_mod_path: &ModPath,
175 user_input_lowercased: &str,
176) -> usize {
177 mark::hit!(certain_fuzzy_order_test);
178 let proposed_import_name = match proposed_mod_path.segments().last() {
179 Some(name) => name.to_string().to_lowercase(),
180 None => return usize::MAX,
181 };
182 match proposed_import_name.match_indices(user_input_lowercased).next() {
183 Some((first_matching_index, _)) => first_matching_index,
184 None => usize::MAX,
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use expect_test::{expect, Expect};
191 use test_utils::mark;
192
193 use crate::{
194 item::CompletionKind,
195 test_utils::{check_edit, completion_list},
196 };
197
198 fn check(ra_fixture: &str, expect: Expect) {
199 let actual = completion_list(ra_fixture, CompletionKind::Magic);
200 expect.assert_eq(&actual);
201 }
202
203 #[test]
204 fn function_fuzzy_completion() {
205 check_edit(
206 "stdin",
207 r#"
208//- /lib.rs crate:dep
209pub mod io {
210 pub fn stdin() {}
211};
212
213//- /main.rs crate:main deps:dep
214fn main() {
215 stdi$0
216}
217"#,
218 r#"
219use dep::io::stdin;
220
221fn main() {
222 stdin()$0
223}
224"#,
225 );
226 }
227
228 #[test]
229 fn macro_fuzzy_completion() {
230 check_edit(
231 "macro_with_curlies!",
232 r#"
233//- /lib.rs crate:dep
234/// Please call me as macro_with_curlies! {}
235#[macro_export]
236macro_rules! macro_with_curlies {
237 () => {}
238}
239
240//- /main.rs crate:main deps:dep
241fn main() {
242 curli$0
243}
244"#,
245 r#"
246use dep::macro_with_curlies;
247
248fn main() {
249 macro_with_curlies! {$0}
250}
251"#,
252 );
253 }
254
255 #[test]
256 fn struct_fuzzy_completion() {
257 check_edit(
258 "ThirdStruct",
259 r#"
260//- /lib.rs crate:dep
261pub struct FirstStruct;
262pub mod some_module {
263 pub struct SecondStruct;
264 pub struct ThirdStruct;
265}
266
267//- /main.rs crate:main deps:dep
268use dep::{FirstStruct, some_module::SecondStruct};
269
270fn main() {
271 this$0
272}
273"#,
274 r#"
275use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
276
277fn main() {
278 ThirdStruct
279}
280"#,
281 );
282 }
283
284 #[test]
285 fn short_paths_are_ignored() {
286 mark::check!(ignore_short_input_for_path);
287
288 check(
289 r#"
290//- /lib.rs crate:dep
291pub struct FirstStruct;
292pub mod some_module {
293 pub struct SecondStruct;
294 pub struct ThirdStruct;
295}
296
297//- /main.rs crate:main deps:dep
298use dep::{FirstStruct, some_module::SecondStruct};
299
300fn main() {
301 t$0
302}
303"#,
304 expect![[r#""#]],
305 );
306 }
307
308 #[test]
309 fn fuzzy_completions_come_in_specific_order() {
310 mark::check!(certain_fuzzy_order_test);
311 check(
312 r#"
313//- /lib.rs crate:dep
314pub struct FirstStruct;
315pub mod some_module {
316 // already imported, omitted
317 pub struct SecondStruct;
318 // does not contain all letters from the query, omitted
319 pub struct UnrelatedOne;
320 // contains all letters from the query, but not in sequence, displayed last
321 pub struct ThiiiiiirdStruct;
322 // contains all letters from the query, but not in the beginning, displayed second
323 pub struct AfterThirdStruct;
324 // contains all letters from the query in the begginning, displayed first
325 pub struct ThirdStruct;
326}
327
328//- /main.rs crate:main deps:dep
329use dep::{FirstStruct, some_module::SecondStruct};
330
331fn main() {
332 hir$0
333}
334"#,
335 expect![[r#"
336 st dep::some_module::ThirdStruct
337 st dep::some_module::AfterThirdStruct
338 st dep::some_module::ThiiiiiirdStruct
339 "#]],
340 );
341 }
342
343 #[test]
344 fn trait_function_fuzzy_completion() {
345 let fixture = r#"
346 //- /lib.rs crate:dep
347 pub mod test_mod {
348 pub trait TestTrait {
349 const SPECIAL_CONST: u8;
350 type HumbleType;
351 fn weird_function();
352 fn random_method(&self);
353 }
354 pub struct TestStruct {}
355 impl TestTrait for TestStruct {
356 const SPECIAL_CONST: u8 = 42;
357 type HumbleType = ();
358 fn weird_function() {}
359 fn random_method(&self) {}
360 }
361 }
362
363 //- /main.rs crate:main deps:dep
364 fn main() {
365 dep::test_mod::TestStruct::wei$0
366 }
367 "#;
368
369 check(
370 fixture,
371 expect![[r#"
372 fn weird_function() (dep::test_mod::TestTrait) -> ()
373 "#]],
374 );
375
376 check_edit(
377 "weird_function",
378 fixture,
379 r#"
380use dep::test_mod::TestTrait;
381
382fn main() {
383 dep::test_mod::TestStruct::weird_function()$0
384}
385"#,
386 );
387 }
388
389 #[test]
390 fn trait_const_fuzzy_completion() {
391 let fixture = r#"
392 //- /lib.rs crate:dep
393 pub mod test_mod {
394 pub trait TestTrait {
395 const SPECIAL_CONST: u8;
396 type HumbleType;
397 fn weird_function();
398 fn random_method(&self);
399 }
400 pub struct TestStruct {}
401 impl TestTrait for TestStruct {
402 const SPECIAL_CONST: u8 = 42;
403 type HumbleType = ();
404 fn weird_function() {}
405 fn random_method(&self) {}
406 }
407 }
408
409 //- /main.rs crate:main deps:dep
410 fn main() {
411 dep::test_mod::TestStruct::spe$0
412 }
413 "#;
414
415 check(
416 fixture,
417 expect![[r#"
418 ct SPECIAL_CONST (dep::test_mod::TestTrait)
419 "#]],
420 );
421
422 check_edit(
423 "SPECIAL_CONST",
424 fixture,
425 r#"
426use dep::test_mod::TestTrait;
427
428fn main() {
429 dep::test_mod::TestStruct::SPECIAL_CONST
430}
431"#,
432 );
433 }
434
435 #[test]
436 fn trait_method_fuzzy_completion() {
437 let fixture = r#"
438 //- /lib.rs crate:dep
439 pub mod test_mod {
440 pub trait TestTrait {
441 const SPECIAL_CONST: u8;
442 type HumbleType;
443 fn weird_function();
444 fn random_method(&self);
445 }
446 pub struct TestStruct {}
447 impl TestTrait for TestStruct {
448 const SPECIAL_CONST: u8 = 42;
449 type HumbleType = ();
450 fn weird_function() {}
451 fn random_method(&self) {}
452 }
453 }
454
455 //- /main.rs crate:main deps:dep
456 fn main() {
457 let test_struct = dep::test_mod::TestStruct {};
458 test_struct.ran$0
459 }
460 "#;
461
462 check(
463 fixture,
464 expect![[r#"
465 me random_method() (dep::test_mod::TestTrait) -> ()
466 "#]],
467 );
468
469 check_edit(
470 "random_method",
471 fixture,
472 r#"
473use dep::test_mod::TestTrait;
474
475fn main() {
476 let test_struct = dep::test_mod::TestStruct {};
477 test_struct.random_method()$0
478}
479"#,
480 );
481 }
482
483 #[test]
484 fn no_trait_type_fuzzy_completion() {
485 check(
486 r#"
487//- /lib.rs crate:dep
488pub mod test_mod {
489 pub trait TestTrait {
490 const SPECIAL_CONST: u8;
491 type HumbleType;
492 fn weird_function();
493 fn random_method(&self);
494 }
495 pub struct TestStruct {}
496 impl TestTrait for TestStruct {
497 const SPECIAL_CONST: u8 = 42;
498 type HumbleType = ();
499 fn weird_function() {}
500 fn random_method(&self) {}
501 }
502}
503
504//- /main.rs crate:main deps:dep
505fn main() {
506 dep::test_mod::TestStruct::hum$0
507}
508"#,
509 expect![[r#""#]],
510 );
511 }
512
513 #[test]
514 fn does_not_propose_names_in_scope() {
515 check(
516 r#"
517//- /lib.rs crate:dep
518pub mod test_mod {
519 pub trait TestTrait {
520 const SPECIAL_CONST: u8;
521 type HumbleType;
522 fn weird_function();
523 fn random_method(&self);
524 }
525 pub struct TestStruct {}
526 impl TestTrait for TestStruct {
527 const SPECIAL_CONST: u8 = 42;
528 type HumbleType = ();
529 fn weird_function() {}
530 fn random_method(&self) {}
531 }
532}
533
534//- /main.rs crate:main deps:dep
535use dep::test_mod::TestStruct;
536fn main() {
537 TestSt$0
538}
539"#,
540 expect![[r#""#]],
541 );
542 }
543
544 #[test]
545 fn does_not_propose_traits_in_scope() {
546 check(
547 r#"
548//- /lib.rs crate:dep
549pub mod test_mod {
550 pub trait TestTrait {
551 const SPECIAL_CONST: u8;
552 type HumbleType;
553 fn weird_function();
554 fn random_method(&self);
555 }
556 pub struct TestStruct {}
557 impl TestTrait for TestStruct {
558 const SPECIAL_CONST: u8 = 42;
559 type HumbleType = ();
560 fn weird_function() {}
561 fn random_method(&self) {}
562 }
563}
564
565//- /main.rs crate:main deps:dep
566use dep::test_mod::{TestStruct, TestTrait};
567fn main() {
568 dep::test_mod::TestStruct::hum$0
569}
570"#,
571 expect![[r#""#]],
572 );
573 }
574
575 #[test]
576 fn blanket_trait_impl_import() {
577 check_edit(
578 "another_function",
579 r#"
580//- /lib.rs crate:dep
581pub mod test_mod {
582 pub struct TestStruct {}
583 pub trait TestTrait {
584 fn another_function();
585 }
586 impl<T> TestTrait for T {
587 fn another_function() {}
588 }
589}
590
591//- /main.rs crate:main deps:dep
592fn main() {
593 dep::test_mod::TestStruct::ano$0
594}
595"#,
596 r#"
597use dep::test_mod::TestTrait;
598
599fn main() {
600 dep::test_mod::TestStruct::another_function()$0
601}
602"#,
603 );
604 }
605
606 #[test]
607 fn zero_input_deprecated_assoc_item_completion() {
608 check(
609 r#"
610//- /lib.rs crate:dep
611pub mod test_mod {
612 #[deprecated]
613 pub trait TestTrait {
614 const SPECIAL_CONST: u8;
615 type HumbleType;
616 fn weird_function();
617 fn random_method(&self);
618 }
619 pub struct TestStruct {}
620 impl TestTrait for TestStruct {
621 const SPECIAL_CONST: u8 = 42;
622 type HumbleType = ();
623 fn weird_function() {}
624 fn random_method(&self) {}
625 }
626}
627
628//- /main.rs crate:main deps:dep
629fn main() {
630 let test_struct = dep::test_mod::TestStruct {};
631 test_struct.$0
632}
633 "#,
634 expect![[r#"
635 me random_method() (dep::test_mod::TestTrait) -> () DEPRECATED
636 "#]],
637 );
638
639 check(
640 r#"
641//- /lib.rs crate:dep
642pub mod test_mod {
643 #[deprecated]
644 pub trait TestTrait {
645 const SPECIAL_CONST: u8;
646 type HumbleType;
647 fn weird_function();
648 fn random_method(&self);
649 }
650 pub struct TestStruct {}
651 impl TestTrait for TestStruct {
652 const SPECIAL_CONST: u8 = 42;
653 type HumbleType = ();
654 fn weird_function() {}
655 fn random_method(&self) {}
656 }
657}
658
659//- /main.rs crate:main deps:dep
660fn main() {
661 dep::test_mod::TestStruct::$0
662}
663"#,
664 expect![[r#"
665 ct SPECIAL_CONST (dep::test_mod::TestTrait) DEPRECATED
666 fn weird_function() (dep::test_mod::TestTrait) -> () DEPRECATED
667 "#]],
668 );
669 }
670
671 #[test]
672 fn no_completions_in_use_statements() {
673 check(
674 r#"
675//- /lib.rs crate:dep
676pub mod io {
677 pub fn stdin() {}
678};
679
680//- /main.rs crate:main deps:dep
681use stdi$0
682
683fn main() {}
684"#,
685 expect![[]],
686 );
687 }
688}