aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion/src/completions/flyimport.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_completion/src/completions/flyimport.rs')
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs778
1 files changed, 778 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..da8375af9
--- /dev/null
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -0,0 +1,778 @@
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 rustc_hash::FxHashSet;
57use syntax::{AstNode, SyntaxNode, T};
58use test_utils::mark;
59
60use crate::{
61 context::CompletionContext,
62 render::{render_resolution_with_import, RenderContext},
63 ImportEdit,
64};
65
66use super::Completions;
67
68pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
69 if !ctx.config.enable_imports_on_the_fly {
70 return None;
71 }
72 if ctx.use_item_syntax.is_some()
73 || ctx.attribute_under_caret.is_some()
74 || ctx.mod_declaration_under_caret.is_some()
75 {
76 return None;
77 }
78 let potential_import_name = {
79 let token_kind = ctx.token.kind();
80 if matches!(token_kind, T![.] | T![::]) {
81 String::new()
82 } else {
83 ctx.token.to_string()
84 }
85 };
86
87 let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
88
89 let user_input_lowercased = potential_import_name.to_lowercase();
90 let import_assets = import_assets(ctx, potential_import_name)?;
91 let import_scope = ImportScope::find_insert_use_container(
92 position_for_import(ctx, Some(import_assets.import_candidate()))?,
93 &ctx.sema,
94 )?;
95
96 let scope_definitions = scope_definitions(ctx);
97 let mut all_mod_paths = import_assets
98 .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
99 .into_iter()
100 .map(|(mod_path, item_in_ns)| {
101 let scope_item = match item_in_ns {
102 hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
103 hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
104 hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
105 };
106 (mod_path, scope_item)
107 })
108 .filter(|(_, proposed_def)| !scope_definitions.contains(proposed_def))
109 .collect::<Vec<_>>();
110 all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
111 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
112 });
113
114 acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
115 let import_for_trait_assoc_item = match definition {
116 ScopeDef::ModuleDef(module_def) => module_def
117 .as_assoc_item(ctx.db)
118 .and_then(|assoc| assoc.containing_trait(ctx.db))
119 .is_some(),
120 _ => false,
121 };
122 let import_edit = ImportEdit {
123 import_path,
124 import_scope: import_scope.clone(),
125 import_for_trait_assoc_item,
126 };
127 render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
128 }));
129 Some(())
130}
131
132fn scope_definitions(ctx: &CompletionContext) -> FxHashSet<ScopeDef> {
133 let mut scope_definitions = FxHashSet::default();
134 ctx.scope.process_all_names(&mut |_, scope_def| {
135 scope_definitions.insert(scope_def);
136 });
137 scope_definitions
138}
139
140pub(crate) fn position_for_import<'a>(
141 ctx: &'a CompletionContext,
142 import_candidate: Option<&ImportCandidate>,
143) -> Option<&'a SyntaxNode> {
144 Some(match import_candidate {
145 Some(ImportCandidate::Path(_)) => ctx.name_ref_syntax.as_ref()?.syntax(),
146 Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual.as_ref()?.syntax(),
147 Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver.as_ref()?.syntax(),
148 None => ctx
149 .name_ref_syntax
150 .as_ref()
151 .map(|name_ref| name_ref.syntax())
152 .or_else(|| ctx.path_qual.as_ref().map(|path| path.syntax()))
153 .or_else(|| ctx.dot_receiver.as_ref().map(|expr| expr.syntax()))?,
154 })
155}
156
157fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
158 let current_module = ctx.scope.module()?;
159 if let Some(dot_receiver) = &ctx.dot_receiver {
160 ImportAssets::for_fuzzy_method_call(
161 current_module,
162 ctx.sema.type_of_expr(dot_receiver)?,
163 fuzzy_name,
164 )
165 } else {
166 let fuzzy_name_length = fuzzy_name.len();
167 let assets_for_path = ImportAssets::for_fuzzy_path(
168 current_module,
169 ctx.path_qual.clone(),
170 fuzzy_name,
171 &ctx.sema,
172 );
173
174 if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_))
175 && fuzzy_name_length < 2
176 {
177 mark::hit!(ignore_short_input_for_path);
178 None
179 } else {
180 assets_for_path
181 }
182 }
183}
184
185fn compute_fuzzy_completion_order_key(
186 proposed_mod_path: &ModPath,
187 user_input_lowercased: &str,
188) -> usize {
189 mark::hit!(certain_fuzzy_order_test);
190 let proposed_import_name = match proposed_mod_path.segments().last() {
191 Some(name) => name.to_string().to_lowercase(),
192 None => return usize::MAX,
193 };
194 match proposed_import_name.match_indices(user_input_lowercased).next() {
195 Some((first_matching_index, _)) => first_matching_index,
196 None => usize::MAX,
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use expect_test::{expect, Expect};
203 use test_utils::mark;
204
205 use crate::{
206 item::CompletionKind,
207 test_utils::{check_edit, check_edit_with_config, completion_list, TEST_CONFIG},
208 };
209
210 fn check(ra_fixture: &str, expect: Expect) {
211 let actual = completion_list(ra_fixture, CompletionKind::Magic);
212 expect.assert_eq(&actual);
213 }
214
215 #[test]
216 fn function_fuzzy_completion() {
217 check_edit(
218 "stdin",
219 r#"
220//- /lib.rs crate:dep
221pub mod io {
222 pub fn stdin() {}
223};
224
225//- /main.rs crate:main deps:dep
226fn main() {
227 stdi$0
228}
229"#,
230 r#"
231use dep::io::stdin;
232
233fn main() {
234 stdin()$0
235}
236"#,
237 );
238 }
239
240 #[test]
241 fn macro_fuzzy_completion() {
242 check_edit(
243 "macro_with_curlies!",
244 r#"
245//- /lib.rs crate:dep
246/// Please call me as macro_with_curlies! {}
247#[macro_export]
248macro_rules! macro_with_curlies {
249 () => {}
250}
251
252//- /main.rs crate:main deps:dep
253fn main() {
254 curli$0
255}
256"#,
257 r#"
258use dep::macro_with_curlies;
259
260fn main() {
261 macro_with_curlies! {$0}
262}
263"#,
264 );
265 }
266
267 #[test]
268 fn struct_fuzzy_completion() {
269 check_edit(
270 "ThirdStruct",
271 r#"
272//- /lib.rs crate:dep
273pub struct FirstStruct;
274pub mod some_module {
275 pub struct SecondStruct;
276 pub struct ThirdStruct;
277}
278
279//- /main.rs crate:main deps:dep
280use dep::{FirstStruct, some_module::SecondStruct};
281
282fn main() {
283 this$0
284}
285"#,
286 r#"
287use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
288
289fn main() {
290 ThirdStruct
291}
292"#,
293 );
294 }
295
296 #[test]
297 fn short_paths_are_ignored() {
298 mark::check!(ignore_short_input_for_path);
299
300 check(
301 r#"
302//- /lib.rs crate:dep
303pub struct FirstStruct;
304pub mod some_module {
305 pub struct SecondStruct;
306 pub struct ThirdStruct;
307}
308
309//- /main.rs crate:main deps:dep
310use dep::{FirstStruct, some_module::SecondStruct};
311
312fn main() {
313 t$0
314}
315"#,
316 expect![[r#""#]],
317 );
318 }
319
320 #[test]
321 fn fuzzy_completions_come_in_specific_order() {
322 mark::check!(certain_fuzzy_order_test);
323 check(
324 r#"
325//- /lib.rs crate:dep
326pub struct FirstStruct;
327pub mod some_module {
328 // already imported, omitted
329 pub struct SecondStruct;
330 // does not contain all letters from the query, omitted
331 pub struct UnrelatedOne;
332 // contains all letters from the query, but not in sequence, displayed last
333 pub struct ThiiiiiirdStruct;
334 // contains all letters from the query, but not in the beginning, displayed second
335 pub struct AfterThirdStruct;
336 // contains all letters from the query in the begginning, displayed first
337 pub struct ThirdStruct;
338}
339
340//- /main.rs crate:main deps:dep
341use dep::{FirstStruct, some_module::SecondStruct};
342
343fn main() {
344 hir$0
345}
346"#,
347 expect![[r#"
348 st dep::some_module::ThirdStruct
349 st dep::some_module::AfterThirdStruct
350 st dep::some_module::ThiiiiiirdStruct
351 "#]],
352 );
353 }
354
355 #[test]
356 fn trait_function_fuzzy_completion() {
357 let fixture = r#"
358 //- /lib.rs crate:dep
359 pub mod test_mod {
360 pub trait TestTrait {
361 const SPECIAL_CONST: u8;
362 type HumbleType;
363 fn weird_function();
364 fn random_method(&self);
365 }
366 pub struct TestStruct {}
367 impl TestTrait for TestStruct {
368 const SPECIAL_CONST: u8 = 42;
369 type HumbleType = ();
370 fn weird_function() {}
371 fn random_method(&self) {}
372 }
373 }
374
375 //- /main.rs crate:main deps:dep
376 fn main() {
377 dep::test_mod::TestStruct::wei$0
378 }
379 "#;
380
381 check(
382 fixture,
383 expect![[r#"
384 fn weird_function() (dep::test_mod::TestTrait) -> ()
385 "#]],
386 );
387
388 check_edit(
389 "weird_function",
390 fixture,
391 r#"
392use dep::test_mod::TestTrait;
393
394fn main() {
395 dep::test_mod::TestStruct::weird_function()$0
396}
397"#,
398 );
399 }
400
401 #[test]
402 fn trait_const_fuzzy_completion() {
403 let fixture = r#"
404 //- /lib.rs crate:dep
405 pub mod test_mod {
406 pub trait TestTrait {
407 const SPECIAL_CONST: u8;
408 type HumbleType;
409 fn weird_function();
410 fn random_method(&self);
411 }
412 pub struct TestStruct {}
413 impl TestTrait for TestStruct {
414 const SPECIAL_CONST: u8 = 42;
415 type HumbleType = ();
416 fn weird_function() {}
417 fn random_method(&self) {}
418 }
419 }
420
421 //- /main.rs crate:main deps:dep
422 fn main() {
423 dep::test_mod::TestStruct::spe$0
424 }
425 "#;
426
427 check(
428 fixture,
429 expect![[r#"
430 ct SPECIAL_CONST (dep::test_mod::TestTrait)
431 "#]],
432 );
433
434 check_edit(
435 "SPECIAL_CONST",
436 fixture,
437 r#"
438use dep::test_mod::TestTrait;
439
440fn main() {
441 dep::test_mod::TestStruct::SPECIAL_CONST
442}
443"#,
444 );
445 }
446
447 #[test]
448 fn trait_method_fuzzy_completion() {
449 let fixture = r#"
450 //- /lib.rs crate:dep
451 pub mod test_mod {
452 pub trait TestTrait {
453 const SPECIAL_CONST: u8;
454 type HumbleType;
455 fn weird_function();
456 fn random_method(&self);
457 }
458 pub struct TestStruct {}
459 impl TestTrait for TestStruct {
460 const SPECIAL_CONST: u8 = 42;
461 type HumbleType = ();
462 fn weird_function() {}
463 fn random_method(&self) {}
464 }
465 }
466
467 //- /main.rs crate:main deps:dep
468 fn main() {
469 let test_struct = dep::test_mod::TestStruct {};
470 test_struct.ran$0
471 }
472 "#;
473
474 check(
475 fixture,
476 expect![[r#"
477 me random_method() (dep::test_mod::TestTrait) -> ()
478 "#]],
479 );
480
481 check_edit(
482 "random_method",
483 fixture,
484 r#"
485use dep::test_mod::TestTrait;
486
487fn main() {
488 let test_struct = dep::test_mod::TestStruct {};
489 test_struct.random_method()$0
490}
491"#,
492 );
493 }
494
495 #[test]
496 fn no_trait_type_fuzzy_completion() {
497 check(
498 r#"
499//- /lib.rs crate:dep
500pub mod test_mod {
501 pub trait TestTrait {
502 const SPECIAL_CONST: u8;
503 type HumbleType;
504 fn weird_function();
505 fn random_method(&self);
506 }
507 pub struct TestStruct {}
508 impl TestTrait for TestStruct {
509 const SPECIAL_CONST: u8 = 42;
510 type HumbleType = ();
511 fn weird_function() {}
512 fn random_method(&self) {}
513 }
514}
515
516//- /main.rs crate:main deps:dep
517fn main() {
518 dep::test_mod::TestStruct::hum$0
519}
520"#,
521 expect![[r#""#]],
522 );
523 }
524
525 #[test]
526 fn does_not_propose_names_in_scope() {
527 check(
528 r#"
529//- /lib.rs crate:dep
530pub mod test_mod {
531 pub trait TestTrait {
532 const SPECIAL_CONST: u8;
533 type HumbleType;
534 fn weird_function();
535 fn random_method(&self);
536 }
537 pub struct TestStruct {}
538 impl TestTrait for TestStruct {
539 const SPECIAL_CONST: u8 = 42;
540 type HumbleType = ();
541 fn weird_function() {}
542 fn random_method(&self) {}
543 }
544}
545
546//- /main.rs crate:main deps:dep
547use dep::test_mod::TestStruct;
548fn main() {
549 TestSt$0
550}
551"#,
552 expect![[r#""#]],
553 );
554 }
555
556 #[test]
557 fn does_not_propose_traits_in_scope() {
558 check(
559 r#"
560//- /lib.rs crate:dep
561pub mod test_mod {
562 pub trait TestTrait {
563 const SPECIAL_CONST: u8;
564 type HumbleType;
565 fn weird_function();
566 fn random_method(&self);
567 }
568 pub struct TestStruct {}
569 impl TestTrait for TestStruct {
570 const SPECIAL_CONST: u8 = 42;
571 type HumbleType = ();
572 fn weird_function() {}
573 fn random_method(&self) {}
574 }
575}
576
577//- /main.rs crate:main deps:dep
578use dep::test_mod::{TestStruct, TestTrait};
579fn main() {
580 dep::test_mod::TestStruct::hum$0
581}
582"#,
583 expect![[r#""#]],
584 );
585 }
586
587 #[test]
588 fn blanket_trait_impl_import() {
589 check_edit(
590 "another_function",
591 r#"
592//- /lib.rs crate:dep
593pub mod test_mod {
594 pub struct TestStruct {}
595 pub trait TestTrait {
596 fn another_function();
597 }
598 impl<T> TestTrait for T {
599 fn another_function() {}
600 }
601}
602
603//- /main.rs crate:main deps:dep
604fn main() {
605 dep::test_mod::TestStruct::ano$0
606}
607"#,
608 r#"
609use dep::test_mod::TestTrait;
610
611fn main() {
612 dep::test_mod::TestStruct::another_function()$0
613}
614"#,
615 );
616 }
617
618 #[test]
619 fn zero_input_deprecated_assoc_item_completion() {
620 check(
621 r#"
622//- /lib.rs crate:dep
623pub mod test_mod {
624 #[deprecated]
625 pub trait TestTrait {
626 const SPECIAL_CONST: u8;
627 type HumbleType;
628 fn weird_function();
629 fn random_method(&self);
630 }
631 pub struct TestStruct {}
632 impl TestTrait for TestStruct {
633 const SPECIAL_CONST: u8 = 42;
634 type HumbleType = ();
635 fn weird_function() {}
636 fn random_method(&self) {}
637 }
638}
639
640//- /main.rs crate:main deps:dep
641fn main() {
642 let test_struct = dep::test_mod::TestStruct {};
643 test_struct.$0
644}
645 "#,
646 expect![[r#"
647 me random_method() (dep::test_mod::TestTrait) -> () DEPRECATED
648 "#]],
649 );
650
651 check(
652 r#"
653//- /lib.rs crate:dep
654pub mod test_mod {
655 #[deprecated]
656 pub trait TestTrait {
657 const SPECIAL_CONST: u8;
658 type HumbleType;
659 fn weird_function();
660 fn random_method(&self);
661 }
662 pub struct TestStruct {}
663 impl TestTrait for TestStruct {
664 const SPECIAL_CONST: u8 = 42;
665 type HumbleType = ();
666 fn weird_function() {}
667 fn random_method(&self) {}
668 }
669}
670
671//- /main.rs crate:main deps:dep
672fn main() {
673 dep::test_mod::TestStruct::$0
674}
675"#,
676 expect![[r#"
677 ct SPECIAL_CONST (dep::test_mod::TestTrait) DEPRECATED
678 fn weird_function() (dep::test_mod::TestTrait) -> () DEPRECATED
679 "#]],
680 );
681 }
682
683 #[test]
684 fn no_completions_in_use_statements() {
685 check(
686 r#"
687//- /lib.rs crate:dep
688pub mod io {
689 pub fn stdin() {}
690};
691
692//- /main.rs crate:main deps:dep
693use stdi$0
694
695fn main() {}
696"#,
697 expect![[]],
698 );
699 }
700
701 #[test]
702 fn prefix_config_usage() {
703 let fixture = r#"
704mod foo {
705 pub mod bar {
706 pub struct Item;
707 }
708}
709
710use crate::foo::bar;
711
712fn main() {
713 Ite$0
714}"#;
715 let mut config = TEST_CONFIG;
716
717 config.insert_use.prefix_kind = hir::PrefixKind::ByCrate;
718 check_edit_with_config(
719 config.clone(),
720 "Item",
721 fixture,
722 r#"
723mod foo {
724 pub mod bar {
725 pub struct Item;
726 }
727}
728
729use crate::foo::bar::{self, Item};
730
731fn main() {
732 Item
733}"#,
734 );
735
736 config.insert_use.prefix_kind = hir::PrefixKind::BySelf;
737 check_edit_with_config(
738 config.clone(),
739 "Item",
740 fixture,
741 r#"
742mod foo {
743 pub mod bar {
744 pub struct Item;
745 }
746}
747
748use crate::foo::bar;
749
750use self::foo::bar::Item;
751
752fn main() {
753 Item
754}"#,
755 );
756
757 config.insert_use.prefix_kind = hir::PrefixKind::Plain;
758 check_edit_with_config(
759 config,
760 "Item",
761 fixture,
762 r#"
763mod foo {
764 pub mod bar {
765 pub struct Item;
766 }
767}
768
769use foo::bar::Item;
770
771use crate::foo::bar;
772
773fn main() {
774 Item
775}"#,
776 );
777 }
778}