about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-01-16 17:52:29 +0000
committerGitHub <noreply@github.com>2021-01-16 17:52:29 +0000
commit9a349f280ff1c6d0b57df80aa3d6720474e4b00a (patch)
tree23bbb365f31438949358a576bc9467fa70fa874e
parent3782c78d7558633be5483a04aa1c098fe76100b9 (diff)
parent497fc232e7d90d8d39c7a13742dd85d758dc2f72 (diff)
downloadrust-9a349f280ff1c6d0b57df80aa3d6720474e4b00a.tar.gz
rust-9a349f280ff1c6d0b57df80aa3d6720474e4b00a.zip
Merge #7295
7295: Share import_assets and related entities r=matklad a=SomeoneToIgnore

Part of https://github.com/rust-analyzer/rust-analyzer/pull/7293
Addresses https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761569558

Prepares `import_assets` and related to be used later for the trait fuzzy importing.
Also moves fuzzy imports into a separate completion module and renames them, as suggested in https://github.com/rust-analyzer/rust-analyzer/pull/7293#discussion_r558896685

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
-rw-r--r--crates/assists/src/assist_config.rs8
-rw-r--r--crates/assists/src/handlers/auto_import.rs9
-rw-r--r--crates/assists/src/handlers/qualify_path.rs40
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests.rs9
-rw-r--r--crates/assists/src/utils.rs1
-rw-r--r--crates/completion/src/completions.rs1
-rw-r--r--crates/completion/src/completions/flyimport.rs291
-rw-r--r--crates/completion/src/completions/unqualified_path.rs251
-rw-r--r--crates/completion/src/config.rs4
-rw-r--r--crates/completion/src/lib.rs5
-rw-r--r--crates/completion/src/render.rs15
-rw-r--r--crates/completion/src/test_utils.rs14
-rw-r--r--crates/ide/src/lib.rs2
-rw-r--r--crates/ide_db/src/helpers.rs1
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs (renamed from crates/assists/src/utils/import_assets.rs)50
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs6
-rw-r--r--crates/ide_db/src/imports_locator.rs6
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs5
-rw-r--r--crates/rust-analyzer/src/config.rs33
-rw-r--r--crates/rust-analyzer/src/to_proto.rs5
21 files changed, 421 insertions, 337 deletions
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs
index 4fe8ea76145..9cabf037c0e 100644
--- a/crates/assists/src/assist_config.rs
+++ b/crates/assists/src/assist_config.rs
@@ -4,7 +4,7 @@
 //! module, and we use to statically check that we only produce snippet
 //! assists if we are allowed to.
 
-use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap};
+use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
 
 use crate::AssistKind;
 
@@ -14,9 +14,3 @@ pub struct AssistConfig {
     pub allowed: Option<Vec<AssistKind>>,
     pub insert_use: InsertUseConfig,
 }
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub struct InsertUseConfig {
-    pub merge: Option<MergeBehavior>,
-    pub prefix_kind: hir::PrefixKind,
-}
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index 55620f0f39d..4e2a4fcd965 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -1,13 +1,11 @@
 use ide_db::helpers::{
+    import_assets::{ImportAssets, ImportCandidate},
     insert_use::{insert_use, ImportScope},
     mod_path_to_ast,
 };
 use syntax::ast;
 
-use crate::{
-    utils::import_assets::{ImportAssets, ImportCandidate},
-    AssistContext, AssistId, AssistKind, Assists, GroupLabel,
-};
+use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
 
 // Feature: Auto Import
 //
