]> git.lizzy.rs Git - rust.git/commitdiff
Share import_assets and related entities
authorKirill Bulatov <mail4score@gmail.com>
Sat, 16 Jan 2021 17:33:36 +0000 (19:33 +0200)
committerKirill Bulatov <mail4score@gmail.com>
Sat, 16 Jan 2021 17:33:36 +0000 (19:33 +0200)
21 files changed:
crates/assists/src/assist_config.rs
crates/assists/src/handlers/auto_import.rs
crates/assists/src/handlers/qualify_path.rs
crates/assists/src/lib.rs
crates/assists/src/tests.rs
crates/assists/src/utils.rs
crates/assists/src/utils/import_assets.rs [deleted file]
crates/completion/src/completions.rs
crates/completion/src/completions/flyimport.rs [new file with mode: 0644]
crates/completion/src/completions/unqualified_path.rs
crates/completion/src/config.rs
crates/completion/src/lib.rs
crates/completion/src/test_utils.rs
crates/ide/src/lib.rs
crates/ide_db/src/helpers.rs
crates/ide_db/src/helpers/import_assets.rs [new file with mode: 0644]
crates/ide_db/src/helpers/insert_use.rs
crates/ide_db/src/imports_locator.rs
crates/rust-analyzer/src/cli/analysis_bench.rs
crates/rust-analyzer/src/config.rs
crates/rust-analyzer/src/to_proto.rs

index 4fe8ea76145caadae3fd61736f3c7aab709461f5..9cabf037c0e5046b051b22123d244f47d70d3bec 100644 (file)
@@ -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,
-}
index 55620f0f39d6425cce574ec7dc634c2930cb70a3..4e2a4fcd9657c169339ef21234a1f41bb6f3c931 100644 (file)
@@ -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)
         }
index f7fbf37f4d5068d7d2ded072eda2006a78da5353..a7d9fd4dc2f617a63a1208fd282d29d8f8d7330b 100644 (file)
@@ -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 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),
     }
