1 //! Feature: completion with imports-on-the-fly
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).
11 //! # pub mod std { pub mod marker { pub struct PhantomData { } } }
15 //! use std::marker::PhantomData;
20 //! # pub mod std { pub mod marker { pub struct PhantomData { } } }
23 //! Also completes associated items, that require trait imports.
24 //! If any unresolved and/or partially-qualified path predeces the input, it will be taken into account.
25 //! Currently, only the imports with their import path ending with the whole qialifier will be proposed
26 //! (no fuzzy matching for qualifier).
34 //! pub const TEST_ASSOC: usize = 3;
40 //! bar::Item::TEST_A$0
52 //! pub const TEST_ASSOC: usize = 3;
58 //! bar::Item::TEST_ASSOC
62 //! NOTE: currently, if an assoc item comes from a trait that's not currently imported and it also has an unresolved and/or partially-qualified path,
63 //! no imports will be proposed.
65 //! .Fuzzy search details
67 //! To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
68 //! (i.e. in `HashMap` in the `std::collections::HashMap` path).
69 //! For the same reasons, avoids searching for any path imports for inputs with their length less that 2 symbols
70 //! (but shows all associated items for any input length).
72 //! .Import configuration
74 //! It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
75 //! Mimics the corresponding behavior of the `Auto Import` feature.
77 //! .LSP and performance implications
79 //! The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
80 //! (case sensitive) resolve client capability in its client capabilities.
81 //! This way the server is able to defer the costly computations, doing them for a selected completion item only.
82 //! For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
83 //! which might be slow ergo the feature is automatically disabled.
87 //! The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
88 //! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
89 //! capability enabled.
92 use ide_db::helpers::{
93 import_assets::{ImportAssets, ImportCandidate},
94 insert_use::ImportScope,
96 use itertools::Itertools;
97 use syntax::{AstNode, SyntaxNode, T};
100 context::CompletionContext,
101 render::{render_resolution_with_import, RenderContext},
105 use super::Completions;
107 pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
108 if !ctx.config.enable_imports_on_the_fly {
111 if ctx.use_item_syntax.is_some()
112 || ctx.attribute_under_caret.is_some()
113 || ctx.mod_declaration_under_caret.is_some()
117 let potential_import_name = {
118 let token_kind = ctx.token.kind();
119 if matches!(token_kind, T![.] | T![::]) {
122 ctx.token.to_string()
126 let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
128 let user_input_lowercased = potential_import_name.to_lowercase();
129 let import_assets = import_assets(ctx, potential_import_name)?;
130 let import_scope = ImportScope::find_insert_use_container(
131 position_for_import(ctx, Some(import_assets.import_candidate()))?,
137 .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
139 .sorted_by_key(|located_import| {
140 compute_fuzzy_completion_order_key(
141 &located_import.import_path,
142 &user_input_lowercased,
145 .filter_map(|import| {
146 render_resolution_with_import(
147 RenderContext::new(ctx),
148 ImportEdit { import, scope: import_scope.clone() },
155 pub(crate) fn position_for_import<'a>(
156 ctx: &'a CompletionContext,
157 import_candidate: Option<&ImportCandidate>,
158 ) -> Option<&'a SyntaxNode> {
159 Some(match import_candidate {
160 Some(ImportCandidate::Path(_)) => ctx.name_ref_syntax.as_ref()?.syntax(),
161 Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual.as_ref()?.syntax(),
162 Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver.as_ref()?.syntax(),
166 .map(|name_ref| name_ref.syntax())
167 .or_else(|| ctx.path_qual.as_ref().map(|path| path.syntax()))
168 .or_else(|| ctx.dot_receiver.as_ref().map(|expr| expr.syntax()))?,
172 fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
173 let current_module = ctx.scope.module()?;
174 if let Some(dot_receiver) = &ctx.dot_receiver {
175 ImportAssets::for_fuzzy_method_call(
177 ctx.sema.type_of_expr(dot_receiver)?,
179 dot_receiver.syntax().clone(),
182 let fuzzy_name_length = fuzzy_name.len();
183 let approximate_node = match current_module.definition_source(ctx.db).value {
184 hir::ModuleSource::SourceFile(s) => s.syntax().clone(),
185 hir::ModuleSource::Module(m) => m.syntax().clone(),
186 hir::ModuleSource::BlockExpr(b) => b.syntax().clone(),
188 let assets_for_path = ImportAssets::for_fuzzy_path(
190 ctx.path_qual.clone(),
196 if matches!(assets_for_path.import_candidate(), ImportCandidate::Path(_))
197 && fuzzy_name_length < 2
199 cov_mark::hit!(ignore_short_input_for_path);
202 Some(assets_for_path)
207 fn compute_fuzzy_completion_order_key(
208 proposed_mod_path: &ModPath,
209 user_input_lowercased: &str,
211 cov_mark::hit!(certain_fuzzy_order_test);
212 let import_name = match proposed_mod_path.segments().last() {
213 Some(name) => name.to_string().to_lowercase(),
214 None => return usize::MAX,
216 match import_name.match_indices(user_input_lowercased).next() {
217 Some((first_matching_index, _)) => first_matching_index,
224 use expect_test::{expect, Expect};
227 item::CompletionKind,
228 test_utils::{check_edit, check_edit_with_config, completion_list, TEST_CONFIG},
231 fn check(ra_fixture: &str, expect: Expect) {
232 let actual = completion_list(ra_fixture, CompletionKind::Magic);
233 expect.assert_eq(&actual);
237 fn function_fuzzy_completion() {
241 //- /lib.rs crate:dep
246 //- /main.rs crate:main deps:dep
262 fn macro_fuzzy_completion() {
264 "macro_with_curlies!",
266 //- /lib.rs crate:dep
267 /// Please call me as macro_with_curlies! {}
269 macro_rules! macro_with_curlies {
273 //- /main.rs crate:main deps:dep
279 use dep::macro_with_curlies;
282 macro_with_curlies! {$0}
289 fn struct_fuzzy_completion() {
293 //- /lib.rs crate:dep
294 pub struct FirstStruct;
295 pub mod some_module {
296 pub struct SecondStruct;
297 pub struct ThirdStruct;
300 //- /main.rs crate:main deps:dep
301 use dep::{FirstStruct, some_module::SecondStruct};
308 use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
318 fn short_paths_are_ignored() {
319 cov_mark::check!(ignore_short_input_for_path);
323 //- /lib.rs crate:dep
324 pub struct FirstStruct;
325 pub mod some_module {
326 pub struct SecondStruct;
327 pub struct ThirdStruct;
330 //- /main.rs crate:main deps:dep
331 use dep::{FirstStruct, some_module::SecondStruct};
342 fn fuzzy_completions_come_in_specific_order() {
343 cov_mark::check!(certain_fuzzy_order_test);
346 //- /lib.rs crate:dep
347 pub struct FirstStruct;
348 pub mod some_module {
349 // already imported, omitted
350 pub struct SecondStruct;
351 // does not contain all letters from the query, omitted
352 pub struct UnrelatedOne;
353 // contains all letters from the query, but not in sequence, displayed last
354 pub struct ThiiiiiirdStruct;
355 // contains all letters from the query, but not in the beginning, displayed second
356 pub struct AfterThirdStruct;
357 // contains all letters from the query in the begginning, displayed first
358 pub struct ThirdStruct;
361 //- /main.rs crate:main deps:dep
362 use dep::{FirstStruct, some_module::SecondStruct};
369 st dep::some_module::ThirdStruct
370 st dep::some_module::AfterThirdStruct
371 st dep::some_module::ThiiiiiirdStruct
377 fn trait_function_fuzzy_completion() {
379 //- /lib.rs crate:dep
381 pub trait TestTrait {
382 const SPECIAL_CONST: u8;
385 fn random_method(&self);
387 pub struct TestStruct {}
388 impl TestTrait for TestStruct {
389 const SPECIAL_CONST: u8 = 42;
390 type HumbleType = ();
391 fn weird_function() {}
392 fn random_method(&self) {}
396 //- /main.rs crate:main deps:dep
398 dep::test_mod::TestStruct::wei$0
405 fn weird_function() (dep::test_mod::TestTrait) fn()
413 use dep::test_mod::TestTrait;
416 dep::test_mod::TestStruct::weird_function()$0
423 fn trait_const_fuzzy_completion() {
425 //- /lib.rs crate:dep
427 pub trait TestTrait {
428 const SPECIAL_CONST: u8;
431 fn random_method(&self);
433 pub struct TestStruct {}
434 impl TestTrait for TestStruct {
435 const SPECIAL_CONST: u8 = 42;
436 type HumbleType = ();
437 fn weird_function() {}
438 fn random_method(&self) {}
442 //- /main.rs crate:main deps:dep
444 dep::test_mod::TestStruct::spe$0
451 ct SPECIAL_CONST (dep::test_mod::TestTrait)
459 use dep::test_mod::TestTrait;
462 dep::test_mod::TestStruct::SPECIAL_CONST
469 fn trait_method_fuzzy_completion() {
471 //- /lib.rs crate:dep
473 pub trait TestTrait {
474 const SPECIAL_CONST: u8;
477 fn random_method(&self);
479 pub struct TestStruct {}
480 impl TestTrait for TestStruct {
481 const SPECIAL_CONST: u8 = 42;
482 type HumbleType = ();
483 fn weird_function() {}
484 fn random_method(&self) {}
488 //- /main.rs crate:main deps:dep
490 let test_struct = dep::test_mod::TestStruct {};
498 me random_method() (dep::test_mod::TestTrait) fn(&self)
506 use dep::test_mod::TestTrait;
509 let test_struct = dep::test_mod::TestStruct {};
510 test_struct.random_method()$0
517 fn no_trait_type_fuzzy_completion() {
520 //- /lib.rs crate:dep
522 pub trait TestTrait {
523 const SPECIAL_CONST: u8;
526 fn random_method(&self);
528 pub struct TestStruct {}
529 impl TestTrait for TestStruct {
530 const SPECIAL_CONST: u8 = 42;
531 type HumbleType = ();
532 fn weird_function() {}
533 fn random_method(&self) {}
537 //- /main.rs crate:main deps:dep
539 dep::test_mod::TestStruct::hum$0
547 fn does_not_propose_names_in_scope() {
550 //- /lib.rs crate:dep
552 pub trait TestTrait {
553 const SPECIAL_CONST: u8;
556 fn random_method(&self);
558 pub struct TestStruct {}
559 impl TestTrait for TestStruct {
560 const SPECIAL_CONST: u8 = 42;
561 type HumbleType = ();
562 fn weird_function() {}
563 fn random_method(&self) {}
567 //- /main.rs crate:main deps:dep
568 use dep::test_mod::TestStruct;
578 fn does_not_propose_traits_in_scope() {
581 //- /lib.rs crate:dep
583 pub trait TestTrait {
584 const SPECIAL_CONST: u8;
587 fn random_method(&self);
589 pub struct TestStruct {}
590 impl TestTrait for TestStruct {
591 const SPECIAL_CONST: u8 = 42;
592 type HumbleType = ();
593 fn weird_function() {}
594 fn random_method(&self) {}
598 //- /main.rs crate:main deps:dep
599 use dep::test_mod::{TestStruct, TestTrait};
601 dep::test_mod::TestStruct::hum$0
609 fn blanket_trait_impl_import() {
613 //- /lib.rs crate:dep
615 pub struct TestStruct {}
616 pub trait TestTrait {
617 fn another_function();
619 impl<T> TestTrait for T {
620 fn another_function() {}
624 //- /main.rs crate:main deps:dep
626 dep::test_mod::TestStruct::ano$0
630 use dep::test_mod::TestTrait;
633 dep::test_mod::TestStruct::another_function()$0
640 fn zero_input_deprecated_assoc_item_completion() {
643 //- /lib.rs crate:dep
646 pub trait TestTrait {
647 const SPECIAL_CONST: u8;
650 fn random_method(&self);
652 pub struct TestStruct {}
653 impl TestTrait for TestStruct {
654 const SPECIAL_CONST: u8 = 42;
655 type HumbleType = ();
656 fn weird_function() {}
657 fn random_method(&self) {}
661 //- /main.rs crate:main deps:dep
663 let test_struct = dep::test_mod::TestStruct {};
668 me random_method() (dep::test_mod::TestTrait) fn(&self) DEPRECATED
674 //- /lib.rs crate:dep
677 pub trait TestTrait {
678 const SPECIAL_CONST: u8;
681 fn random_method(&self);
683 pub struct TestStruct {}
684 impl TestTrait for TestStruct {
685 const SPECIAL_CONST: u8 = 42;
686 type HumbleType = ();
687 fn weird_function() {}
688 fn random_method(&self) {}
692 //- /main.rs crate:main deps:dep
694 dep::test_mod::TestStruct::$0
698 ct SPECIAL_CONST (dep::test_mod::TestTrait) DEPRECATED
699 fn weird_function() (dep::test_mod::TestTrait) fn() DEPRECATED
705 fn no_completions_in_use_statements() {
708 //- /lib.rs crate:dep
713 //- /main.rs crate:main deps:dep
723 fn prefix_config_usage() {
736 let mut config = TEST_CONFIG;
738 config.insert_use.prefix_kind = hir::PrefixKind::ByCrate;
739 check_edit_with_config(
750 use crate::foo::bar::{self, Item};
757 config.insert_use.prefix_kind = hir::PrefixKind::BySelf;
758 check_edit_with_config(
771 use self::foo::bar::Item;
778 config.insert_use.prefix_kind = hir::PrefixKind::Plain;
779 check_edit_with_config(
801 fn unresolved_qualifier() {
818 st foo::bar::baz::Item
843 fn unresolved_assoc_item_container() {
849 pub const TEST_ASSOC: usize = 3;
860 ct TEST_ASSOC (foo::Item)
874 pub const TEST_ASSOC: usize = 3;
885 fn unresolved_assoc_item_container_with_path() {
892 pub const TEST_ASSOC: usize = 3;
904 ct TEST_ASSOC (foo::bar::Item)
919 pub const TEST_ASSOC: usize = 3;
925 bar::Item::TEST_ASSOC
931 fn fuzzy_unresolved_path() {
939 pub const TEST_ASSOC: usize = 3;