Revamp auto-import exclude config

This commit is contained in:
Lukas Wirth 2025-01-01 15:05:24 +01:00
parent c5bda0d3f7
commit 5303dc5d99
10 changed files with 169 additions and 55 deletions

View file

@ -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
}

View file

@ -28,10 +28,16 @@ pub struct CompletionConfig<'a> {
pub snippets: Vec<Snippet>,
pub limit: Option<usize>,
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,

View file

@ -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<hir::Trait>,
pub(crate) exclude_flyimport: FxHashMap<ModuleDef, AutoImportExclusionType>,
/// 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,
};

View file

@ -31,7 +31,7 @@ use crate::{
};
pub use crate::{
config::{CallableSnippets, CompletionConfig},
config::{AutoImportExclusionType, CallableSnippets, CompletionConfig},
item::{
CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
CompletionRelevanceReturnType, CompletionRelevanceTypeMatch,

View file

@ -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: &[],
};

View file

@ -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#"

View file

@ -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<String> = vec![
"core::borrow::Borrow".to_owned(),
"core::borrow::BorrowMut".to_owned(),
completion_autoimport_exclude: Vec<AutoImportExclusion> = 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<AutoImportExclusion>" => 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})"),
}

View file

@ -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 =

View file

@ -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`.

View file

@ -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": {