@@ -121,8 +119,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
 
 fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
     let name = match import_candidate {
-        ImportCandidate::UnqualifiedName(candidate)
-        | ImportCandidate::QualifierStart(candidate) => format!("Import {}", &candidate.name),
+        ImportCandidate::Path(candidate) => format!("Import {}", &candidate.name),
         ImportCandidate::TraitAssocItem(candidate) => {
             format!("Import a trait for item {}", &candidate.name)
         }
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs
index f7fbf37f4d5..a7d9fd4dc2f 100644
--- a/crates/assists/src/handlers/qualify_path.rs
+++ b/crates/assists/src/handlers/qualify_path.rs
@@ -1,7 +1,10 @@
 use std::iter;
 
 use hir::AsName;
-use ide_db::helpers::mod_path_to_ast;
+use ide_db::helpers::{
+    import_assets::{ImportAssets, ImportCandidate},
+    mod_path_to_ast,
+};
 use ide_db::RootDatabase;
 use syntax::{
     ast,
@@ -12,7 +15,6 @@ use test_utils::mark;
 
 use crate::{
     assist_context::{AssistContext, Assists},
-    utils::import_assets::{ImportAssets, ImportCandidate},
     AssistId, AssistKind, GroupLabel,
 };
 
@@ -53,17 +55,18 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
     let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
 
     let qualify_candidate = match candidate {
-        ImportCandidate::QualifierStart(_) => {
-            mark::hit!(qualify_path_qualifier_start);
-            let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
-            let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
-            QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
-        }
-        ImportCandidate::UnqualifiedName(_) => {
-            mark::hit!(qualify_path_unqualified_name);
-            let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
-            let generics = path.segment()?.generic_arg_list();
-            QualifyCandidate::UnqualifiedName(generics)
+        ImportCandidate::Path(candidate) => {
+            if candidate.qualifier.is_some() {
+                mark::hit!(qualify_path_qualifier_start);
+                let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
+                let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
+                QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
+            } else {
+                mark::hit!(qualify_path_unqualified_name);
+                let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
+                let generics = path.segment()?.generic_arg_list();
+                QualifyCandidate::UnqualifiedName(generics)
+            }
         }
         ImportCandidate::TraitAssocItem(_) => {
             mark::hit!(qualify_path_trait_assoc_item);
@@ -186,7 +189,7 @@ fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> {
 
 fn group_label(candidate: &ImportCandidate) -> GroupLabel {
     let name = match candidate {
-        ImportCandidate::UnqualifiedName(it) | ImportCandidate::QualifierStart(it) => &it.name,
+        ImportCandidate::Path(it) => &it.name,
         ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
     };
     GroupLabel(format!("Qualify {}", name))
@@ -194,8 +197,13 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel {
 
 fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String {
     match candidate {
-        ImportCandidate::UnqualifiedName(_) => format!("Qualify as `{}`", &import),
-        ImportCandidate::QualifierStart(_) => format!("Qualify with `{}`", &import),
+        ImportCandidate::Path(candidate) => {
+            if candidate.qualifier.is_some() {
+                format!("Qualify with `{}`", &import)
+            } else {
+                format!("Qualify as `{}`", &import)
+            }
+        }
         ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import),
         ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import),
     }
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 3d79718060a..14178a6510f 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -24,7 +24,7 @@ use syntax::TextRange;
 
 pub(crate) use crate::assist_context::{AssistContext, Assists};
 
-pub use assist_config::{AssistConfig, InsertUseConfig};
+pub use assist_config::AssistConfig;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum AssistKind {
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs
index 71431b4065c..32bd8698b1f 100644
--- a/crates/assists/src/tests.rs
+++ b/crates/assists/src/tests.rs
@@ -3,16 +3,17 @@ mod generated;
 use hir::Semantics;
 use ide_db::{
     base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
-    helpers::{insert_use::MergeBehavior, SnippetCap},
+    helpers::{
+        insert_use::{InsertUseConfig, MergeBehavior},
+        SnippetCap,
+    },
     source_change::FileSystemEdit,
     RootDatabase,
 };
 use syntax::TextRange;
 use test_utils::{assert_eq_text, extract_offset, extract_range};
 
-use crate::{
-    handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists, InsertUseConfig,
-};
+use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
 use stdx::{format_to, trim_indent};
 
 pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 9ea96eb73bc..fc9f83bab47 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -1,5 +1,4 @@
 //! Assorted functions shared by several assists.
-pub(crate) mod import_assets;
 
 use std::ops;
 
diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs
index 00c9e76f038..c3ce6e51d54 100644
--- a/crates/completion/src/completions.rs
+++ b/crates/completion/src/completions.rs
@@ -13,6 +13,7 @@ pub(crate) mod postfix;
 pub(crate) mod macro_in_item_position;
 pub(crate) mod trait_impl;
 pub(crate) mod mod_;
+pub(crate) mod flyimport;
 
 use hir::{ModPath, ScopeDef, Type};
 
diff --git a/crates/completion/src/completions/flyimport.rs b/crates/completion/src/completions/flyimport.rs
new file mode 100644
index 00000000000..22280963845
--- /dev/null
+++ b/crates/completion/src/completions/flyimport.rs
@@ -0,0 +1,291 @@
+//! Feature: completion with imports-on-the-fly
+//!
+//! When completing names in the current scope, proposes additional imports from other modules or crates,
+//! if they can be qualified in the scope and their name contains all symbols from the completion input
+//! (case-insensitive, in any order or places).
+//!
+//! ```
+//! fn main() {
+//!     pda$0
+//! }
+//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
+//! ```
+//! ->
+//! ```
+//! use std::marker::PhantomData;
+//!
+//! fn main() {
+//!     PhantomData
+//! }
+//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
+//! ```
+//!
+//! .Fuzzy search details
+//!
+//! To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
+//! (i.e. in `HashMap` in the `std::collections::HashMap` path).
+//! For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
+//!
+//! .Import configuration
+//!
+//! It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
+//! Mimics the corresponding behavior of the `Auto Import` feature.
+//!
+//! .LSP and performance implications
+//!
+//! The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
+//! (case sensitive) resolve client capability in its client capabilities.
+//! This way the server is able to defer the costly computations, doing them for a selected completion item only.
+//! For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
+//! which might be slow ergo the feature is automatically disabled.
+//!
+//! .Feature toggle
+//!
+//! The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
+//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
+//! capability enabled.
+
+use either::Either;
+use hir::{ModPath, ScopeDef};
+use ide_db::{helpers::insert_use::ImportScope, imports_locator};
+use syntax::AstNode;
+use test_utils::mark;
+
+use crate::{
+    context::CompletionContext,
+    render::{render_resolution_with_import, RenderContext},
+    ImportEdit,
+};
+
+use super::Completions;
+
+pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
+    if !ctx.config.enable_autoimport_completions {
+        return None;
+    }
+    if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
+        return None;
+    }
+    let potential_import_name = ctx.token.to_string();
+    if potential_import_name.len() < 2 {
+        return None;
+    }
+    let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
+
+    let current_module = ctx.scope.module()?;
+    let anchor = ctx.name_ref_syntax.as_ref()?;
+    let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
+
+    let user_input_lowercased = potential_import_name.to_lowercase();
+    let mut all_mod_paths = imports_locator::find_similar_imports(
+        &ctx.sema,
+        ctx.krate?,
+        Some(40),
+        potential_import_name,
+        true,
+        true,
+    )
+    .filter_map(|import_candidate| {
+        Some(match import_candidate {
+            Either::Left(module_def) => {
+                (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def))
+            }
+            Either::Right(macro_def) => {
+                (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
+            }
+        })
+    })
+    .filter(|(mod_path, _)| mod_path.len() > 1)
+    .collect::<Vec<_>>();
+
+    all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
+        compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
+    });
+
+    acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
+        render_resolution_with_import(
+            RenderContext::new(ctx),
+            ImportEdit { import_path, import_scope: import_scope.clone() },
+            &definition,
+        )
+    }));
+    Some(())
+}
+
+fn compute_fuzzy_completion_order_key(
+    proposed_mod_path: &ModPath,
+    user_input_lowercased: &str,
+) -> usize {
+    mark::hit!(certain_fuzzy_order_test);
+    let proposed_import_name = match proposed_mod_path.segments.last() {
+        Some(name) => name.to_string().to_lowercase(),
+        None => return usize::MAX,
+    };
+    match proposed_import_name.match_indices(user_input_lowercased).next() {
+        Some((first_matching_index, _)) => first_matching_index,
+        None => usize::MAX,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use expect_test::{expect, Expect};
+    use test_utils::mark;
+
+    use crate::{
+        item::CompletionKind,
+        test_utils::{check_edit, completion_list},
+    };
+
+    fn check(ra_fixture: &str, expect: Expect) {
+        let actual = completion_list(ra_fixture, CompletionKind::Magic);
+        expect.assert_eq(&actual);
+    }
+
+    #[test]
+    fn function_fuzzy_completion() {
+        check_edit(
+            "stdin",
+            r#"
+//- /lib.rs crate:dep
+pub mod io {
+    pub fn stdin() {}
+};
+
+//- /main.rs crate:main deps:dep
+fn main() {
+    stdi$0
+}
+"#,
+            r#"
+use dep::io::stdin;
+
+fn main() {
+    stdin()$0
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn macro_fuzzy_completion() {
+        check_edit(
+            "macro_with_curlies!",
+            r#"
+//- /lib.rs crate:dep
+/// Please call me as macro_with_curlies! {}
+#[macro_export]
+macro_rules! macro_with_curlies {
+    () => {}
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+    curli$0
+}
+"#,
+            r#"
+use dep::macro_with_curlies;
+
+fn main() {
+    macro_with_curlies! {$0}
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn struct_fuzzy_completion() {
+        check_edit(
+            "ThirdStruct",
+            r#"
+//- /lib.rs crate:dep
+pub struct FirstStruct;
+pub mod some_module {
+    pub struct SecondStruct;
+    pub struct ThirdStruct;
+}
+
+//- /main.rs crate:main deps:dep
+use dep::{FirstStruct, some_module::SecondStruct};
+
+fn main() {
+    this$0
+}
+"#,
+            r#"
+use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
+
+fn main() {
+    ThirdStruct
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn fuzzy_completions_come_in_specific_order() {
+        mark::check!(certain_fuzzy_order_test);
+        check(
+            r#"
+//- /lib.rs crate:dep
+pub struct FirstStruct;
+pub mod some_module {
+    // already imported, omitted
+    pub struct SecondStruct;
+    // does not contain all letters from the query, omitted
+    pub struct UnrelatedOne;
+    // contains all letters from the query, but not in sequence, displayed last
+    pub struct ThiiiiiirdStruct;
+    // contains all letters from the query, but not in the beginning, displayed second
+    pub struct AfterThirdStruct;
+    // contains all letters from the query in the begginning, displayed first
+    pub struct ThirdStruct;
+}
+
+//- /main.rs crate:main deps:dep
+use dep::{FirstStruct, some_module::SecondStruct};
+
+fn main() {
+    hir$0
+}
+"#,
+            expect![[r#"
+                st dep::some_module::ThirdStruct
+                st dep::some_module::AfterThirdStruct
+                st dep::some_module::ThiiiiiirdStruct
+            "#]],
+        );
+    }
+
+    #[test]
+    fn does_not_propose_names_in_scope() {
+        check(
+            r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+    pub trait TestTrait {
+        const SPECIAL_CONST: u8;
+        type HumbleType;
+        fn weird_function();
+        fn random_method(&self);
+    }
+    pub struct TestStruct {}
+    impl TestTrait for TestStruct {
+        const SPECIAL_CONST: u8 = 42;
+        type HumbleType = ();
+        fn weird_function() {}
+        fn random_method(&self) {}
+    }
+}
+
+//- /main.rs crate:main deps:dep
+use dep::test_mod::TestStruct;
+fn main() {
+    TestSt$0
+}
+"#,
+            expect![[r#""#]],
+        );
+    }
+}
diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs
index 53e1391f34e..ac5596ca49d 100644
--- a/crates/completion/src/completions/unqualified_path.rs
+++ b/crates/completion/src/completions/unqualified_path.rs
@@ -2,17 +2,11 @@
 
 use std::iter;
 
-use either::Either;
-use hir::{Adt, ModPath, ModuleDef, ScopeDef, Type};
-use ide_db::helpers::insert_use::ImportScope;
-use ide_db::imports_locator;
+use hir::{Adt, ModuleDef, ScopeDef, Type};
 use syntax::AstNode;
 use test_utils::mark;
 
-use crate::{
-    render::{render_resolution_with_import, RenderContext},
-    CompletionContext, Completions, ImportEdit,
-};
+use crate::{CompletionContext, Completions};
 
 pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
     if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
@@ -45,10 +39,6 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
         }
         acc.add_resolution(ctx, name.to_string(), &res)
     });
-
-    if ctx.config.enable_autoimport_completions {
-        fuzzy_completion(acc, ctx);
-    }
 }
 
 fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
@@ -77,124 +67,13 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
     }
 }
 
-// Feature: Fuzzy Completion and Autoimports
-//
-// When completing names in the current scope, proposes additional imports from other modules or crates,
-// if they can be qualified in the scope and their name contains all symbols from the completion input
-// (case-insensitive, in any order or places).
-//
-// ```
-// fn main() {
-//     pda$0
-// }
-// # pub mod std { pub mod marker { pub struct PhantomData { } } }
-// ```
-// ->
-// ```
-// use std::marker::PhantomData;
-//
-// fn main() {
-//     PhantomData
-// }
-// # pub mod std { pub mod marker { pub struct PhantomData { } } }
-// ```
-//
-// .Fuzzy search details
-//
-// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
-// (i.e. in `HashMap` in the `std::collections::HashMap` path).
-// For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
-//
-// .Merge Behavior
-//
-// It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
-// Mimics the corresponding behavior of the `Auto Import` feature.
-//
-// .LSP and performance implications
-//
-// The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
-// (case sensitive) resolve client capability in its client capabilities.
-// This way the server is able to defer the costly computations, doing them for a selected completion item only.
-// For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
-// which might be slow ergo the feature is automatically disabled.
-//
-// .Feature toggle
-//
-// The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
-// Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
-// capability enabled.
-fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
-    let potential_import_name = ctx.token.to_string();
-    let _p = profile::span("fuzzy_completion").detail(|| potential_import_name.clone());
-
-    if potential_import_name.len() < 2 {
-        return None;
-    }
-
-    let current_module = ctx.scope.module()?;
-    let anchor = ctx.name_ref_syntax.as_ref()?;
-    let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
-
-    let user_input_lowercased = potential_import_name.to_lowercase();
-    let mut all_mod_paths = imports_locator::find_similar_imports(
-        &ctx.sema,
-        ctx.krate?,
-        Some(40),
-        potential_import_name,
-        true,
-        true,
-    )
-    .filter_map(|import_candidate| {
-        Some(match import_candidate {
-            Either::Left(module_def) => {
-                (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def))
-            }
-            Either::Right(macro_def) => {
-                (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
-            }
-        })
-    })
-    .filter(|(mod_path, _)| mod_path.len() > 1)
-    .collect::<Vec<_>>();
-
-    all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
-        compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
-    });
-
-    acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
-        render_resolution_with_import(
-            RenderContext::new(ctx),
-            ImportEdit { import_path, import_scope: import_scope.clone() },
-            &definition,
-        )
-    }));
-    Some(())
-}
-
-fn compute_fuzzy_completion_order_key(
-    proposed_mod_path: &ModPath,
-    user_input_lowercased: &str,
-) -> usize {
-    mark::hit!(certain_fuzzy_order_test);
-    let proposed_import_name = match proposed_mod_path.segments.last() {
-        Some(name) => name.to_string().to_lowercase(),
-        None => return usize::MAX,
-    };
-    match proposed_import_name.match_indices(user_input_lowercased).next() {
-        Some((first_matching_index, _)) => first_matching_index,
-        None => usize::MAX,
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};
     use test_utils::mark;
 
     use crate::{
-        test_utils::{
-            check_edit, check_edit_with_config, completion_list_with_config, TEST_CONFIG,
-        },
+        test_utils::{check_edit, completion_list_with_config, TEST_CONFIG},
         CompletionConfig, CompletionKind,
     };
 
@@ -855,128 +734,4 @@ impl My$0
             "#]],
         )
     }
-
-    #[test]
-    fn function_fuzzy_completion() {
-        check_edit_with_config(
-            TEST_CONFIG,
-            "stdin",
-            r#"
-//- /lib.rs crate:dep
-pub mod io {
-    pub fn stdin() {}
-};
-
-//- /main.rs crate:main deps:dep
-fn main() {
-    stdi$0
-}
-"#,
-            r#"
-use dep::io::stdin;
-
-fn main() {
-    stdin()$0
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn macro_fuzzy_completion() {
-        check_edit_with_config(
-            TEST_CONFIG,
-            "macro_with_curlies!",
-            r#"
-//- /lib.rs crate:dep
-/// Please call me as macro_with_curlies! {}
-#[macro_export]
-macro_rules! macro_with_curlies {
-    () => {}
-}
-
-//- /main.rs crate:main deps:dep
-fn main() {
-    curli$0
-}
-"#,
-            r#"
-use dep::macro_with_curlies;
-
-fn main() {
-    macro_with_curlies! {$0}
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn struct_fuzzy_completion() {
-        check_edit_with_config(
-            TEST_CONFIG,
-            "ThirdStruct",
-            r#"
-//- /lib.rs crate:dep
-pub struct FirstStruct;
-pub mod some_module {
-    pub struct SecondStruct;
-    pub struct ThirdStruct;
-}
-
-//- /main.rs crate:main deps:dep
-use dep::{FirstStruct, some_module::SecondStruct};
-
-fn main() {
-    this$0
-}
-"#,
-            r#"
-use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
-
-fn main() {
-    ThirdStruct
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn fuzzy_completions_come_in_specific_order() {
-        mark::check!(certain_fuzzy_order_test);
-        check_with_config(
-            TEST_CONFIG,
-            r#"
-//- /lib.rs crate:dep
-pub struct FirstStruct;
-pub mod some_module {
-    // already imported, omitted
-    pub struct SecondStruct;
-    // does not contain all letters from the query, omitted
-    pub struct UnrelatedOne;
-    // contains all letters from the query, but not in sequence, displayed last
-    pub struct ThiiiiiirdStruct;
-    // contains all letters from the query, but not in the beginning, displayed second
-    pub struct AfterThirdStruct;
-    // contains all letters from the query in the begginning, displayed first
-    pub struct ThirdStruct;
-}
-
-//- /main.rs crate:main deps:dep
-use dep::{FirstStruct, some_module::SecondStruct};
-
-fn main() {
-    hir$0
-}
-"#,
-            expect![[r#"
-                fn main()           fn main()
-                st SecondStruct
-                st FirstStruct
-                md dep
-                st dep::some_module::ThirdStruct
-                st dep::some_module::AfterThirdStruct
-                st dep::some_module::ThiiiiiirdStruct
-            "#]],
-        );
-    }
 }
diff --git a/crates/completion/src/config.rs b/crates/completion/src/config.rs
index b4439b7d143..58fc700f33d 100644
--- a/crates/completion/src/config.rs
+++ b/crates/completion/src/config.rs
@@ -4,7 +4,7 @@
 //! module, and we use to statically check that we only produce snippet
 //! completions if we are allowed to.
 
-use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap};
+use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct CompletionConfig {
@@ -13,5 +13,5 @@ pub struct CompletionConfig {
     pub add_call_parenthesis: bool,
     pub add_call_argument_snippets: bool,
     pub snippet_cap: Option<SnippetCap>,
-    pub merge: Option<MergeBehavior>,
+    pub insert_use: InsertUseConfig,
 }
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index 6cba88a6b87..ee1b822e7c0 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -127,6 +127,7 @@ pub fn completions(
     completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
     completions::trait_impl::complete_trait_impl(&mut acc, &ctx);
     completions::mod_::complete_mod(&mut acc, &ctx);
+    completions::flyimport::import_on_the_fly(&mut acc, &ctx);
 
     Some(acc)
 }
@@ -153,7 +154,9 @@ pub fn resolve_completion_edits(
         })
         .find(|mod_path| mod_path.to_string() == full_import_path)?;
 
-    ImportEdit { import_path, import_scope }.to_text_edit(config.merge).map(|edit| vec![edit])
+    ImportEdit { import_path, import_scope }
+        .to_text_edit(config.insert_use.merge)
+        .map(|edit| vec![edit])
 }
 
 #[cfg(test)]
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs
index e93c59f7177..820dd01d1f7 100644
--- a/crates/completion/src/render.rs
+++ b/crates/completion/src/render.rs
@@ -51,11 +51,16 @@ pub(crate) fn render_resolution_with_import<'a>(
     import_edit: ImportEdit,
     resolution: &ScopeDef,
 ) -> Option<CompletionItem> {
-    Render::new(ctx).render_resolution(
-        import_edit.import_path.segments.last()?.to_string(),
-        Some(import_edit),
-        resolution,
-    )
+    Render::new(ctx)
+        .render_resolution(
+            import_edit.import_path.segments.last()?.to_string(),
+            Some(import_edit),
+            resolution,
+        )
+        .map(|mut item| {
+            item.completion_kind = CompletionKind::Magic;
+            item
+        })
 }
 
 /// 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 3f4b9d4acc8..6ea6da9893c 100644
--- a/crates/completion/src/test_utils.rs
+++ b/crates/completion/src/test_utils.rs
@@ -1,9 +1,12 @@
 //! Runs completion for testing purposes.
 
-use hir::Semantics;
+use hir::{PrefixKind, Semantics};
 use ide_db::{
     base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
-    helpers::{insert_use::MergeBehavior, SnippetCap},
+    helpers::{
+        insert_use::{InsertUseConfig, MergeBehavior},
+        SnippetCap,
+    },
     RootDatabase,
 };
 use itertools::Itertools;
@@ -19,7 +22,10 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
     add_call_parenthesis: true,
     add_call_argument_snippets: true,
     snippet_cap: SnippetCap::new(true),
-    merge: Some(MergeBehavior::Full),
+    insert_use: InsertUseConfig {
+        merge: Some(MergeBehavior::Full),
+        prefix_kind: PrefixKind::Plain,
+    },
 };
 
 /// Creates analysis from a multi-file fixture, returns positions marked with $0.
@@ -110,7 +116,7 @@ pub(crate) fn check_edit_with_config(
 
     let mut combined_edit = completion.text_edit().to_owned();
     if let Some(import_text_edit) =
-        completion.import_to_add().and_then(|edit| edit.to_text_edit(config.merge))
+        completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use.merge))
     {
         combined_edit.union(import_text_edit).expect(
             "Failed to apply completion resolve changes: change ranges overlap, but should not",
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index afd552008db..f8d69382e03 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -80,7 +80,7 @@ pub use crate::{
         HlRange,
     },
 };
-pub use assists::{Assist, AssistConfig, AssistId, AssistKind, InsertUseConfig};
+pub use assists::{Assist, AssistConfig, AssistId, AssistKind};
 pub use completion::{
     CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit,
     InsertTextFormat,
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index c6763ae369e..0dcc4dd29db 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -1,5 +1,6 @@
 //! A module with ide helpers for high-level ide features.
 pub mod insert_use;
+pub mod import_assets;
 
 use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait};
 use syntax::ast::{self, make};
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index 4ce82c1ba5e..edc3da318e0 100644
--- a/crates/assists/src/utils/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -1,19 +1,17 @@
 //! Look up accessible paths for items.
 use either::Either;
 use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
-use ide_db::{imports_locator, RootDatabase};
 use rustc_hash::FxHashSet;
 use syntax::{ast, AstNode, SyntaxNode};
 
-use crate::assist_config::InsertUseConfig;
+use crate::{imports_locator, RootDatabase};
+
+use super::insert_use::InsertUseConfig;
 
 #[derive(Debug)]
-pub(crate) enum ImportCandidate {
-    /// Simple name like 'HashMap'
-    UnqualifiedName(PathImportCandidate),
-    /// First part of the qualified name.
-    /// For 'std::collections::HashMap', that will be 'std'.
-    QualifierStart(PathImportCandidate),
+pub enum ImportCandidate {
+    // A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
+    Path(PathImportCandidate),
     /// A trait associated function (with no self parameter) or associated constant.
     /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
     /// and `name` is the `test_function`
@@ -25,25 +23,26 @@ pub(crate) enum ImportCandidate {
 }
 
 #[derive(Debug)]
-pub(crate) struct TraitImportCandidate {
-    pub(crate) ty: hir::Type,
-    pub(crate) name: ast::NameRef,
+pub struct TraitImportCandidate {
+    pub ty: hir::Type,
+    pub name: ast::NameRef,
 }
 
 #[derive(Debug)]
-pub(crate) struct PathImportCandidate {
-    pub(crate) name: ast::NameRef,
+pub struct PathImportCandidate {
+    pub qualifier: Option<ast::Path>,
+    pub name: ast::NameRef,
 }
 
 #[derive(Debug)]
-pub(crate) struct ImportAssets {
+pub struct ImportAssets {
     import_candidate: ImportCandidate,
     module_with_name_to_import: hir::Module,
     syntax_under_caret: SyntaxNode,
 }
 
 impl ImportAssets {
-    pub(crate) fn for_method_call(
+    pub fn for_method_call(
         method_call: ast::MethodCallExpr,
         sema: &Semantics<RootDatabase>,
     ) -> Option<Self> {
@@ -56,7 +55,7 @@ impl ImportAssets {
         })
     }
 
-    pub(crate) fn for_regular_path(
+    pub fn for_regular_path(
         path_under_caret: ast::Path,
         sema: &Semantics<RootDatabase>,
     ) -> Option<Self> {
@@ -73,24 +72,23 @@ impl ImportAssets {
         })
     }
 
-    pub(crate) fn syntax_under_caret(&self) -> &SyntaxNode {
+    pub fn syntax_under_caret(&self) -> &SyntaxNode {
         &self.syntax_under_caret
     }
 
-    pub(crate) fn import_candidate(&self) -> &ImportCandidate {
+    pub fn import_candidate(&self) -> &ImportCandidate {
         &self.import_candidate
     }
 
     fn get_search_query(&self) -> &str {
         match &self.import_candidate {
-            ImportCandidate::UnqualifiedName(candidate)
-            | ImportCandidate::QualifierStart(candidate) => candidate.name.text(),
+            ImportCandidate::Path(candidate) => candidate.name.text(),
             ImportCandidate::TraitAssocItem(candidate)
             | ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
         }
     }
 
-    pub(crate) fn search_for_imports(
+    pub fn search_for_imports(
         &self,
         sema: &Semantics<RootDatabase>,
         config: &InsertUseConfig,
@@ -101,7 +99,7 @@ impl ImportAssets {
 
     /// This may return non-absolute paths if a part of the returned path is already imported into scope.
     #[allow(dead_code)]
-    pub(crate) fn search_for_relative_paths(
+    pub fn search_for_relative_paths(
         &self,
         sema: &Semantics<RootDatabase>,
     ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
@@ -253,10 +251,14 @@ impl ImportCandidate {
                     _ => return None,
                 }
             } else {
-                ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start })
+                ImportCandidate::Path(PathImportCandidate {
+                    qualifier: Some(qualifier),
+                    name: qualifier_start,
+                })
             }
         } else {
-            ImportCandidate::UnqualifiedName(PathImportCandidate {
+            ImportCandidate::Path(PathImportCandidate {
+                qualifier: None,
                 name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
             })
         };
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index d2f9f5d25c1..877d4f1c79a 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -15,6 +15,12 @@ use syntax::{
 };
 use test_utils::mark;
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct InsertUseConfig {
+    pub merge: Option<MergeBehavior>,
+    pub prefix_kind: hir::PrefixKind,
+}
+
 #[derive(Debug, Clone)]
 pub enum ImportScope {
     File(ast::SourceFile),
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs
index e9f23adf838..d111fba9257 100644
--- a/crates/ide_db/src/imports_locator.rs
+++ b/crates/ide_db/src/imports_locator.rs
@@ -12,6 +12,8 @@ use crate::{
 use either::Either;
 use rustc_hash::FxHashSet;
 
+const QUERY_SEARCH_LIMIT: usize = 40;
+
 pub fn find_exact_imports<'a>(
     sema: &Semantics<'a, RootDatabase>,
     krate: Crate,
@@ -24,11 +26,11 @@ pub fn find_exact_imports<'a>(
         {
             let mut local_query = symbol_index::Query::new(name_to_import.clone());
             local_query.exact();
-            local_query.limit(40);
+            local_query.limit(QUERY_SEARCH_LIMIT);
             local_query
         },
         import_map::Query::new(name_to_import)
-            .limit(40)
+            .limit(QUERY_SEARCH_LIMIT)
             .name_only()
             .search_mode(import_map::SearchMode::Equals)
             .case_sensitive(),
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 7d3fda7a84c..a02c8327fe2 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -3,6 +3,7 @@
 use std::{env, path::PathBuf, str::FromStr, sync::Arc, time::Instant};
 
 use anyhow::{bail, format_err, Result};
+use hir::PrefixKind;
 use ide::{
     Analysis, AnalysisHost, Change, CompletionConfig, DiagnosticsConfig, FilePosition, LineCol,
 };
@@ -11,7 +12,7 @@ use ide_db::{
         salsa::{Database, Durability},
         FileId,
     },
-    helpers::SnippetCap,
+    helpers::{insert_use::InsertUseConfig, SnippetCap},
 };
 use vfs::AbsPathBuf;
 
@@ -96,7 +97,7 @@ impl BenchCmd {
                         add_call_parenthesis: true,
                         add_call_argument_snippets: true,
                         snippet_cap: SnippetCap::new(true),
-                        merge: None,
+                        insert_use: InsertUseConfig { merge: None, prefix_kind: PrefixKind::Plain },
                     };
                     let res = do_work(&mut host, file_id, |analysis| {
                         analysis.completions(&options, file_position)
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 27b92a5a9bf..ce9655818c6 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -11,11 +11,11 @@ use std::{convert::TryFrom, ffi::OsString, iter, path::PathBuf};
 
 use flycheck::FlycheckConfig;
 use hir::PrefixKind;
-use ide::{
-    AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig,
-    InsertUseConfig,
+use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig};
+use ide_db::helpers::{
+    insert_use::{InsertUseConfig, MergeBehavior},
+    SnippetCap,
 };
-use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap};
 use itertools::Itertools;
 use lsp_types::{ClientCapabilities, MarkupKind};
 use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest};
@@ -542,11 +542,18 @@ impl Config {
             max_length: self.data.inlayHints_maxLength,
         }
     }
-    fn merge_behavior(&self) -> Option<MergeBehavior> {
-        match self.data.assist_importMergeBehavior {
-            MergeBehaviorDef::None => None,
-            MergeBehaviorDef::Full => Some(MergeBehavior::Full),
-            MergeBehaviorDef::Last => Some(MergeBehavior::Last),
+    fn insert_use_config(&self) -> InsertUseConfig {
+        InsertUseConfig {
+            merge: match self.data.assist_importMergeBehavior {
+                MergeBehaviorDef::None => None,
+                MergeBehaviorDef::Full => Some(MergeBehavior::Full),
+                MergeBehaviorDef::Last => Some(MergeBehavior::Last),
+            },
+            prefix_kind: match self.data.assist_importPrefix {
+                ImportPrefixDef::Plain => PrefixKind::Plain,
+                ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
+                ImportPrefixDef::BySelf => PrefixKind::BySelf,
+            },
         }
     }
     pub fn completion(&self) -> CompletionConfig {
@@ -556,7 +563,7 @@ impl Config {
                 && completion_item_edit_resolve(&self.caps),
             add_call_parenthesis: self.data.completion_addCallParenthesis,
             add_call_argument_snippets: self.data.completion_addCallArgumentSnippets,
-            merge: self.merge_behavior(),
+            insert_use: self.insert_use_config(),
             snippet_cap: SnippetCap::new(try_or!(
                 self.caps
                     .text_document
@@ -575,7 +582,11 @@ impl Config {
             snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
             allowed: None,
             insert_use: InsertUseConfig {
-                merge: self.merge_behavior(),
+                merge: match self.data.assist_importMergeBehavior {
+                    MergeBehaviorDef::None => None,
+                    MergeBehaviorDef::Full => Some(MergeBehavior::Full),
+                    MergeBehaviorDef::Last => Some(MergeBehavior::Last),
+                },
                 prefix_kind: match self.data.assist_importPrefix {
                     ImportPrefixDef::Plain => PrefixKind::Plain,
                     ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index dc67d19a754..1ff2d3fea65 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -861,8 +861,9 @@ pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
 
 #[cfg(test)]
 mod tests {
+    use hir::PrefixKind;
     use ide::Analysis;
-    use ide_db::helpers::SnippetCap;
+    use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
 
     use super::*;
 
@@ -887,7 +888,7 @@ mod tests {
                     add_call_parenthesis: true,
                     add_call_argument_snippets: true,
                     snippet_cap: SnippetCap::new(true),
-                    merge: None,
+                    insert_use: InsertUseConfig { merge: None, prefix_kind: PrefixKind::Plain },
                 },
                 ide_db::base_db::FilePosition { file_id, offset },
             )