From 6742f38e49d001359a7a9911becc0fcae4c67910 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 16 Jan 2021 19:33:36 +0200 Subject: Share import_assets and related entities --- crates/assists/src/assist_config.rs | 8 +- crates/assists/src/handlers/auto_import.rs | 9 +- crates/assists/src/handlers/qualify_path.rs | 40 +-- crates/assists/src/lib.rs | 2 +- crates/assists/src/tests.rs | 9 +- crates/assists/src/utils.rs | 1 - crates/assists/src/utils/import_assets.rs | 265 ------------------- crates/completion/src/completions.rs | 1 + crates/completion/src/completions/flyimport.rs | 291 +++++++++++++++++++++ .../completion/src/completions/unqualified_path.rs | 251 +----------------- crates/completion/src/config.rs | 4 +- crates/completion/src/lib.rs | 5 +- crates/completion/src/test_utils.rs | 14 +- crates/ide/src/lib.rs | 2 +- crates/ide_db/src/helpers.rs | 1 + crates/ide_db/src/helpers/import_assets.rs | 267 +++++++++++++++++++ crates/ide_db/src/helpers/insert_use.rs | 6 + crates/ide_db/src/imports_locator.rs | 6 +- crates/rust-analyzer/src/cli/analysis_bench.rs | 5 +- crates/rust-analyzer/src/config.rs | 33 ++- crates/rust-analyzer/src/to_proto.rs | 5 +- 21 files changed, 652 insertions(+), 573 deletions(-) delete mode 100644 crates/assists/src/utils/import_assets.rs create mode 100644 crates/completion/src/completions/flyimport.rs create mode 100644 crates/ide_db/src/helpers/import_assets.rs diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs index 4fe8ea761..9cabf037c 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>, pub insert_use: InsertUseConfig, } - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct InsertUseConfig { - pub merge: Option, - pub prefix_kind: hir::PrefixKind, -} diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index 55620f0f3..4e2a4fcd9 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 f7fbf37f4..a7d9fd4dc 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 { 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 3d7971806..14178a651 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 71431b406..32bd8698b 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 9ea96eb73..fc9f83bab 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/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs deleted file mode 100644 index 4ce82c1ba..000000000 --- a/crates/assists/src/utils/import_assets.rs +++ /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, - ) -> Option { - 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, - ) -> Option { - 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, - 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, - ) -> 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, - prefixed: Option, - ) -> 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| { - 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::>(); - res.sort_by_key(|(path, _)| path.clone()); - res - } - - fn assoc_to_trait(assoc: AssocItemContainer) -> Option { - if let AssocItemContainer::Trait(extracted_trait) = assoc { - Some(extracted_trait) - } else { - None - } - } -} - -impl ImportCandidate { - fn for_method_call( - sema: &Semantics, - method_call: &ast::MethodCallExpr, - ) -> Option { - 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, - path_under_caret: &ast::Path, - ) -> Option { - 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) - } -} diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 00c9e76f0..c3ce6e51d 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 000000000..54f8e6d24 --- /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::>(); + + 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 53e1391f3..ac5596ca4 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::>(); - - 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 b4439b7d1..58fc700f3 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, - pub merge: Option, + pub insert_use: InsertUseConfig, } diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index 6cba88a6b..ee1b822e7 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/test_utils.rs b/crates/completion/src/test_utils.rs index 3f4b9d4ac..6ea6da989 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 afd552008..f8d69382e 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 c6763ae36..0dcc4dd29 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/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs new file mode 100644 index 000000000..edc3da318 --- /dev/null +++ b/crates/ide_db/src/helpers/import_assets.rs @@ -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, + 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, + ) -> Option { + 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, + ) -> Option { + 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, + 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, + ) -> 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, + prefixed: Option, + ) -> 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| { + 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::>(); + res.sort_by_key(|(path, _)| path.clone()); + res + } + + fn assoc_to_trait(assoc: AssocItemContainer) -> Option { + if let AssocItemContainer::Trait(extracted_trait) = assoc { + Some(extracted_trait) + } else { + None + } + } +} + +impl ImportCandidate { + fn for_method_call( + sema: &Semantics, + method_call: &ast::MethodCallExpr, + ) -> Option { + 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, + path_under_caret: &ast::Path, + ) -> Option { + 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) + } +} diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs index d2f9f5d25..877d4f1c7 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, + 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 e9f23adf8..d111fba92 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 7d3fda7a8..a02c8327f 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 27b92a5a9..ce9655818 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 { - 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 dc67d19a7..1ff2d3fea 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 }, ) -- cgit v1.2.3