index 3d79718060a4eaac54fe93854dcec3251c6124f6..14178a6510f3096fe12d57fb7e006c4970feca44 100644 (file)
@@ -24,7 +24,7 @@ macro_rules! eprintln {
 
 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 {
index 71431b4065c7b1d32e3238c156766aea73c340fb..32bd8698b1ff3307a038ba32ba7deb8dd26fa888 100644 (file)
@@ -3,16 +3,17 @@
 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 {
index 9ea96eb73bca53d9947e9947c772b5ad718644b0..fc9f83bab4793f6aa400f4875ecdfca5eade5a51 100644 (file)
@@ -1,5 +1,4 @@
 //! Assorted functions shared by several assists.
-pub(crate) mod import_assets;
 
 use std::ops;
 
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs
deleted file mode 100644 (file)
index 4ce82c1..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-//! 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;
-
-#[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),
-    /// 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`
-    TraitAssocItem(TraitImportCandidate),
-    /// A trait method with self parameter.
-    /// For 'test_enum.test_method()', `ty` is the `test_enum` expression type
-    /// and `name` is the `test_method`
-    TraitMethod(TraitImportCandidate),
-}
-
-#[derive(Debug)]
-pub(crate) struct TraitImportCandidate {
-    pub(crate) ty: hir::Type,
-    pub(crate) name: ast::NameRef,
-}
-
-#[derive(Debug)]
-pub(crate) struct PathImportCandidate {
-    pub(crate) name: ast::NameRef,
-}
-
-#[derive(Debug)]
-pub(crate) struct ImportAssets {
-    import_candidate: ImportCandidate,
-    module_with_name_to_import: hir::Module,
-    syntax_under_caret: SyntaxNode,
-}
-
-impl ImportAssets {
-    pub(crate) fn for_method_call(
-        method_call: ast::MethodCallExpr,
-        sema: &Semantics<RootDatabase>,
-    ) -> Option<Self> {
-        let syntax_under_caret = method_call.syntax().to_owned();
-        let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
-        Some(Self {
-            import_candidate: ImportCandidate::for_method_call(sema, &method_call)?,
-            module_with_name_to_import,
-            syntax_under_caret,
-        })
-    }
-
-    pub(crate) fn for_regular_path(
-        path_under_caret: ast::Path,
-        sema: &Semantics<RootDatabase>,
-    ) -> Option<Self> {
-        let syntax_under_caret = path_under_caret.syntax().to_owned();
-        if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
-            return None;
-        }
-
-        let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
-        Some(Self {
-            import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?,
-            module_with_name_to_import,
-            syntax_under_caret,
-        })
-    }
-
-    pub(crate) fn syntax_under_caret(&self) -> &SyntaxNode {
-        &self.syntax_under_caret
-    }
-
-    pub(crate) 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::TraitAssocItem(candidate)
-            | ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
-        }
-    }
-
-    pub(crate) fn search_for_imports(
-        &self,
-        sema: &Semantics<RootDatabase>,
-        config: &InsertUseConfig,
-    ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
-        let _p = profile::span("import_assists::search_for_imports");
-        self.search_for(sema, Some(config.prefix_kind))
-    }
-
-    /// 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(
-        &self,
-        sema: &Semantics<RootDatabase>,
-    ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
-        let _p = profile::span("import_assists::search_for_relative_paths");
-        self.search_for(sema, None)
-    }
-
-    fn search_for(
-        &self,
-        sema: &Semantics<RootDatabase>,
-        prefixed: Option<hir::PrefixKind>,
-    ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
-        let db = sema.db;
-        let mut trait_candidates = FxHashSet::default();
-        let current_crate = self.module_with_name_to_import.krate();
-
-        let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| {
-            trait_candidates.clear();
-            match &self.import_candidate {
-                ImportCandidate::TraitAssocItem(trait_candidate) => {
-                    let located_assoc_item = match candidate {
-                        Either::Left(ModuleDef::Function(located_function)) => {
-                            located_function.as_assoc_item(db)
-                        }
-                        Either::Left(ModuleDef::Const(located_const)) => {
-                            located_const.as_assoc_item(db)
-                        }
-                        _ => None,
-                    }
-                    .map(|assoc| assoc.container(db))
-                    .and_then(Self::assoc_to_trait)?;
-
-                    trait_candidates.insert(located_assoc_item.into());
-
-                    trait_candidate
-                        .ty
-                        .iterate_path_candidates(
-                            db,
-                            current_crate,
-                            &trait_candidates,
-                            None,
-                            |_, assoc| Self::assoc_to_trait(assoc.container(db)),
-                        )
-                        .map(ModuleDef::from)
-                        .map(Either::Left)
-                }
-                ImportCandidate::TraitMethod(trait_candidate) => {
-                    let located_assoc_item =
-                        if let Either::Left(ModuleDef::Function(located_function)) = candidate {
-                            located_function
-                                .as_assoc_item(db)
-                                .map(|assoc| assoc.container(db))
-                                .and_then(Self::assoc_to_trait)
-                        } else {
-                            None
-                        }?;
-
-                    trait_candidates.insert(located_assoc_item.into());
-
-                    trait_candidate
-                        .ty
-                        .iterate_method_candidates(
-                            db,
-                            current_crate,
-                            &trait_candidates,
-                            None,
-                            |_, function| {
-                                Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
-                            },
-                        )
-                        .map(ModuleDef::from)
-                        .map(Either::Left)
-                }
-                _ => Some(candidate),
-            }
-        };
-
-        let mut res = imports_locator::find_exact_imports(
-            sema,
-            current_crate,
-            self.get_search_query().to_string(),
-        )
-        .filter_map(filter)
-        .filter_map(|candidate| {
-            let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
-            if let Some(prefix_kind) = prefixed {
-                self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
-            } else {
-                self.module_with_name_to_import.find_use_path(db, item)
-            }
-            .map(|path| (path, item))
-        })
-        .filter(|(use_path, _)| use_path.len() > 1)
-        .take(20)
-        .collect::<Vec<_>>();
-        res.sort_by_key(|(path, _)| path.clone());
-        res
-    }
-
-    fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
-        if let AssocItemContainer::Trait(extracted_trait) = assoc {
-            Some(extracted_trait)
-        } else {
-            None
-        }
-    }
-}
-
-impl ImportCandidate {
-    fn for_method_call(
-        sema: &Semantics<RootDatabase>,
-        method_call: &ast::MethodCallExpr,
-    ) -> Option<Self> {
-        match sema.resolve_method_call(method_call) {
-            Some(_) => None,
-            None => Some(Self::TraitMethod(TraitImportCandidate {
-                ty: sema.type_of_expr(&method_call.receiver()?)?,
-                name: method_call.name_ref()?,
-            })),
-        }
-    }
-
-    fn for_regular_path(
-        sema: &Semantics<RootDatabase>,
-        path_under_caret: &ast::Path,
-    ) -> Option<Self> {
-        if sema.resolve_path(path_under_caret).is_some() {
-            return None;
-        }
-
-        let segment = path_under_caret.segment()?;
-        let candidate = if let Some(qualifier) = path_under_caret.qualifier() {
-            let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
-            let qualifier_start_path =
-                qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
-            if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
-                let qualifier_resolution = if qualifier_start_path == qualifier {
-                    qualifier_start_resolution
-                } else {
-                    sema.resolve_path(&qualifier)?
-                };
-                match qualifier_resolution {
-                    hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
-                        ImportCandidate::TraitAssocItem(TraitImportCandidate {
-                            ty: assoc_item_path.ty(sema.db),
-                            name: segment.name_ref()?,
-                        })
-                    }
-                    _ => return None,
-                }
-            } else {
-                ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start })
-            }
-        } else {
-            ImportCandidate::UnqualifiedName(PathImportCandidate {
-                name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
-            })
-        };
-        Some(candidate)
-    }
-}
index 00c9e76f038e1871ccc3f2b86bc0623e3cf08fde..c3ce6e51d54b73fdc245a9eade122a0a167a0dd3 100644 (file)
@@ -13,6 +13,7 @@
 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 (file)
