diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index afa94affb3..3b2b2fd706 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -8,6 +8,7 @@ use itertools::Itertools; use syntax::{ast, AstNode, SyntaxNode, ToSmolStr, T}; use crate::{ + config::AutoImportExclusionType, context::{ CompletionContext, DotAccess, PathCompletionCtx, PathKind, PatternContext, Qualified, TypeLocation, @@ -258,8 +259,6 @@ fn import_on_the_fly( let import_cfg = ctx.config.import_path_config(); - let completed_name = ctx.token.to_string(); - import_assets .search_for_imports(&ctx.sema, import_cfg, ctx.config.insert_use.prefix_kind) .filter(ns_filter) @@ -270,19 +269,17 @@ fn import_on_the_fly( && ctx.check_stability(original_item.attrs(ctx.db).as_deref()) }) .filter(|import| { - if let ModuleDef::Trait(trait_) = import.item_to_import.into_module_def() { - let excluded = ctx.exclude_flyimport_traits.contains(&trait_); - let trait_itself_imported = import.item_to_import == import.original_item; - if !excluded || trait_itself_imported { - return true; + let def = import.item_to_import.into_module_def(); + if let Some(&kind) = ctx.exclude_flyimport.get(&def) { + if kind == AutoImportExclusionType::Always { + return false; + } + let method_imported = import.item_to_import != import.original_item; + if method_imported { + return false; } - - let item = import.original_item.into_module_def(); - // Filter that item out, unless its name matches the name the user wrote exactly - in which case preserve it. - item.name(ctx.db).is_some_and(|name| name.eq_ident(&completed_name)) - } else { - true } + true }) .sorted_by(|a, b| { let key = |import_path| { @@ -363,8 +360,6 @@ fn import_on_the_fly_method( let cfg = ctx.config.import_path_config(); - let completed_name = ctx.token.to_string(); - import_assets .search_for_imports(&ctx.sema, cfg, ctx.config.insert_use.prefix_kind) .filter(|import| { @@ -372,14 +367,19 @@ fn import_on_the_fly_method( && !ctx.is_item_hidden(&import.original_item) }) .filter(|import| { - if let ModuleDef::Trait(trait_) = import.item_to_import.into_module_def() { - if !ctx.exclude_flyimport_traits.contains(&trait_) { - return true; + let def = import.item_to_import.into_module_def(); + if let Some(&kind) = ctx.exclude_flyimport.get(&def) { + if kind == AutoImportExclusionType::Always { + return false; } + let method_imported = import.item_to_import != import.original_item; + if method_imported { + return false; + } + } - let item = import.original_item.into_module_def(); - // Filter that method out, unless its name matches the name the user wrote exactly - in which case preserve it. - item.name(ctx.db).is_some_and(|name| name.eq_ident(&completed_name)) + if let ModuleDef::Trait(_) = import.item_to_import.into_module_def() { + !ctx.exclude_flyimport.contains_key(&def) } else { true } diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs index ed36fe8d02..8b1ce11e8a 100644 --- a/crates/ide-completion/src/config.rs +++ b/crates/ide-completion/src/config.rs @@ -28,10 +28,16 @@ pub struct CompletionConfig<'a> { pub snippets: Vec, pub limit: Option, pub fields_to_resolve: CompletionFieldsToResolve, - pub exclude_flyimport_traits: &'a [String], + pub exclude_flyimport: Vec<(String, AutoImportExclusionType)>, pub exclude_traits: &'a [String], } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum AutoImportExclusionType { + Always, + Methods, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum CallableSnippets { FillArguments, diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index c436528425..183490c2ed 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -22,6 +22,7 @@ use syntax::{ }; use crate::{ + config::AutoImportExclusionType, context::analysis::{expand_and_analyze, AnalysisResult}, CompletionConfig, }; @@ -466,7 +467,7 @@ pub(crate) struct CompletionContext<'a> { /// importing those traits. /// /// Note the trait *themselves* are not excluded, only their methods are. - pub(crate) exclude_flyimport_traits: FxHashSet, + pub(crate) exclude_flyimport: FxHashMap, /// Traits whose methods should always be excluded, even when in scope (compare `exclude_flyimport_traits`). /// They will *not* be excluded, however, if they are available as a generic bound. /// @@ -780,22 +781,20 @@ impl<'a> CompletionContext<'a> { }) .collect(); - let mut exclude_flyimport_traits: FxHashSet<_> = config - .exclude_flyimport_traits + let mut exclude_flyimport: FxHashMap<_, _> = config + .exclude_flyimport .iter() - .filter_map(|path| { + .flat_map(|(path, kind)| { scope .resolve_mod_path(&ModPath::from_segments( hir::PathKind::Plain, path.split("::").map(Symbol::intern).map(Name::new_symbol_root), )) - .find_map(|it| match it { - hir::ItemInNs::Types(ModuleDef::Trait(t)) => Some(t), - _ => None, - }) + .map(|it| (it.into_module_def(), *kind)) }) .collect(); - exclude_flyimport_traits.extend(exclude_traits.iter().copied()); + exclude_flyimport + .extend(exclude_traits.iter().map(|&t| (t.into(), AutoImportExclusionType::Always))); let complete_semicolon = if config.add_semicolon_to_unit { let inside_closure_ret = token.parent_ancestors().try_for_each(|ancestor| { @@ -861,7 +860,7 @@ impl<'a> CompletionContext<'a> { qualifier_ctx, locals, depth_from_crate_root, - exclude_flyimport_traits, + exclude_flyimport, exclude_traits, complete_semicolon, }; diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 05563bc912..ca6c9ad9f0 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -31,7 +31,7 @@ use crate::{ }; pub use crate::{ - config::{CallableSnippets, CompletionConfig}, + config::{AutoImportExclusionType, CallableSnippets, CompletionConfig}, item::{ CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceReturnType, CompletionRelevanceTypeMatch, diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 3b2d3fbbfc..1815f34053 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -85,7 +85,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig<'_> = CompletionConfig { snippets: Vec::new(), limit: None, fields_to_resolve: CompletionFieldsToResolve::empty(), - exclude_flyimport_traits: &[], + exclude_flyimport: vec![], exclude_traits: &[], }; diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index d1bbf1e8d0..a9db1d9531 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -2,6 +2,7 @@ use expect_test::{expect, Expect}; use crate::{ + config::AutoImportExclusionType, tests::{ check_edit, check_empty, completion_list, completion_list_with_config, BASE_ITEMS_FIXTURE, TEST_CONFIG, @@ -1605,7 +1606,10 @@ fn foo() { fn flyimport_excluded_trait_method_is_excluded_from_flyimport() { check_with_config( CompletionConfig { - exclude_flyimport_traits: &["test::module2::ExcludedTrait".to_owned()], + exclude_flyimport: vec![( + "test::module2::ExcludedTrait".to_owned(), + AutoImportExclusionType::Methods, + )], ..TEST_CONFIG }, r#" diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index b6678c12c7..b7297d0c02 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -440,22 +440,27 @@ config_data! { /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. completion_autoimport_enable: bool = true, - /// A list of full paths to traits to exclude from flyimport. + /// A list of full paths to items to exclude from auto-importing completions. /// /// Traits in this list won't have their methods suggested in completions unless the trait /// is in scope. /// + /// You can either specify a string path which defaults to type "always" or use the more verbose + /// form `{ "path": "path::to::item", type: "always" }`. + /// + /// For traits the type "methods" can be used to only exclude the methods but not the trait itself. + /// /// This setting also inherits `#rust-analyzer.completion.excludeTraits#`. - completion_autoimport_excludeTraits: Vec = vec![ - "core::borrow::Borrow".to_owned(), - "core::borrow::BorrowMut".to_owned(), + completion_autoimport_exclude: Vec = vec![ + AutoImportExclusion::Verbose { path: "core::borrow::Borrow".to_owned(), r#type: AutoImportExclusionType::Methods }, + AutoImportExclusion::Verbose { path: "core::borrow::BorrowMut".to_owned(), r#type: AutoImportExclusionType::Methods }, ], /// Toggles the additional completions that automatically show method calls and field accesses /// with `self` prefixed to them when inside a method. completion_autoself_enable: bool = true, /// Whether to add parenthesis and argument snippets when completing function. completion_callable_snippets: CallableCompletionDef = CallableCompletionDef::FillArguments, - /// A list of full paths to traits to exclude from completion. + /// A list of full paths to traits whose methods to exclude from completion. /// /// Methods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`. /// @@ -1478,7 +1483,26 @@ impl Config { } else { CompletionFieldsToResolve::from_client_capabilities(&client_capability_fields) }, - exclude_flyimport_traits: self.completion_autoimport_excludeTraits(source_root), + exclude_flyimport: self + .completion_autoimport_exclude(source_root) + .iter() + .map(|it| match it { + AutoImportExclusion::Path(path) => { + (path.clone(), ide_completion::AutoImportExclusionType::Always) + } + AutoImportExclusion::Verbose { path, r#type } => ( + path.clone(), + match r#type { + AutoImportExclusionType::Always => { + ide_completion::AutoImportExclusionType::Always + } + AutoImportExclusionType::Methods => { + ide_completion::AutoImportExclusionType::Methods + } + }, + ), + }) + .collect(), exclude_traits: self.completion_excludeTraits(source_root), } } @@ -2419,6 +2443,21 @@ enum ExprFillDefaultDef { Default, } +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +#[serde(rename_all = "snake_case")] +pub enum AutoImportExclusion { + Path(String), + Verbose { path: String, r#type: AutoImportExclusionType }, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum AutoImportExclusionType { + Always, + Methods, +} + #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ImportGranularityDef { @@ -3490,6 +3529,32 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json } ] }, + "Vec" => set! { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "object", + "properties": { + "path": { + "type": "string", + }, + "type": { + "type": "string", + "enum": ["always", "methods"], + "enumDescriptions": [ + "Do not show this item or its methods (if it is a trait) in auto-import completions.", + "Do not show this traits methods in auto-import completions." + ], + }, + } + } + ] + } + }, _ => panic!("missing entry for {ty}: {default} (field {field})"), } diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index bd164fe440..fcfd06679b 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -174,7 +174,7 @@ fn integrated_completion_benchmark() { limit: None, add_semicolon_to_unit: true, fields_to_resolve: CompletionFieldsToResolve::empty(), - exclude_flyimport_traits: &[], + exclude_flyimport: vec![], exclude_traits: &[], }; let position = @@ -224,7 +224,7 @@ fn integrated_completion_benchmark() { limit: None, add_semicolon_to_unit: true, fields_to_resolve: CompletionFieldsToResolve::empty(), - exclude_flyimport_traits: &[], + exclude_flyimport: vec![], exclude_traits: &[], }; let position = @@ -272,7 +272,7 @@ fn integrated_completion_benchmark() { limit: None, add_semicolon_to_unit: true, fields_to_resolve: CompletionFieldsToResolve::empty(), - exclude_flyimport_traits: &[], + exclude_flyimport: vec![], exclude_traits: &[], }; let position = diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index bce26f7dd7..a44f5a12b2 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -286,21 +286,32 @@ In `match` arms it completes a comma instead. Toggles the additional completions that automatically add imports when completed. Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. -- -[[rust-analyzer.completion.autoimport.excludeTraits]]rust-analyzer.completion.autoimport.excludeTraits:: +[[rust-analyzer.completion.autoimport.exclude]]rust-analyzer.completion.autoimport.exclude:: + -- Default: ---- [ - "core::borrow::Borrow", - "core::borrow::BorrowMut" + { + "path": "core::borrow::Borrow", + "type": "methods" + }, + { + "path": "core::borrow::BorrowMut", + "type": "methods" + } ] ---- -A list of full paths to traits to exclude from flyimport. +A list of full paths to items to exclude from auto-importing completions. Traits in this list won't have their methods suggested in completions unless the trait is in scope. +You can either specify a string path which defaults to type "always" or use the more verbose +form `{ "path": "path::to::item", type: "always" }`. + +For traits the type "methods" can be used to only exclude the methods but not the trait itself. + This setting also inherits `#rust-analyzer.completion.excludeTraits#`. -- @@ -318,7 +329,7 @@ Whether to add parenthesis and argument snippets when completing function. [[rust-analyzer.completion.excludeTraits]]rust-analyzer.completion.excludeTraits (default: `[]`):: + -- -A list of full paths to traits to exclude from completion. +A list of full paths to traits whose methods to exclude from completion. Methods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`. diff --git a/editors/code/package.json b/editors/code/package.json index 31419a1942..03c00a37fb 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1142,15 +1142,44 @@ { "title": "completion", "properties": { - "rust-analyzer.completion.autoimport.excludeTraits": { - "markdownDescription": "A list of full paths to traits to exclude from flyimport.\n\nTraits in this list won't have their methods suggested in completions unless the trait\nis in scope.\n\nThis setting also inherits `#rust-analyzer.completion.excludeTraits#`.", + "rust-analyzer.completion.autoimport.exclude": { + "markdownDescription": "A list of full paths to items to exclude from auto-importing completions.\n\nTraits in this list won't have their methods suggested in completions unless the trait\nis in scope.\n\nYou can either specify a string path which defaults to type \"always\" or use the more verbose\nform `{ \"path\": \"path::to::item\", type: \"always\" }`.\n\nFor traits the type \"methods\" can be used to only exclude the methods but not the trait itself.\n\nThis setting also inherits `#rust-analyzer.completion.excludeTraits#`.", "default": [ - "core::borrow::Borrow", - "core::borrow::BorrowMut" + { + "path": "core::borrow::Borrow", + "type": "methods" + }, + { + "path": "core::borrow::BorrowMut", + "type": "methods" + } ], "type": "array", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "always", + "methods" + ], + "enumDescriptions": [ + "Do not show this item or its methods (if it is a trait) in auto-import completions.", + "Do not show this traits methods in auto-import completions." + ] + } + } + } + ] } } } @@ -1189,7 +1218,7 @@ "title": "completion", "properties": { "rust-analyzer.completion.excludeTraits": { - "markdownDescription": "A list of full paths to traits to exclude from completion.\n\nMethods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`.\n\nNote that the trait themselves can still be completed.", + "markdownDescription": "A list of full paths to traits whose methods to exclude from completion.\n\nMethods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`.\n\nNote that the trait themselves can still be completed.", "default": [], "type": "array", "items": {