mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-25 19:35:06 +00:00
Merge pull request #18179 from ChayimFriedman2/omit-trait-completion
feat: Allow excluding specific traits from completion
This commit is contained in:
commit
7e639ee3dd
20 changed files with 1028 additions and 100 deletions
|
@ -913,7 +913,7 @@ pub fn iterate_path_candidates(
|
|||
traits_in_scope: &FxHashSet<TraitId>,
|
||||
visible_from_module: VisibleFromModule,
|
||||
name: Option<&Name>,
|
||||
callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>,
|
||||
callback: &mut dyn MethodCandidateCallback,
|
||||
) -> ControlFlow<()> {
|
||||
iterate_method_candidates_dyn(
|
||||
ty,
|
||||
|
@ -924,7 +924,7 @@ pub fn iterate_path_candidates(
|
|||
name,
|
||||
LookupMode::Path,
|
||||
// the adjustments are not relevant for path lookup
|
||||
&mut |_, id, _| callback(id),
|
||||
callback,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -936,7 +936,7 @@ pub fn iterate_method_candidates_dyn(
|
|||
visible_from_module: VisibleFromModule,
|
||||
name: Option<&Name>,
|
||||
mode: LookupMode,
|
||||
callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
|
||||
callback: &mut dyn MethodCandidateCallback,
|
||||
) -> ControlFlow<()> {
|
||||
let _p = tracing::info_span!(
|
||||
"iterate_method_candidates_dyn",
|
||||
|
@ -1006,7 +1006,7 @@ fn iterate_method_candidates_with_autoref(
|
|||
traits_in_scope: &FxHashSet<TraitId>,
|
||||
visible_from_module: VisibleFromModule,
|
||||
name: Option<&Name>,
|
||||
mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
|
||||
callback: &mut dyn MethodCandidateCallback,
|
||||
) -> ControlFlow<()> {
|
||||
if receiver_ty.value.is_general_var(Interner, &receiver_ty.binders) {
|
||||
// don't try to resolve methods on unknown types
|
||||
|
@ -1021,7 +1021,7 @@ fn iterate_method_candidates_with_autoref(
|
|||
traits_in_scope,
|
||||
visible_from_module,
|
||||
name,
|
||||
&mut callback,
|
||||
callback,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -1051,6 +1051,45 @@ fn iterate_method_candidates_with_autoref(
|
|||
iterate_method_candidates_by_receiver(ref_muted, first_adjustment.with_autoref(Mutability::Mut))
|
||||
}
|
||||
|
||||
pub trait MethodCandidateCallback {
|
||||
fn on_inherent_method(
|
||||
&mut self,
|
||||
adjustments: ReceiverAdjustments,
|
||||
item: AssocItemId,
|
||||
is_visible: bool,
|
||||
) -> ControlFlow<()>;
|
||||
|
||||
fn on_trait_method(
|
||||
&mut self,
|
||||
adjustments: ReceiverAdjustments,
|
||||
item: AssocItemId,
|
||||
is_visible: bool,
|
||||
) -> ControlFlow<()>;
|
||||
}
|
||||
|
||||
impl<F> MethodCandidateCallback for F
|
||||
where
|
||||
F: FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
|
||||
{
|
||||
fn on_inherent_method(
|
||||
&mut self,
|
||||
adjustments: ReceiverAdjustments,
|
||||
item: AssocItemId,
|
||||
is_visible: bool,
|
||||
) -> ControlFlow<()> {
|
||||
self(adjustments, item, is_visible)
|
||||
}
|
||||
|
||||
fn on_trait_method(
|
||||
&mut self,
|
||||
adjustments: ReceiverAdjustments,
|
||||
item: AssocItemId,
|
||||
is_visible: bool,
|
||||
) -> ControlFlow<()> {
|
||||
self(adjustments, item, is_visible)
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(name = ?name))]
|
||||
fn iterate_method_candidates_by_receiver(
|
||||
table: &mut InferenceTable<'_>,
|
||||
|
@ -1059,7 +1098,7 @@ fn iterate_method_candidates_by_receiver(
|
|||
traits_in_scope: &FxHashSet<TraitId>,
|
||||
visible_from_module: VisibleFromModule,
|
||||
name: Option<&Name>,
|
||||
mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
|
||||
callback: &mut dyn MethodCandidateCallback,
|
||||
) -> ControlFlow<()> {
|
||||
let receiver_ty = table.instantiate_canonical(receiver_ty);
|
||||
// We're looking for methods with *receiver* type receiver_ty. These could
|
||||
|
@ -1075,7 +1114,9 @@ fn iterate_method_candidates_by_receiver(
|
|||
Some(&receiver_ty),
|
||||
Some(receiver_adjustments.clone()),
|
||||
visible_from_module,
|
||||
&mut callback,
|
||||
&mut |adjustments, item, is_visible| {
|
||||
callback.on_inherent_method(adjustments, item, is_visible)
|
||||
},
|
||||
)?
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
|
@ -1095,7 +1136,9 @@ fn iterate_method_candidates_by_receiver(
|
|||
name,
|
||||
Some(&receiver_ty),
|
||||
Some(receiver_adjustments.clone()),
|
||||
&mut callback,
|
||||
&mut |adjustments, item, is_visible| {
|
||||
callback.on_trait_method(adjustments, item, is_visible)
|
||||
},
|
||||
)?
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
|
@ -1110,7 +1153,7 @@ fn iterate_method_candidates_for_self_ty(
|
|||
traits_in_scope: &FxHashSet<TraitId>,
|
||||
visible_from_module: VisibleFromModule,
|
||||
name: Option<&Name>,
|
||||
mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
|
||||
callback: &mut dyn MethodCandidateCallback,
|
||||
) -> ControlFlow<()> {
|
||||
let mut table = InferenceTable::new(db, env);
|
||||
let self_ty = table.instantiate_canonical(self_ty.clone());
|
||||
|
@ -1121,7 +1164,9 @@ fn iterate_method_candidates_for_self_ty(
|
|||
None,
|
||||
None,
|
||||
visible_from_module,
|
||||
&mut callback,
|
||||
&mut |adjustments, item, is_visible| {
|
||||
callback.on_inherent_method(adjustments, item, is_visible)
|
||||
},
|
||||
)?;
|
||||
iterate_trait_method_candidates(
|
||||
&self_ty,
|
||||
|
@ -1130,7 +1175,9 @@ fn iterate_method_candidates_for_self_ty(
|
|||
name,
|
||||
None,
|
||||
None,
|
||||
callback,
|
||||
&mut |adjustments, item, is_visible| {
|
||||
callback.on_trait_method(adjustments, item, is_visible)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -258,7 +258,7 @@ fn resolve_impl_trait_item(
|
|||
&traits_in_scope,
|
||||
method_resolution::VisibleFromModule::None,
|
||||
Some(name),
|
||||
&mut |assoc_item_id| {
|
||||
&mut |_, assoc_item_id: AssocItemId, _| {
|
||||
// If two traits in scope define the same item, Rustdoc links to no specific trait (for
|
||||
// instance, given two methods `a`, Rustdoc simply links to `method.a` with no
|
||||
// disambiguation) so we just pick the first one we find as well.
|
||||
|
|
|
@ -5223,21 +5223,18 @@ impl Type {
|
|||
) -> Option<T> {
|
||||
let _p = tracing::info_span!("iterate_method_candidates_with_traits").entered();
|
||||
let mut slot = None;
|
||||
|
||||
self.iterate_method_candidates_dyn(
|
||||
self.iterate_method_candidates_split_inherent(
|
||||
db,
|
||||
scope,
|
||||
traits_in_scope,
|
||||
with_local_impls,
|
||||
name,
|
||||
&mut |assoc_item_id| {
|
||||
if let AssocItemId::FunctionId(func) = assoc_item_id {
|
||||
if let Some(res) = callback(func.into()) {
|
||||
slot = Some(res);
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
|f| match callback(f) {
|
||||
it @ Some(_) => {
|
||||
slot = it;
|
||||
ControlFlow::Break(())
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
None => ControlFlow::Continue(()),
|
||||
},
|
||||
);
|
||||
slot
|
||||
|
@ -5261,15 +5258,49 @@ impl Type {
|
|||
)
|
||||
}
|
||||
|
||||
fn iterate_method_candidates_dyn(
|
||||
/// Allows you to treat inherent and non-inherent methods differently.
|
||||
///
|
||||
/// Note that inherent methods may actually be trait methods! For example, in `dyn Trait`, the trait's methods
|
||||
/// are considered inherent methods.
|
||||
pub fn iterate_method_candidates_split_inherent(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
scope: &SemanticsScope<'_>,
|
||||
traits_in_scope: &FxHashSet<TraitId>,
|
||||
with_local_impls: Option<Module>,
|
||||
name: Option<&Name>,
|
||||
callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>,
|
||||
callback: impl MethodCandidateCallback,
|
||||
) {
|
||||
struct Callback<T>(T);
|
||||
|
||||
impl<T: MethodCandidateCallback> method_resolution::MethodCandidateCallback for Callback<T> {
|
||||
fn on_inherent_method(
|
||||
&mut self,
|
||||
_adjustments: method_resolution::ReceiverAdjustments,
|
||||
item: AssocItemId,
|
||||
_is_visible: bool,
|
||||
) -> ControlFlow<()> {
|
||||
if let AssocItemId::FunctionId(func) = item {
|
||||
self.0.on_inherent_method(func.into())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
fn on_trait_method(
|
||||
&mut self,
|
||||
_adjustments: method_resolution::ReceiverAdjustments,
|
||||
item: AssocItemId,
|
||||
_is_visible: bool,
|
||||
) -> ControlFlow<()> {
|
||||
if let AssocItemId::FunctionId(func) = item {
|
||||
self.0.on_trait_method(func.into())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _p = tracing::info_span!(
|
||||
"iterate_method_candidates_dyn",
|
||||
with_local_impls = traits_in_scope.len(),
|
||||
|
@ -5294,7 +5325,7 @@ impl Type {
|
|||
with_local_impls.and_then(|b| b.id.containing_block()).into(),
|
||||
name,
|
||||
method_resolution::LookupMode::MethodCall,
|
||||
&mut |_adj, id, _| callback(id),
|
||||
&mut Callback(callback),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -5310,33 +5341,61 @@ impl Type {
|
|||
) -> Option<T> {
|
||||
let _p = tracing::info_span!("iterate_path_candidates").entered();
|
||||
let mut slot = None;
|
||||
self.iterate_path_candidates_dyn(
|
||||
|
||||
self.iterate_path_candidates_split_inherent(
|
||||
db,
|
||||
scope,
|
||||
traits_in_scope,
|
||||
with_local_impls,
|
||||
name,
|
||||
&mut |assoc_item_id| {
|
||||
if let Some(res) = callback(assoc_item_id.into()) {
|
||||
slot = Some(res);
|
||||
return ControlFlow::Break(());
|
||||
|item| match callback(item) {
|
||||
it @ Some(_) => {
|
||||
slot = it;
|
||||
ControlFlow::Break(())
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
None => ControlFlow::Continue(()),
|
||||
},
|
||||
);
|
||||
slot
|
||||
}
|
||||
|
||||
/// Iterates over inherent methods.
|
||||
///
|
||||
/// In some circumstances, inherent methods methods may actually be trait methods!
|
||||
/// For example, when `dyn Trait` is a receiver, _trait_'s methods would be considered
|
||||
/// to be inherent methods.
|
||||
#[tracing::instrument(skip_all, fields(name = ?name))]
|
||||
fn iterate_path_candidates_dyn(
|
||||
pub fn iterate_path_candidates_split_inherent(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
scope: &SemanticsScope<'_>,
|
||||
traits_in_scope: &FxHashSet<TraitId>,
|
||||
with_local_impls: Option<Module>,
|
||||
name: Option<&Name>,
|
||||
callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>,
|
||||
callback: impl PathCandidateCallback,
|
||||
) {
|
||||
struct Callback<T>(T);
|
||||
|
||||
impl<T: PathCandidateCallback> method_resolution::MethodCandidateCallback for Callback<T> {
|
||||
fn on_inherent_method(
|
||||
&mut self,
|
||||
_adjustments: method_resolution::ReceiverAdjustments,
|
||||
item: AssocItemId,
|
||||
_is_visible: bool,
|
||||
) -> ControlFlow<()> {
|
||||
self.0.on_inherent_item(item.into())
|
||||
}
|
||||
|
||||
fn on_trait_method(
|
||||
&mut self,
|
||||
_adjustments: method_resolution::ReceiverAdjustments,
|
||||
item: AssocItemId,
|
||||
_is_visible: bool,
|
||||
) -> ControlFlow<()> {
|
||||
self.0.on_trait_item(item.into())
|
||||
}
|
||||
}
|
||||
|
||||
let canonical = hir_ty::replace_errors_with_variables(&self.ty);
|
||||
|
||||
let krate = scope.krate();
|
||||
|
@ -5352,7 +5411,7 @@ impl Type {
|
|||
traits_in_scope,
|
||||
with_local_impls.and_then(|b| b.id.containing_block()).into(),
|
||||
name,
|
||||
callback,
|
||||
&mut Callback(callback),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -6060,3 +6119,41 @@ fn push_ty_diagnostics(
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MethodCandidateCallback {
|
||||
fn on_inherent_method(&mut self, f: Function) -> ControlFlow<()>;
|
||||
|
||||
fn on_trait_method(&mut self, f: Function) -> ControlFlow<()>;
|
||||
}
|
||||
|
||||
impl<F> MethodCandidateCallback for F
|
||||
where
|
||||
F: FnMut(Function) -> ControlFlow<()>,
|
||||
{
|
||||
fn on_inherent_method(&mut self, f: Function) -> ControlFlow<()> {
|
||||
self(f)
|
||||
}
|
||||
|
||||
fn on_trait_method(&mut self, f: Function) -> ControlFlow<()> {
|
||||
self(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PathCandidateCallback {
|
||||
fn on_inherent_item(&mut self, item: AssocItem) -> ControlFlow<()>;
|
||||
|
||||
fn on_trait_item(&mut self, item: AssocItem) -> ControlFlow<()>;
|
||||
}
|
||||
|
||||
impl<F> PathCandidateCallback for F
|
||||
where
|
||||
F: FnMut(AssocItem) -> ControlFlow<()>,
|
||||
{
|
||||
fn on_inherent_item(&mut self, item: AssocItem) -> ControlFlow<()> {
|
||||
self(item)
|
||||
}
|
||||
|
||||
fn on_trait_item(&mut self, item: AssocItem) -> ControlFlow<()> {
|
||||
self(item)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2060,6 +2060,11 @@ impl SemanticsScope<'_> {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn resolve_mod_path(&self, path: &ModPath) -> impl Iterator<Item = ItemInNs> {
|
||||
let items = self.resolver.resolve_module_path_in_items(self.db.upcast(), path);
|
||||
items.iter_items().map(|(item, _)| item.into())
|
||||
}
|
||||
|
||||
/// Iterates over associated types that may be specified after the given path (using
|
||||
/// `Ty::Assoc` syntax).
|
||||
pub fn assoc_type_shorthand_candidates<R>(
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! Completes references after dot (fields and method calls).
|
||||
|
||||
use hir::{sym, Name};
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use hir::{sym, HasContainer, ItemContainer, MethodCandidateCallback, Name};
|
||||
use ide_db::FxHashSet;
|
||||
use syntax::SmolStr;
|
||||
|
||||
|
@ -158,21 +160,55 @@ fn complete_fields(
|
|||
fn complete_methods(
|
||||
ctx: &CompletionContext<'_>,
|
||||
receiver: &hir::Type,
|
||||
mut f: impl FnMut(hir::Function),
|
||||
f: impl FnMut(hir::Function),
|
||||
) {
|
||||
let mut seen_methods = FxHashSet::default();
|
||||
receiver.iterate_method_candidates_with_traits(
|
||||
struct Callback<'a, F> {
|
||||
ctx: &'a CompletionContext<'a>,
|
||||
f: F,
|
||||
seen_methods: FxHashSet<Name>,
|
||||
}
|
||||
|
||||
impl<F> MethodCandidateCallback for Callback<'_, F>
|
||||
where
|
||||
F: FnMut(hir::Function),
|
||||
{
|
||||
// We don't want to exclude inherent trait methods - that is, methods of traits available from
|
||||
// `where` clauses or `dyn Trait`.
|
||||
fn on_inherent_method(&mut self, func: hir::Function) -> ControlFlow<()> {
|
||||
if func.self_param(self.ctx.db).is_some()
|
||||
&& self.seen_methods.insert(func.name(self.ctx.db))
|
||||
{
|
||||
(self.f)(func);
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn on_trait_method(&mut self, func: hir::Function) -> ControlFlow<()> {
|
||||
// This needs to come before the `seen_methods` test, so that if we see the same method twice,
|
||||
// once as inherent and once not, we will include it.
|
||||
if let ItemContainer::Trait(trait_) = func.container(self.ctx.db) {
|
||||
if self.ctx.exclude_traits.contains(&trait_) {
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
}
|
||||
|
||||
if func.self_param(self.ctx.db).is_some()
|
||||
&& self.seen_methods.insert(func.name(self.ctx.db))
|
||||
{
|
||||
(self.f)(func);
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
receiver.iterate_method_candidates_split_inherent(
|
||||
ctx.db,
|
||||
&ctx.scope,
|
||||
&ctx.traits_in_scope(),
|
||||
Some(ctx.module),
|
||||
None,
|
||||
|func| {
|
||||
if func.self_param(ctx.db).is_some() && seen_methods.insert(func.name(ctx.db)) {
|
||||
f(func);
|
||||
}
|
||||
None::<()>
|
||||
},
|
||||
Callback { ctx, f, seen_methods: FxHashSet::default() },
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
//! Completion of names from the current scope in expression position.
|
||||
|
||||
use hir::{sym, Name, ScopeDef};
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use hir::{sym, Name, PathCandidateCallback, ScopeDef};
|
||||
use ide_db::FxHashSet;
|
||||
use syntax::ast;
|
||||
|
||||
use crate::{
|
||||
|
@ -9,6 +12,38 @@ use crate::{
|
|||
CompletionContext, Completions,
|
||||
};
|
||||
|
||||
struct PathCallback<'a, F> {
|
||||
ctx: &'a CompletionContext<'a>,
|
||||
acc: &'a mut Completions,
|
||||
add_assoc_item: F,
|
||||
seen: FxHashSet<hir::AssocItem>,
|
||||
}
|
||||
|
||||
impl<F> PathCandidateCallback for PathCallback<'_, F>
|
||||
where
|
||||
F: FnMut(&mut Completions, hir::AssocItem),
|
||||
{
|
||||
fn on_inherent_item(&mut self, item: hir::AssocItem) -> ControlFlow<()> {
|
||||
if self.seen.insert(item) {
|
||||
(self.add_assoc_item)(self.acc, item);
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn on_trait_item(&mut self, item: hir::AssocItem) -> ControlFlow<()> {
|
||||
// The excluded check needs to come before the `seen` test, so that if we see the same method twice,
|
||||
// once as inherent and once not, we will include it.
|
||||
if item
|
||||
.container_trait(self.ctx.db)
|
||||
.is_none_or(|trait_| !self.ctx.exclude_traits.contains(&trait_))
|
||||
&& self.seen.insert(item)
|
||||
{
|
||||
(self.add_assoc_item)(self.acc, item);
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn complete_expr_path(
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext<'_>,
|
||||
|
@ -50,12 +85,18 @@ pub(crate) fn complete_expr_path(
|
|||
};
|
||||
|
||||
match qualified {
|
||||
// We exclude associated types/consts of excluded traits here together with methods,
|
||||
// even though we don't exclude them when completing in type position, because it's easier.
|
||||
Qualified::TypeAnchor { ty: None, trait_: None } => ctx
|
||||
.traits_in_scope()
|
||||
.iter()
|
||||
.flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
|
||||
.copied()
|
||||
.map(hir::Trait::from)
|
||||
.filter(|it| !ctx.exclude_traits.contains(it))
|
||||
.flat_map(|it| it.items(ctx.sema.db))
|
||||
.for_each(|item| add_assoc_item(acc, item)),
|
||||
Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
|
||||
// Don't filter excluded traits here, user requested this specific trait.
|
||||
trait_.items(ctx.sema.db).into_iter().for_each(|item| add_assoc_item(acc, item))
|
||||
}
|
||||
Qualified::TypeAnchor { ty: Some(ty), trait_: None } => {
|
||||
|
@ -64,9 +105,14 @@ pub(crate) fn complete_expr_path(
|
|||
acc.add_enum_variants(ctx, path_ctx, e);
|
||||
}
|
||||
|
||||
ctx.iterate_path_candidates(ty, |item| {
|
||||
add_assoc_item(acc, item);
|
||||
});
|
||||
ty.iterate_path_candidates_split_inherent(
|
||||
ctx.db,
|
||||
&ctx.scope,
|
||||
&ctx.traits_in_scope(),
|
||||
Some(ctx.module),
|
||||
None,
|
||||
PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
|
||||
);
|
||||
|
||||
// Iterate assoc types separately
|
||||
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
|
||||
|
@ -121,9 +167,14 @@ pub(crate) fn complete_expr_path(
|
|||
// XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
|
||||
// (where AssocType is defined on a trait, not an inherent impl)
|
||||
|
||||
ctx.iterate_path_candidates(&ty, |item| {
|
||||
add_assoc_item(acc, item);
|
||||
});
|
||||
ty.iterate_path_candidates_split_inherent(
|
||||
ctx.db,
|
||||
&ctx.scope,
|
||||
&ctx.traits_in_scope(),
|
||||
Some(ctx.module),
|
||||
None,
|
||||
PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
|
||||
);
|
||||
|
||||
// Iterate assoc types separately
|
||||
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
|
||||
|
@ -134,6 +185,7 @@ pub(crate) fn complete_expr_path(
|
|||
});
|
||||
}
|
||||
hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
|
||||
// Don't filter excluded traits here, user requested this specific trait.
|
||||
// Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
|
||||
for item in t.items(ctx.db) {
|
||||
add_assoc_item(acc, item);
|
||||
|
@ -151,9 +203,14 @@ pub(crate) fn complete_expr_path(
|
|||
acc.add_enum_variants(ctx, path_ctx, e);
|
||||
}
|
||||
|
||||
ctx.iterate_path_candidates(&ty, |item| {
|
||||
add_assoc_item(acc, item);
|
||||
});
|
||||
ty.iterate_path_candidates_split_inherent(
|
||||
ctx.db,
|
||||
&ctx.scope,
|
||||
&ctx.traits_in_scope(),
|
||||
Some(ctx.module),
|
||||
None,
|
||||
PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -267,6 +268,19 @@ fn import_on_the_fly(
|
|||
&& !ctx.is_item_hidden(original_item)
|
||||
&& ctx.check_stability(original_item.attrs(ctx.db).as_deref())
|
||||
})
|
||||
.filter(|import| {
|
||||
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;
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
.sorted_by(|a, b| {
|
||||
let key = |import_path| {
|
||||
(
|
||||
|
@ -352,6 +366,24 @@ fn import_on_the_fly_method(
|
|||
!ctx.is_item_hidden(&import.item_to_import)
|
||||
&& !ctx.is_item_hidden(&import.original_item)
|
||||
})
|
||||
.filter(|import| {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if let ModuleDef::Trait(_) = import.item_to_import.into_module_def() {
|
||||
!ctx.exclude_flyimport.contains_key(&def)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.sorted_by(|a, b| {
|
||||
let key = |import_path| {
|
||||
(
|
||||
|
|
|
@ -10,7 +10,7 @@ use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap};
|
|||
use crate::{snippet::Snippet, CompletionFieldsToResolve};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CompletionConfig {
|
||||
pub struct CompletionConfig<'a> {
|
||||
pub enable_postfix_completions: bool,
|
||||
pub enable_imports_on_the_fly: bool,
|
||||
pub enable_self_on_the_fly: bool,
|
||||
|
@ -28,6 +28,14 @@ pub struct CompletionConfig {
|
|||
pub snippets: Vec<Snippet>,
|
||||
pub limit: Option<usize>,
|
||||
pub fields_to_resolve: CompletionFieldsToResolve,
|
||||
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)]
|
||||
|
@ -36,7 +44,7 @@ pub enum CallableSnippets {
|
|||
AddParentheses,
|
||||
}
|
||||
|
||||
impl CompletionConfig {
|
||||
impl CompletionConfig<'_> {
|
||||
pub fn postfix_snippets(&self) -> impl Iterator<Item = (&str, &Snippet)> {
|
||||
self.snippets
|
||||
.iter()
|
||||
|
|
|
@ -7,8 +7,8 @@ mod tests;
|
|||
use std::{iter, ops::ControlFlow};
|
||||
|
||||
use hir::{
|
||||
HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope,
|
||||
Symbol, Type, TypeInfo,
|
||||
HasAttrs, Local, ModPath, ModuleDef, ModuleSource, Name, PathResolution, ScopeDef, Semantics,
|
||||
SemanticsScope, Symbol, Type, TypeInfo,
|
||||
};
|
||||
use ide_db::{
|
||||
base_db::SourceDatabase, famous_defs::FamousDefs, helpers::is_editable_crate, FilePosition,
|
||||
|
@ -22,6 +22,7 @@ use syntax::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
config::AutoImportExclusionType,
|
||||
context::analysis::{expand_and_analyze, AnalysisResult},
|
||||
CompletionConfig,
|
||||
};
|
||||
|
@ -429,7 +430,7 @@ pub(crate) struct CompletionContext<'a> {
|
|||
pub(crate) sema: Semantics<'a, RootDatabase>,
|
||||
pub(crate) scope: SemanticsScope<'a>,
|
||||
pub(crate) db: &'a RootDatabase,
|
||||
pub(crate) config: &'a CompletionConfig,
|
||||
pub(crate) config: &'a CompletionConfig<'a>,
|
||||
pub(crate) position: FilePosition,
|
||||
|
||||
/// The token before the cursor, in the original file.
|
||||
|
@ -462,6 +463,17 @@ pub(crate) struct CompletionContext<'a> {
|
|||
/// Here depth will be 2
|
||||
pub(crate) depth_from_crate_root: usize,
|
||||
|
||||
/// Traits whose methods will be excluded from flyimport. Flyimport should not suggest
|
||||
/// importing those traits.
|
||||
///
|
||||
/// Note the trait *themselves* are not excluded, only their methods are.
|
||||
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.
|
||||
///
|
||||
/// Note the trait *themselves* are not excluded, only their methods are.
|
||||
pub(crate) exclude_traits: FxHashSet<hir::Trait>,
|
||||
|
||||
/// Whether and how to complete semicolon for unit-returning functions.
|
||||
pub(crate) complete_semicolon: CompleteSemicolon,
|
||||
}
|
||||
|
@ -670,7 +682,7 @@ impl<'a> CompletionContext<'a> {
|
|||
pub(crate) fn new(
|
||||
db: &'a RootDatabase,
|
||||
position @ FilePosition { file_id, offset }: FilePosition,
|
||||
config: &'a CompletionConfig,
|
||||
config: &'a CompletionConfig<'a>,
|
||||
) -> Option<(CompletionContext<'a>, CompletionAnalysis)> {
|
||||
let _p = tracing::info_span!("CompletionContext::new").entered();
|
||||
let sema = Semantics::new(db);
|
||||
|
@ -747,12 +759,43 @@ impl<'a> CompletionContext<'a> {
|
|||
});
|
||||
|
||||
let depth_from_crate_root = iter::successors(Some(module), |m| m.parent(db))
|
||||
// `BlockExpr` modules are not count as module depth
|
||||
// `BlockExpr` modules do not count towards module depth
|
||||
.filter(|m| !matches!(m.definition_source(db).value, ModuleSource::BlockExpr(_)))
|
||||
.count()
|
||||
// exclude `m` itself
|
||||
.saturating_sub(1);
|
||||
|
||||
let exclude_traits: FxHashSet<_> = config
|
||||
.exclude_traits
|
||||
.iter()
|
||||
.filter_map(|path| {
|
||||
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,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut exclude_flyimport: FxHashMap<_, _> = config
|
||||
.exclude_flyimport
|
||||
.iter()
|
||||
.flat_map(|(path, kind)| {
|
||||
scope
|
||||
.resolve_mod_path(&ModPath::from_segments(
|
||||
hir::PathKind::Plain,
|
||||
path.split("::").map(Symbol::intern).map(Name::new_symbol_root),
|
||||
))
|
||||
.map(|it| (it.into_module_def(), *kind))
|
||||
})
|
||||
.collect();
|
||||
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| {
|
||||
match_ast! {
|
||||
|
@ -817,6 +860,8 @@ impl<'a> CompletionContext<'a> {
|
|||
qualifier_ctx,
|
||||
locals,
|
||||
depth_from_crate_root,
|
||||
exclude_flyimport,
|
||||
exclude_traits,
|
||||
complete_semicolon,
|
||||
};
|
||||
Some((ctx, analysis))
|
||||
|
|
|
@ -31,7 +31,7 @@ use crate::{
|
|||
};
|
||||
|
||||
pub use crate::{
|
||||
config::{CallableSnippets, CompletionConfig},
|
||||
config::{AutoImportExclusionType, CallableSnippets, CompletionConfig},
|
||||
item::{
|
||||
CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
|
||||
CompletionRelevanceReturnType, CompletionRelevanceTypeMatch,
|
||||
|
@ -184,7 +184,7 @@ impl CompletionFieldsToResolve {
|
|||
/// analysis.
|
||||
pub fn completions(
|
||||
db: &RootDatabase,
|
||||
config: &CompletionConfig,
|
||||
config: &CompletionConfig<'_>,
|
||||
position: FilePosition,
|
||||
trigger_character: Option<char>,
|
||||
) -> Option<Vec<CompletionItem>> {
|
||||
|
@ -269,7 +269,7 @@ pub fn completions(
|
|||
/// This is used for import insertion done via completions like flyimport and custom user snippets.
|
||||
pub fn resolve_completion_edits(
|
||||
db: &RootDatabase,
|
||||
config: &CompletionConfig,
|
||||
config: &CompletionConfig<'_>,
|
||||
FilePosition { file_id, offset }: FilePosition,
|
||||
imports: impl IntoIterator<Item = (String, String)>,
|
||||
) -> Option<Vec<TextEdit>> {
|
||||
|
|
|
@ -100,9 +100,9 @@
|
|||
// }
|
||||
// ----
|
||||
|
||||
use hir::{ModPath, Name, Symbol};
|
||||
use ide_db::imports::import_assets::LocatedImport;
|
||||
use itertools::Itertools;
|
||||
use syntax::{ast, AstNode, GreenNode, SyntaxNode};
|
||||
|
||||
use crate::context::CompletionContext;
|
||||
|
||||
|
@ -123,10 +123,7 @@ pub struct Snippet {
|
|||
pub scope: SnippetScope,
|
||||
pub description: Option<Box<str>>,
|
||||
snippet: String,
|
||||
// These are `ast::Path`'s but due to SyntaxNodes not being Send we store these
|
||||
// and reconstruct them on demand instead. This is cheaper than reparsing them
|
||||
// from strings
|
||||
requires: Box<[GreenNode]>,
|
||||
requires: Box<[ModPath]>,
|
||||
}
|
||||
|
||||
impl Snippet {
|
||||
|
@ -143,7 +140,6 @@ impl Snippet {
|
|||
}
|
||||
let (requires, snippet, description) = validate_snippet(snippet, description, requires)?;
|
||||
Some(Snippet {
|
||||
// Box::into doesn't work as that has a Copy bound 😒
|
||||
postfix_triggers: postfix_triggers.iter().map(String::as_str).map(Into::into).collect(),
|
||||
prefix_triggers: prefix_triggers.iter().map(String::as_str).map(Into::into).collect(),
|
||||
scope,
|
||||
|
@ -167,15 +163,11 @@ impl Snippet {
|
|||
}
|
||||
}
|
||||
|
||||
fn import_edits(ctx: &CompletionContext<'_>, requires: &[GreenNode]) -> Option<Vec<LocatedImport>> {
|
||||
fn import_edits(ctx: &CompletionContext<'_>, requires: &[ModPath]) -> Option<Vec<LocatedImport>> {
|
||||
let import_cfg = ctx.config.import_path_config();
|
||||
|
||||
let resolve = |import: &GreenNode| {
|
||||
let path = ast::Path::cast(SyntaxNode::new_root(import.clone()))?;
|
||||
let item = match ctx.scope.speculative_resolve(&path)? {
|
||||
hir::PathResolution::Def(def) => def.into(),
|
||||
_ => return None,
|
||||
};
|
||||
let resolve = |import| {
|
||||
let item = ctx.scope.resolve_mod_path(import).next()?;
|
||||
let path = ctx.module.find_use_path(
|
||||
ctx.db,
|
||||
item,
|
||||
|
@ -198,19 +190,14 @@ fn validate_snippet(
|
|||
snippet: &[String],
|
||||
description: &str,
|
||||
requires: &[String],
|
||||
) -> Option<(Box<[GreenNode]>, String, Option<Box<str>>)> {
|
||||
) -> Option<(Box<[ModPath]>, String, Option<Box<str>>)> {
|
||||
let mut imports = Vec::with_capacity(requires.len());
|
||||
for path in requires.iter() {
|
||||
let use_path =
|
||||
ast::SourceFile::parse(&format!("use {path};"), syntax::Edition::CURRENT_FIXME)
|
||||
.syntax_node()
|
||||
.descendants()
|
||||
.find_map(ast::Path::cast)?;
|
||||
if use_path.syntax().text() != path.as_str() {
|
||||
return None;
|
||||
}
|
||||
let green = use_path.syntax().green().into_owned();
|
||||
imports.push(green);
|
||||
let use_path = ModPath::from_segments(
|
||||
hir::PathKind::Plain,
|
||||
path.split("::").map(Symbol::intern).map(Name::new_symbol_root),
|
||||
);
|
||||
imports.push(use_path);
|
||||
}
|
||||
let snippet = snippet.iter().join("\n");
|
||||
let description = (!description.is_empty())
|
||||
|
|
|
@ -61,7 +61,7 @@ fn function() {}
|
|||
union Union { field: i32 }
|
||||
"#;
|
||||
|
||||
pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
|
||||
pub(crate) const TEST_CONFIG: CompletionConfig<'_> = CompletionConfig {
|
||||
enable_postfix_completions: true,
|
||||
enable_imports_on_the_fly: true,
|
||||
enable_self_on_the_fly: true,
|
||||
|
@ -85,6 +85,8 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
|
|||
snippets: Vec::new(),
|
||||
limit: None,
|
||||
fields_to_resolve: CompletionFieldsToResolve::empty(),
|
||||
exclude_flyimport: vec![],
|
||||
exclude_traits: &[],
|
||||
};
|
||||
|
||||
pub(crate) fn completion_list(ra_fixture: &str) -> String {
|
||||
|
@ -109,7 +111,7 @@ pub(crate) fn completion_list_with_trigger_character(
|
|||
}
|
||||
|
||||
fn completion_list_with_config_raw(
|
||||
config: CompletionConfig,
|
||||
config: CompletionConfig<'_>,
|
||||
ra_fixture: &str,
|
||||
include_keywords: bool,
|
||||
trigger_character: Option<char>,
|
||||
|
@ -132,7 +134,7 @@ fn completion_list_with_config_raw(
|
|||
}
|
||||
|
||||
fn completion_list_with_config(
|
||||
config: CompletionConfig,
|
||||
config: CompletionConfig<'_>,
|
||||
ra_fixture: &str,
|
||||
include_keywords: bool,
|
||||
trigger_character: Option<char>,
|
||||
|
@ -161,7 +163,7 @@ pub(crate) fn do_completion(code: &str, kind: CompletionItemKind) -> Vec<Complet
|
|||
}
|
||||
|
||||
pub(crate) fn do_completion_with_config(
|
||||
config: CompletionConfig,
|
||||
config: CompletionConfig<'_>,
|
||||
code: &str,
|
||||
kind: CompletionItemKind,
|
||||
) -> Vec<CompletionItem> {
|
||||
|
@ -220,7 +222,7 @@ pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after:
|
|||
|
||||
#[track_caller]
|
||||
pub(crate) fn check_edit_with_config(
|
||||
config: CompletionConfig,
|
||||
config: CompletionConfig<'_>,
|
||||
what: &str,
|
||||
ra_fixture_before: &str,
|
||||
ra_fixture_after: &str,
|
||||
|
@ -257,7 +259,7 @@ fn check_empty(ra_fixture: &str, expect: Expect) {
|
|||
}
|
||||
|
||||
pub(crate) fn get_all_items(
|
||||
config: CompletionConfig,
|
||||
config: CompletionConfig<'_>,
|
||||
code: &str,
|
||||
trigger_character: Option<char>,
|
||||
) -> Vec<CompletionItem> {
|
||||
|
|
|
@ -1,13 +1,30 @@
|
|||
//! Completion tests for expressions.
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
use crate::tests::{check_edit, check_empty, completion_list, BASE_ITEMS_FIXTURE};
|
||||
use crate::{
|
||||
config::AutoImportExclusionType,
|
||||
tests::{
|
||||
check_edit, check_empty, completion_list, completion_list_with_config, BASE_ITEMS_FIXTURE,
|
||||
TEST_CONFIG,
|
||||
},
|
||||
CompletionConfig,
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}{ra_fixture}"));
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
||||
fn check_with_config(config: CompletionConfig<'_>, ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list_with_config(
|
||||
config,
|
||||
&format!("{BASE_ITEMS_FIXTURE}{ra_fixture}"),
|
||||
true,
|
||||
None,
|
||||
);
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_literal_struct_with_a_private_field() {
|
||||
// `FooDesc.bar` is private, the completion should not be triggered.
|
||||
|
@ -1390,3 +1407,381 @@ fn main() {
|
|||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn excluded_trait_method_is_excluded() {
|
||||
check_with_config(
|
||||
CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
|
||||
r#"
|
||||
trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
fn inherent(&self) {}
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
Foo.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar() (as ExcludedTrait) fn(&self)
|
||||
me baz() (as ExcludedTrait) fn(&self)
|
||||
me foo() (as ExcludedTrait) fn(&self)
|
||||
me inherent() fn(&self)
|
||||
sn box Box::new(expr)
|
||||
sn call function(expr)
|
||||
sn dbg dbg!(expr)
|
||||
sn dbgr dbg!(&expr)
|
||||
sn deref *expr
|
||||
sn let let
|
||||
sn letm let mut
|
||||
sn match match expr {}
|
||||
sn ref &expr
|
||||
sn refm &mut expr
|
||||
sn return return expr
|
||||
sn unsafe unsafe {}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn excluded_trait_not_excluded_when_inherent() {
|
||||
check_with_config(
|
||||
CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
|
||||
r#"
|
||||
trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
|
||||
fn foo(v: &dyn ExcludedTrait) {
|
||||
v.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar() (as ExcludedTrait) fn(&self)
|
||||
me baz() (as ExcludedTrait) fn(&self)
|
||||
me foo() (as ExcludedTrait) fn(&self)
|
||||
sn box Box::new(expr)
|
||||
sn call function(expr)
|
||||
sn dbg dbg!(expr)
|
||||
sn dbgr dbg!(&expr)
|
||||
sn deref *expr
|
||||
sn let let
|
||||
sn letm let mut
|
||||
sn match match expr {}
|
||||
sn ref &expr
|
||||
sn refm &mut expr
|
||||
sn return return expr
|
||||
sn unsafe unsafe {}
|
||||
"#]],
|
||||
);
|
||||
check_with_config(
|
||||
CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
|
||||
r#"
|
||||
trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
|
||||
fn foo(v: impl ExcludedTrait) {
|
||||
v.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar() (as ExcludedTrait) fn(&self)
|
||||
me baz() (as ExcludedTrait) fn(&self)
|
||||
me foo() (as ExcludedTrait) fn(&self)
|
||||
sn box Box::new(expr)
|
||||
sn call function(expr)
|
||||
sn dbg dbg!(expr)
|
||||
sn dbgr dbg!(&expr)
|
||||
sn deref *expr
|
||||
sn let let
|
||||
sn letm let mut
|
||||
sn match match expr {}
|
||||
sn ref &expr
|
||||
sn refm &mut expr
|
||||
sn return return expr
|
||||
sn unsafe unsafe {}
|
||||
"#]],
|
||||
);
|
||||
check_with_config(
|
||||
CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
|
||||
r#"
|
||||
trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
|
||||
fn foo<T: ExcludedTrait>(v: T) {
|
||||
v.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar() (as ExcludedTrait) fn(&self)
|
||||
me baz() (as ExcludedTrait) fn(&self)
|
||||
me foo() (as ExcludedTrait) fn(&self)
|
||||
sn box Box::new(expr)
|
||||
sn call function(expr)
|
||||
sn dbg dbg!(expr)
|
||||
sn dbgr dbg!(&expr)
|
||||
sn deref *expr
|
||||
sn let let
|
||||
sn letm let mut
|
||||
sn match match expr {}
|
||||
sn ref &expr
|
||||
sn refm &mut expr
|
||||
sn return return expr
|
||||
sn unsafe unsafe {}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn excluded_trait_method_is_excluded_from_flyimport() {
|
||||
check_with_config(
|
||||
CompletionConfig {
|
||||
exclude_traits: &["test::module2::ExcludedTrait".to_owned()],
|
||||
..TEST_CONFIG
|
||||
},
|
||||
r#"
|
||||
mod module2 {
|
||||
pub trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
}
|
||||
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
fn inherent(&self) {}
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
Foo.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar() (use module2::ExcludedTrait) fn(&self)
|
||||
me baz() (use module2::ExcludedTrait) fn(&self)
|
||||
me foo() (use module2::ExcludedTrait) fn(&self)
|
||||
me inherent() fn(&self)
|
||||
sn box Box::new(expr)
|
||||
sn call function(expr)
|
||||
sn dbg dbg!(expr)
|
||||
sn dbgr dbg!(&expr)
|
||||
sn deref *expr
|
||||
sn let let
|
||||
sn letm let mut
|
||||
sn match match expr {}
|
||||
sn ref &expr
|
||||
sn refm &mut expr
|
||||
sn return return expr
|
||||
sn unsafe unsafe {}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flyimport_excluded_trait_method_is_excluded_from_flyimport() {
|
||||
check_with_config(
|
||||
CompletionConfig {
|
||||
exclude_flyimport: vec![(
|
||||
"test::module2::ExcludedTrait".to_owned(),
|
||||
AutoImportExclusionType::Methods,
|
||||
)],
|
||||
..TEST_CONFIG
|
||||
},
|
||||
r#"
|
||||
mod module2 {
|
||||
pub trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
}
|
||||
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
fn inherent(&self) {}
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
Foo.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar() (use module2::ExcludedTrait) fn(&self)
|
||||
me baz() (use module2::ExcludedTrait) fn(&self)
|
||||
me foo() (use module2::ExcludedTrait) fn(&self)
|
||||
me inherent() fn(&self)
|
||||
sn box Box::new(expr)
|
||||
sn call function(expr)
|
||||
sn dbg dbg!(expr)
|
||||
sn dbgr dbg!(&expr)
|
||||
sn deref *expr
|
||||
sn let let
|
||||
sn letm let mut
|
||||
sn match match expr {}
|
||||
sn ref &expr
|
||||
sn refm &mut expr
|
||||
sn return return expr
|
||||
sn unsafe unsafe {}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn excluded_trait_method_is_excluded_from_path_completion() {
|
||||
check_with_config(
|
||||
CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
|
||||
r#"
|
||||
pub trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
fn inherent(&self) {}
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
Foo::$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar(…) (as ExcludedTrait) fn(&self)
|
||||
me baz(…) (as ExcludedTrait) fn(&self)
|
||||
me foo(…) (as ExcludedTrait) fn(&self)
|
||||
me inherent(…) fn(&self)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn excluded_trait_method_is_not_excluded_when_trait_is_specified() {
|
||||
check_with_config(
|
||||
CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
|
||||
r#"
|
||||
pub trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
fn inherent(&self) {}
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
ExcludedTrait::$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar(…) (as ExcludedTrait) fn(&self)
|
||||
me baz(…) (as ExcludedTrait) fn(&self)
|
||||
me foo(…) (as ExcludedTrait) fn(&self)
|
||||
"#]],
|
||||
);
|
||||
check_with_config(
|
||||
CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
|
||||
r#"
|
||||
pub trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
|
||||
struct Foo;
|
||||
impl Foo {
|
||||
fn inherent(&self) {}
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
<Foo as ExcludedTrait>::$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar(…) (as ExcludedTrait) fn(&self)
|
||||
me baz(…) (as ExcludedTrait) fn(&self)
|
||||
me foo(…) (as ExcludedTrait) fn(&self)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn excluded_trait_not_excluded_when_inherent_path() {
|
||||
check_with_config(
|
||||
CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
|
||||
r#"
|
||||
trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
|
||||
fn foo() {
|
||||
<dyn ExcludedTrait>::$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar(…) (as ExcludedTrait) fn(&self)
|
||||
me baz(…) (as ExcludedTrait) fn(&self)
|
||||
me foo(…) (as ExcludedTrait) fn(&self)
|
||||
"#]],
|
||||
);
|
||||
check_with_config(
|
||||
CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
|
||||
r#"
|
||||
trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
|
||||
fn foo<T: ExcludedTrait>() {
|
||||
T::$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me bar(…) (as ExcludedTrait) fn(&self)
|
||||
me baz(…) (as ExcludedTrait) fn(&self)
|
||||
me foo(…) (as ExcludedTrait) fn(&self)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,10 +3,14 @@ use expect_test::{expect, Expect};
|
|||
use crate::{
|
||||
context::{CompletionAnalysis, NameContext, NameKind, NameRefKind},
|
||||
tests::{check_edit, check_edit_with_config, TEST_CONFIG},
|
||||
CompletionConfig,
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let config = TEST_CONFIG;
|
||||
check_with_config(TEST_CONFIG, ra_fixture, expect);
|
||||
}
|
||||
|
||||
fn check_with_config(config: CompletionConfig<'_>, ra_fixture: &str, expect: Expect) {
|
||||
let (db, position) = crate::tests::position(ra_fixture);
|
||||
let (ctx, analysis) = crate::context::CompletionContext::new(&db, position, &config).unwrap();
|
||||
|
||||
|
@ -1762,3 +1766,31 @@ fn function() {
|
|||
expect![""],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn excluded_trait_item_included_when_exact_match() {
|
||||
check_with_config(
|
||||
CompletionConfig {
|
||||
exclude_traits: &["test::module2::ExcludedTrait".to_owned()],
|
||||
..TEST_CONFIG
|
||||
},
|
||||
r#"
|
||||
mod module2 {
|
||||
pub trait ExcludedTrait {
|
||||
fn foo(&self) {}
|
||||
fn bar(&self) {}
|
||||
fn baz(&self) {}
|
||||
}
|
||||
|
||||
impl<T> ExcludedTrait for T {}
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
true.foo$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me foo() (use module2::ExcludedTrait) fn(&self)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1345,7 +1345,7 @@ struct Foo<T: PartialOrd
|
|||
}
|
||||
|
||||
fn check_signatures(src: &str, kind: CompletionItemKind, reduced: Expect, full: Expect) {
|
||||
const FULL_SIGNATURES_CONFIG: crate::CompletionConfig = {
|
||||
const FULL_SIGNATURES_CONFIG: crate::CompletionConfig<'_> = {
|
||||
let mut x = TEST_CONFIG;
|
||||
x.full_function_signatures = true;
|
||||
x
|
||||
|
|
|
@ -671,7 +671,7 @@ impl Analysis {
|
|||
/// Computes completions at the given position.
|
||||
pub fn completions(
|
||||
&self,
|
||||
config: &CompletionConfig,
|
||||
config: &CompletionConfig<'_>,
|
||||
position: FilePosition,
|
||||
trigger_character: Option<char>,
|
||||
) -> Cancellable<Option<Vec<CompletionItem>>> {
|
||||
|
@ -683,7 +683,7 @@ impl Analysis {
|
|||
/// Resolves additional completion data at the position given.
|
||||
pub fn resolve_completion_edits(
|
||||
&self,
|
||||
config: &CompletionConfig,
|
||||
config: &CompletionConfig<'_>,
|
||||
position: FilePosition,
|
||||
imports: impl IntoIterator<Item = (String, String)> + std::panic::UnwindSafe,
|
||||
) -> Cancellable<Vec<TextEdit>> {
|
||||
|
|
|
@ -446,11 +446,32 @@ 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 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_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 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`.
|
||||
///
|
||||
/// Note that the trait themselves can still be completed.
|
||||
completion_excludeTraits: Vec<String> = Vec::new(),
|
||||
/// Whether to show full function/method signatures in completion docs.
|
||||
completion_fullFunctionSignatures_enable: bool = false,
|
||||
/// Whether to omit deprecated items from autocompletion. By default they are marked as deprecated but not hidden.
|
||||
|
@ -1441,7 +1462,7 @@ impl Config {
|
|||
CallHierarchyConfig { exclude_tests: self.references_excludeTests().to_owned() }
|
||||
}
|
||||
|
||||
pub fn completion(&self, source_root: Option<SourceRootId>) -> CompletionConfig {
|
||||
pub fn completion(&self, source_root: Option<SourceRootId>) -> CompletionConfig<'_> {
|
||||
let client_capability_fields = self.completion_resolve_support_properties();
|
||||
CompletionConfig {
|
||||
enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(),
|
||||
|
@ -1472,6 +1493,27 @@ impl Config {
|
|||
} else {
|
||||
CompletionFieldsToResolve::from_client_capabilities(&client_capability_fields)
|
||||
},
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2417,6 +2459,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 {
|
||||
|
@ -3488,6 +3545,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})"),
|
||||
}
|
||||
|
||||
|
|
|
@ -174,6 +174,8 @@ fn integrated_completion_benchmark() {
|
|||
limit: None,
|
||||
add_semicolon_to_unit: true,
|
||||
fields_to_resolve: CompletionFieldsToResolve::empty(),
|
||||
exclude_flyimport: vec![],
|
||||
exclude_traits: &[],
|
||||
};
|
||||
let position =
|
||||
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
|
||||
|
@ -222,6 +224,8 @@ fn integrated_completion_benchmark() {
|
|||
limit: None,
|
||||
add_semicolon_to_unit: true,
|
||||
fields_to_resolve: CompletionFieldsToResolve::empty(),
|
||||
exclude_flyimport: vec![],
|
||||
exclude_traits: &[],
|
||||
};
|
||||
let position =
|
||||
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
|
||||
|
@ -268,6 +272,8 @@ fn integrated_completion_benchmark() {
|
|||
limit: None,
|
||||
add_semicolon_to_unit: true,
|
||||
fields_to_resolve: CompletionFieldsToResolve::empty(),
|
||||
exclude_flyimport: vec![],
|
||||
exclude_traits: &[],
|
||||
};
|
||||
let position =
|
||||
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
|
||||
|
|
|
@ -285,6 +285,35 @@ 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.exclude]]rust-analyzer.completion.autoimport.exclude::
|
||||
+
|
||||
--
|
||||
Default:
|
||||
----
|
||||
[
|
||||
{
|
||||
"path": "core::borrow::Borrow",
|
||||
"type": "methods"
|
||||
},
|
||||
{
|
||||
"path": "core::borrow::BorrowMut",
|
||||
"type": "methods"
|
||||
}
|
||||
]
|
||||
----
|
||||
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#`.
|
||||
|
||||
--
|
||||
[[rust-analyzer.completion.autoself.enable]]rust-analyzer.completion.autoself.enable (default: `true`)::
|
||||
+
|
||||
|
@ -297,6 +326,15 @@ with `self` prefixed to them when inside a method.
|
|||
--
|
||||
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 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`.
|
||||
|
||||
Note that the trait themselves can still be completed.
|
||||
--
|
||||
[[rust-analyzer.completion.fullFunctionSignatures.enable]]rust-analyzer.completion.fullFunctionSignatures.enable (default: `false`)::
|
||||
+
|
||||
--
|
||||
|
|
|
@ -1144,6 +1144,51 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "completion",
|
||||
"properties": {
|
||||
"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": [
|
||||
{
|
||||
"path": "core::borrow::Borrow",
|
||||
"type": "methods"
|
||||
},
|
||||
{
|
||||
"path": "core::borrow::BorrowMut",
|
||||
"type": "methods"
|
||||
}
|
||||
],
|
||||
"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."
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "completion",
|
||||
"properties": {
|
||||
|
@ -1174,6 +1219,19 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "completion",
|
||||
"properties": {
|
||||
"rust-analyzer.completion.excludeTraits": {
|
||||
"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": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "completion",
|
||||
"properties": {
|
||||
|
|
Loading…
Reference in a new issue