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 =