index 0000000..54f8e6d
--- /dev/null
@@ -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#""#]],
+        );
+    }
+}
index 53e1391f34e572a61be6852c68969ead635534f5..ac5596ca49d598483450ac79fc772d5ef0a3d5e3 100644 (file)
@@ -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
-            "#]],
-        );
-    }
 }
index b4439b7d143638dbd4e8c12c3e9aaec2ea312c64..58fc700f33d5c00ce75567fc317b2b1285645b5f 100644 (file)
@@ -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,
 }
index 6cba88a6b8760b46c60b1078db66348f3f3a3e2e..ee1b822e7c04e9a78865b9f3b28d619a477e0ac5 100644 (file)
@@ -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)]
index 3f4b9d4acc80b56c916d0f8fdc4f94eec7ae3d89..6ea6da9893c8bfb60bae3eb4f4dfa38ecc09a3a9 100644 (file)
@@ -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;
     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",
index afd552008db1431fb829e635304ba5bd53d42022..f8d69382e03356f2ef38c95e70ff992dfd71529c 100644 (file)
@@ -80,7 +80,7 @@ macro_rules! eprintln {
         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,
index c6763ae369e57e41957e1ef6a967f4f4be247d05..0dcc4dd29dbda85949abf16735bf7b55ba260c15 100644 (file)
@@ -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/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
new file mode 100644 (file)
index 0000000..edc3da3
--- /dev/null
@@ -0,0 +1,267 @@
+//! Look up accessible paths for items.
+use either::Either;
+use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
+use rustc_hash::FxHashSet;
+use syntax::{ast, AstNode, SyntaxNode};
+
+use crate::{imports_locator, RootDatabase};
+
+use super::insert_use::InsertUseConfig;
+
+#[derive(Debug)]
+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`
+    TraitAssocItem(TraitImportCandidate),
+    /// A trait method with self parameter.
+    /// For 'test_enum.test_method()', `ty` is the `test_enum` expression type
+    /// and `name` is the `test_method`
+    TraitMethod(TraitImportCandidate),
+}
+
+#[derive(Debug)]
+pub struct TraitImportCandidate {
+    pub ty: hir::Type,
+    pub name: ast::NameRef,
+}
+
+#[derive(Debug)]
+pub struct PathImportCandidate {
+    pub qualifier: Option<ast::Path>,
+    pub name: ast::NameRef,
+}
+
+#[derive(Debug)]
+pub struct ImportAssets {
+    import_candidate: ImportCandidate,
+    module_with_name_to_import: hir::Module,
+    syntax_under_caret: SyntaxNode,
+}
+
+impl ImportAssets {
+    pub fn for_method_call(
+        method_call: ast::MethodCallExpr,
+        sema: &Semantics<RootDatabase>,
+    ) -> Option<Self> {
+        let syntax_under_caret = method_call.syntax().to_owned();
+        let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
+        Some(Self {
+            import_candidate: ImportCandidate::for_method_call(sema, &method_call)?,
+            module_with_name_to_import,
+            syntax_under_caret,
+        })
+    }
+
+    pub fn for_regular_path(
+        path_under_caret: ast::Path,
+        sema: &Semantics<RootDatabase>,
+    ) -> Option<Self> {
+        let syntax_under_caret = path_under_caret.syntax().to_owned();
+        if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
+            return None;
+        }
+
+        let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
+        Some(Self {
+            import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?,
+            module_with_name_to_import,
+            syntax_under_caret,
+        })
+    }
+
+    pub fn syntax_under_caret(&self) -> &SyntaxNode {
+        &self.syntax_under_caret
+    }
+
+    pub fn import_candidate(&self) -> &ImportCandidate {
+        &self.import_candidate
+    }
+
+    fn get_search_query(&self) -> &str {
+        match &self.import_candidate {
+            ImportCandidate::Path(candidate) => candidate.name.text(),
+            ImportCandidate::TraitAssocItem(candidate)
+            | ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
+        }
+    }
+
+    pub fn search_for_imports(
+        &self,
+        sema: &Semantics<RootDatabase>,
+        config: &InsertUseConfig,
+    ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
+        let _p = profile::span("import_assists::search_for_imports");
+        self.search_for(sema, Some(config.prefix_kind))
+    }
+
+    /// This may return non-absolute paths if a part of the returned path is already imported into scope.
+    #[allow(dead_code)]
+    pub fn search_for_relative_paths(
+        &self,
+        sema: &Semantics<RootDatabase>,
+    ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
+        let _p = profile::span("import_assists::search_for_relative_paths");
+        self.search_for(sema, None)
+    }
+
+    fn search_for(
+        &self,
+        sema: &Semantics<RootDatabase>,
+        prefixed: Option<hir::PrefixKind>,
+    ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
+        let db = sema.db;
+        let mut trait_candidates = FxHashSet::default();
+        let current_crate = self.module_with_name_to_import.krate();
+
+        let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| {
+            trait_candidates.clear();
+            match &self.import_candidate {
+                ImportCandidate::TraitAssocItem(trait_candidate) => {
+                    let located_assoc_item = match candidate {
+                        Either::Left(ModuleDef::Function(located_function)) => {
+                            located_function.as_assoc_item(db)
+                        }
+                        Either::Left(ModuleDef::Const(located_const)) => {
+                            located_const.as_assoc_item(db)
+                        }
+                        _ => None,
+                    }
+                    .map(|assoc| assoc.container(db))
+                    .and_then(Self::assoc_to_trait)?;
+
+                    trait_candidates.insert(located_assoc_item.into());
+
+                    trait_candidate
+                        .ty
+                        .iterate_path_candidates(
+                            db,
+                            current_crate,
+                            &trait_candidates,
+                            None,
+                            |_, assoc| Self::assoc_to_trait(assoc.container(db)),
+                        )
+                        .map(ModuleDef::from)
+                        .map(Either::Left)
+                }
+                ImportCandidate::TraitMethod(trait_candidate) => {
+                    let located_assoc_item =
+                        if let Either::Left(ModuleDef::Function(located_function)) = candidate {
+                            located_function
+                                .as_assoc_item(db)
+                                .map(|assoc| assoc.container(db))
+                                .and_then(Self::assoc_to_trait)
+                        } else {
+                            None
+                        }?;
+
+                    trait_candidates.insert(located_assoc_item.into());
+
+                    trait_candidate
+                        .ty
+                        .iterate_method_candidates(
+                            db,
+                            current_crate,
+                            &trait_candidates,
+                            None,
+                            |_, function| {
+                                Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
+                            },
+                        )
+                        .map(ModuleDef::from)
+                        .map(Either::Left)
+                }
+                _ => Some(candidate),
+            }
+        };
+
+        let mut res = imports_locator::find_exact_imports(
+            sema,
+            current_crate,
+            self.get_search_query().to_string(),
+        )
+        .filter_map(filter)
+        .filter_map(|candidate| {
+            let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
+            if let Some(prefix_kind) = prefixed {
+                self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
+            } else {
+                self.module_with_name_to_import.find_use_path(db, item)
+            }
+            .map(|path| (path, item))
+        })
+        .filter(|(use_path, _)| use_path.len() > 1)
+        .take(20)
+        .collect::<Vec<_>>();
+        res.sort_by_key(|(path, _)| path.clone());
+        res
+    }
+
+    fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
+        if let AssocItemContainer::Trait(extracted_trait) = assoc {
+            Some(extracted_trait)
+        } else {
+            None
+        }
+    }
+}
+
+impl ImportCandidate {
+    fn for_method_call(
+        sema: &Semantics<RootDatabase>,
+        method_call: &ast::MethodCallExpr,
+    ) -> Option<Self> {
+        match sema.resolve_method_call(method_call) {
+            Some(_) => None,
+            None => Some(Self::TraitMethod(TraitImportCandidate {
+                ty: sema.type_of_expr(&method_call.receiver()?)?,
+                name: method_call.name_ref()?,
+            })),
+        }
+    }
+
+    fn for_regular_path(
+        sema: &Semantics<RootDatabase>,
+        path_under_caret: &ast::Path,
+    ) -> Option<Self> {
+        if sema.resolve_path(path_under_caret).is_some() {
+            return None;
+        }
+
+        let segment = path_under_caret.segment()?;
+        let candidate = if let Some(qualifier) = path_under_caret.qualifier() {
+            let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
+            let qualifier_start_path =
+                qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
+            if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
+                let qualifier_resolution = if qualifier_start_path == qualifier {
+                    qualifier_start_resolution
+                } else {
+                    sema.resolve_path(&qualifier)?
+                };
+                match qualifier_resolution {
+                    hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
+                        ImportCandidate::TraitAssocItem(TraitImportCandidate {
+                            ty: assoc_item_path.ty(sema.db),
+                            name: segment.name_ref()?,
+                        })
+                    }
+                    _ => return None,
+                }
+            } else {
+                ImportCandidate::Path(PathImportCandidate {
+                    qualifier: Some(qualifier),
+                    name: qualifier_start,
+                })
+            }
+        } else {
+            ImportCandidate::Path(PathImportCandidate {
+                qualifier: None,
+                name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
+            })
+        };
+        Some(candidate)
+    }
+}
index d2f9f5d25c193a8705b57d0e5ad658353dad45f5..877d4f1c79a53de5cf2cb9359c914d1eba173a6c 100644 (file)
 };
 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),
index e9f23adf8388d8d7fa439168a3f8f353a9fe19c3..d111fba9257f13c5da3c5321bda9dee2f955a228 100644 (file)
@@ -12,6 +12,8 @@
 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(),
index 7d3fda7a84c91ad71b9d2eb89d0cc82228977b4c..a02c8327fe2f77fb4befda9e85addbabbba5a045 100644 (file)
@@ -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 @@
         salsa::{Database, Durability},
         FileId,
     },
-    helpers::SnippetCap,
+    helpers::{insert_use::InsertUseConfig, SnippetCap},
 };
 use vfs::AbsPathBuf;
 
@@ -96,7 +97,7 @@ pub fn run(self, verbosity: Verbosity) -> Result<()> {
                         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)
index 27b92a5a9bf47478e06488758518761e0a7712f5..ce9655818c6821bb122d140135b6e22526ccf68d 100644 (file)
 
 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 @@ pub fn inlay_hints(&self) -> InlayHintsConfig {
             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 @@ pub fn completion(&self) -> CompletionConfig {
                 && 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 @@ pub fn assist(&self) -> AssistConfig {
             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,
index dc67d19a7547ecfd9a05adf33000ca5913f9c1d0..1ff2d3fea659a76ca95a289c0a7981d3a1a42b3d 100644 (file)
@@ -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 @@ fn main() {
                     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 },
             )