Add flyimport completion for trait assoc items
This commit is contained in:
@@ -3,7 +3,7 @@ use ide_db::helpers::{
|
|||||||
insert_use::{insert_use, ImportScope},
|
insert_use::{insert_use, ImportScope},
|
||||||
mod_path_to_ast,
|
mod_path_to_ast,
|
||||||
};
|
};
|
||||||
use syntax::ast;
|
use syntax::{ast, AstNode, SyntaxNode};
|
||||||
|
|
||||||
use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
|
use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
|
||||||
|
|
||||||
@@ -82,25 +82,16 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
|
|||||||
// # pub mod std { pub mod collections { pub struct HashMap { } } }
|
// # pub mod std { pub mod collections { pub struct HashMap { } } }
|
||||||
// ```
|
// ```
|
||||||
pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||||
let import_assets =
|
let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
|
||||||
if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
|
let proposed_imports =
|
||||||
ImportAssets::for_regular_path(path_under_caret, &ctx.sema)
|
import_assets.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind);
|
||||||
} else if let Some(method_under_caret) =
|
|
||||||
ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
|
|
||||||
{
|
|
||||||
ImportAssets::for_method_call(method_under_caret, &ctx.sema)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}?;
|
|
||||||
let proposed_imports = import_assets.search_for_imports(&ctx.sema, &ctx.config.insert_use);
|
|
||||||
if proposed_imports.is_empty() {
|
if proposed_imports.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
|
let range = ctx.sema.original_range(&syntax_under_caret).range;
|
||||||
let group = import_group_message(import_assets.import_candidate());
|
let group = import_group_message(import_assets.import_candidate());
|
||||||
let scope =
|
let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
|
||||||
ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), &ctx.sema)?;
|
|
||||||
for (import, _) in proposed_imports {
|
for (import, _) in proposed_imports {
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group,
|
&group,
|
||||||
@@ -117,14 +108,28 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
|
|||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn find_importable_node(ctx: &AssistContext) -> Option<(ImportAssets, SyntaxNode)> {
|
||||||
|
if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
|
||||||
|
ImportAssets::for_exact_path(&path_under_caret, &ctx.sema)
|
||||||
|
.zip(Some(path_under_caret.syntax().clone()))
|
||||||
|
} else if let Some(method_under_caret) =
|
||||||
|
ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
|
||||||
|
{
|
||||||
|
ImportAssets::for_method_call(&method_under_caret, &ctx.sema)
|
||||||
|
.zip(Some(method_under_caret.syntax().clone()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
|
fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
|
||||||
let name = match import_candidate {
|
let name = match import_candidate {
|
||||||
ImportCandidate::Path(candidate) => format!("Import {}", &candidate.name),
|
ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()),
|
||||||
ImportCandidate::TraitAssocItem(candidate) => {
|
ImportCandidate::TraitAssocItem(candidate) => {
|
||||||
format!("Import a trait for item {}", &candidate.name)
|
format!("Import a trait for item {}", candidate.name.text())
|
||||||
}
|
}
|
||||||
ImportCandidate::TraitMethod(candidate) => {
|
ImportCandidate::TraitMethod(candidate) => {
|
||||||
format!("Import a trait for method {}", &candidate.name)
|
format!("Import a trait for method {}", candidate.name.text())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
GroupLabel(name)
|
GroupLabel(name)
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use hir::AsName;
|
use hir::AsName;
|
||||||
use ide_db::helpers::{
|
use ide_db::helpers::{import_assets::ImportCandidate, mod_path_to_ast};
|
||||||
import_assets::{ImportAssets, ImportCandidate},
|
|
||||||
mod_path_to_ast,
|
|
||||||
};
|
|
||||||
use ide_db::RootDatabase;
|
use ide_db::RootDatabase;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast,
|
ast,
|
||||||
@@ -18,6 +15,8 @@ use crate::{
|
|||||||
AssistId, AssistKind, GroupLabel,
|
AssistId, AssistKind, GroupLabel,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::auto_import::find_importable_node;
|
||||||
|
|
||||||
// Assist: qualify_path
|
// Assist: qualify_path
|
||||||
//
|
//
|
||||||
// If the name is unresolved, provides all possible qualified paths for it.
|
// If the name is unresolved, provides all possible qualified paths for it.
|
||||||
@@ -36,47 +35,38 @@ use crate::{
|
|||||||
// # pub mod std { pub mod collections { pub struct HashMap { } } }
|
// # pub mod std { pub mod collections { pub struct HashMap { } } }
|
||||||
// ```
|
// ```
|
||||||
pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||||
let import_assets =
|
let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
|
||||||
if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
|
|
||||||
ImportAssets::for_regular_path(path_under_caret, &ctx.sema)
|
|
||||||
} else if let Some(method_under_caret) =
|
|
||||||
ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
|
|
||||||
{
|
|
||||||
ImportAssets::for_method_call(method_under_caret, &ctx.sema)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}?;
|
|
||||||
let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema);
|
let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema);
|
||||||
if proposed_imports.is_empty() {
|
if proposed_imports.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let candidate = import_assets.import_candidate();
|
let candidate = import_assets.import_candidate();
|
||||||
let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
|
let range = ctx.sema.original_range(&syntax_under_caret).range;
|
||||||
|
|
||||||
let qualify_candidate = match candidate {
|
let qualify_candidate = match candidate {
|
||||||
ImportCandidate::Path(candidate) => {
|
ImportCandidate::Path(candidate) => {
|
||||||
if candidate.qualifier.is_some() {
|
if candidate.qualifier.is_some() {
|
||||||
mark::hit!(qualify_path_qualifier_start);
|
mark::hit!(qualify_path_qualifier_start);
|
||||||
let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
|
let path = ast::Path::cast(syntax_under_caret)?;
|
||||||
let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
|
let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
|
||||||
QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
|
QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
|
||||||
} else {
|
} else {
|
||||||
mark::hit!(qualify_path_unqualified_name);
|
mark::hit!(qualify_path_unqualified_name);
|
||||||
let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
|
let path = ast::Path::cast(syntax_under_caret)?;
|
||||||
let generics = path.segment()?.generic_arg_list();
|
let generics = path.segment()?.generic_arg_list();
|
||||||
QualifyCandidate::UnqualifiedName(generics)
|
QualifyCandidate::UnqualifiedName(generics)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImportCandidate::TraitAssocItem(_) => {
|
ImportCandidate::TraitAssocItem(_) => {
|
||||||
mark::hit!(qualify_path_trait_assoc_item);
|
mark::hit!(qualify_path_trait_assoc_item);
|
||||||
let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
|
let path = ast::Path::cast(syntax_under_caret)?;
|
||||||
let (qualifier, segment) = (path.qualifier()?, path.segment()?);
|
let (qualifier, segment) = (path.qualifier()?, path.segment()?);
|
||||||
QualifyCandidate::TraitAssocItem(qualifier, segment)
|
QualifyCandidate::TraitAssocItem(qualifier, segment)
|
||||||
}
|
}
|
||||||
ImportCandidate::TraitMethod(_) => {
|
ImportCandidate::TraitMethod(_) => {
|
||||||
mark::hit!(qualify_path_trait_method);
|
mark::hit!(qualify_path_trait_method);
|
||||||
let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?;
|
let mcall_expr = ast::MethodCallExpr::cast(syntax_under_caret)?;
|
||||||
QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
|
QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -140,7 +130,7 @@ impl QualifyCandidate<'_> {
|
|||||||
let generics =
|
let generics =
|
||||||
mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string);
|
mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string);
|
||||||
let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
|
let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
|
||||||
let trait_ = item_as_trait(item)?;
|
let trait_ = item_as_trait(db, item)?;
|
||||||
let method = find_trait_method(db, trait_, &trait_method_name)?;
|
let method = find_trait_method(db, trait_, &trait_method_name)?;
|
||||||
if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
|
if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
|
||||||
let receiver = match self_access {
|
let receiver = match self_access {
|
||||||
@@ -179,11 +169,13 @@ fn find_trait_method(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> {
|
fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
|
||||||
if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(item.as_module_def_id()?) {
|
let item_module_def = hir::ModuleDef::from(item.as_module_def_id()?);
|
||||||
|
|
||||||
|
if let hir::ModuleDef::Trait(trait_) = item_module_def {
|
||||||
Some(trait_)
|
Some(trait_)
|
||||||
} else {
|
} else {
|
||||||
None
|
item_module_def.as_assoc_item(db)?.containing_trait(db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +183,8 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel {
|
|||||||
let name = match candidate {
|
let name = match candidate {
|
||||||
ImportCandidate::Path(it) => &it.name,
|
ImportCandidate::Path(it) => &it.name,
|
||||||
ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
|
ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
|
||||||
};
|
}
|
||||||
|
.text();
|
||||||
GroupLabel(format!("Qualify {}", name))
|
GroupLabel(format!("Qualify {}", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,8 @@
|
|||||||
//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
|
//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
|
||||||
//! capability enabled.
|
//! capability enabled.
|
||||||
|
|
||||||
use either::Either;
|
|
||||||
use hir::{ModPath, ScopeDef};
|
use hir::{ModPath, ScopeDef};
|
||||||
use ide_db::{helpers::insert_use::ImportScope, imports_locator};
|
use ide_db::helpers::{import_assets::ImportAssets, insert_use::ImportScope};
|
||||||
use syntax::AstNode;
|
use syntax::AstNode;
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ use crate::{
|
|||||||
use super::Completions;
|
use super::Completions;
|
||||||
|
|
||||||
pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||||
if !ctx.config.enable_autoimport_completions {
|
if !ctx.config.enable_imports_on_the_fly {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
|
if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
|
||||||
@@ -72,46 +71,56 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
|
|||||||
}
|
}
|
||||||
let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
|
let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
|
||||||
|
|
||||||
let current_module = ctx.scope.module()?;
|
let import_scope =
|
||||||
let anchor = ctx.name_ref_syntax.as_ref()?;
|
ImportScope::find_insert_use_container(ctx.name_ref_syntax.as_ref()?.syntax(), &ctx.sema)?;
|
||||||
let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
|
|
||||||
|
|
||||||
let user_input_lowercased = potential_import_name.to_lowercase();
|
let user_input_lowercased = potential_import_name.to_lowercase();
|
||||||
let mut all_mod_paths = imports_locator::find_similar_imports(
|
let mut all_mod_paths = import_assets(ctx, potential_import_name)?
|
||||||
&ctx.sema,
|
.search_for_relative_paths(&ctx.sema)
|
||||||
ctx.krate?,
|
.into_iter()
|
||||||
Some(40),
|
.map(|(mod_path, item_in_ns)| {
|
||||||
potential_import_name,
|
let scope_item = match item_in_ns {
|
||||||
true,
|
hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
|
||||||
true,
|
hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
|
||||||
)
|
hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
|
||||||
.filter_map(|import_candidate| {
|
};
|
||||||
Some(match import_candidate {
|
(mod_path, scope_item)
|
||||||
Either::Left(module_def) => {
|
|
||||||
(current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def))
|
|
||||||
}
|
|
||||||
Either::Right(macro_def) => {
|
|
||||||
(current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.filter(|(mod_path, _)| mod_path.len() > 1)
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
|
all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
|
||||||
compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
|
compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
|
||||||
});
|
});
|
||||||
|
|
||||||
acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
|
acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
|
||||||
render_resolution_with_import(
|
let import_for_trait_assoc_item = match definition {
|
||||||
RenderContext::new(ctx),
|
ScopeDef::ModuleDef(module_def) => module_def
|
||||||
ImportEdit { import_path, import_scope: import_scope.clone() },
|
.as_assoc_item(ctx.db)
|
||||||
&definition,
|
.and_then(|assoc| assoc.containing_trait(ctx.db))
|
||||||
)
|
.is_some(),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
let import_edit = ImportEdit {
|
||||||
|
import_path,
|
||||||
|
import_scope: import_scope.clone(),
|
||||||
|
import_for_trait_assoc_item,
|
||||||
|
};
|
||||||
|
render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
|
||||||
}));
|
}));
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
|
||||||
|
let current_module = ctx.scope.module()?;
|
||||||
|
if let Some(dot_receiver) = &ctx.dot_receiver {
|
||||||
|
ImportAssets::for_fuzzy_method_call(
|
||||||
|
current_module,
|
||||||
|
ctx.sema.type_of_expr(dot_receiver)?,
|
||||||
|
fuzzy_name,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ImportAssets::for_fuzzy_path(current_module, ctx.path_qual.clone(), fuzzy_name, &ctx.sema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_fuzzy_completion_order_key(
|
fn compute_fuzzy_completion_order_key(
|
||||||
proposed_mod_path: &ModPath,
|
proposed_mod_path: &ModPath,
|
||||||
user_input_lowercased: &str,
|
user_input_lowercased: &str,
|
||||||
@@ -258,6 +267,176 @@ fn main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trait_function_fuzzy_completion() {
|
||||||
|
let fixture = r#"
|
||||||
|
//- /lib.rs crate:dep
|
||||||
|
pub mod test_mod {
|
||||||
|
pub trait TestTrait {
|
||||||
|
const SPECIAL_CONST: u8;
|
||||||
|
type HumbleType;
|
||||||
|
fn weird_function();
|
||||||
|
fn random_method(&self);
|
||||||
|
}
|
||||||
|
pub struct TestStruct {}
|
||||||
|
impl TestTrait for TestStruct {
|
||||||
|
const SPECIAL_CONST: u8 = 42;
|
||||||
|
type HumbleType = ();
|
||||||
|
fn weird_function() {}
|
||||||
|
fn random_method(&self) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//- /main.rs crate:main deps:dep
|
||||||
|
fn main() {
|
||||||
|
dep::test_mod::TestStruct::wei$0
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
check(
|
||||||
|
fixture,
|
||||||
|
expect![[r#"
|
||||||
|
fn weird_function() (dep::test_mod::TestTrait) fn weird_function()
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_edit(
|
||||||
|
"weird_function",
|
||||||
|
fixture,
|
||||||
|
r#"
|
||||||
|
use dep::test_mod::TestTrait;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dep::test_mod::TestStruct::weird_function()$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trait_const_fuzzy_completion() {
|
||||||
|
let fixture = r#"
|
||||||
|
//- /lib.rs crate:dep
|
||||||
|
pub mod test_mod {
|
||||||
|
pub trait TestTrait {
|
||||||
|
const SPECIAL_CONST: u8;
|
||||||
|
type HumbleType;
|
||||||
|
fn weird_function();
|
||||||
|
fn random_method(&self);
|
||||||
|
}
|
||||||
|
pub struct TestStruct {}
|
||||||
|
impl TestTrait for TestStruct {
|
||||||
|
const SPECIAL_CONST: u8 = 42;
|
||||||
|
type HumbleType = ();
|
||||||
|
fn weird_function() {}
|
||||||
|
fn random_method(&self) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//- /main.rs crate:main deps:dep
|
||||||
|
fn main() {
|
||||||
|
dep::test_mod::TestStruct::spe$0
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
check(
|
||||||
|
fixture,
|
||||||
|
expect![[r#"
|
||||||
|
ct SPECIAL_CONST (dep::test_mod::TestTrait)
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_edit(
|
||||||
|
"SPECIAL_CONST",
|
||||||
|
fixture,
|
||||||
|
r#"
|
||||||
|
use dep::test_mod::TestTrait;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dep::test_mod::TestStruct::SPECIAL_CONST
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trait_method_fuzzy_completion() {
|
||||||
|
let fixture = r#"
|
||||||
|
//- /lib.rs crate:dep
|
||||||
|
pub mod test_mod {
|
||||||
|
pub trait TestTrait {
|
||||||
|
const SPECIAL_CONST: u8;
|
||||||
|
type HumbleType;
|
||||||
|
fn weird_function();
|
||||||
|
fn random_method(&self);
|
||||||
|
}
|
||||||
|
pub struct TestStruct {}
|
||||||
|
impl TestTrait for TestStruct {
|
||||||
|
const SPECIAL_CONST: u8 = 42;
|
||||||
|
type HumbleType = ();
|
||||||
|
fn weird_function() {}
|
||||||
|
fn random_method(&self) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//- /main.rs crate:main deps:dep
|
||||||
|
fn main() {
|
||||||
|
let test_struct = dep::test_mod::TestStruct {};
|
||||||
|
test_struct.ran$0
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
check(
|
||||||
|
fixture,
|
||||||
|
expect![[r#"
|
||||||
|
me random_method() (dep::test_mod::TestTrait) fn random_method(&self)
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_edit(
|
||||||
|
"random_method",
|
||||||
|
fixture,
|
||||||
|
r#"
|
||||||
|
use dep::test_mod::TestTrait;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let test_struct = dep::test_mod::TestStruct {};
|
||||||
|
test_struct.random_method()$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_trait_type_fuzzy_completion() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs crate:dep
|
||||||
|
pub mod test_mod {
|
||||||
|
pub trait TestTrait {
|
||||||
|
const SPECIAL_CONST: u8;
|
||||||
|
type HumbleType;
|
||||||
|
fn weird_function();
|
||||||
|
fn random_method(&self);
|
||||||
|
}
|
||||||
|
pub struct TestStruct {}
|
||||||
|
impl TestTrait for TestStruct {
|
||||||
|
const SPECIAL_CONST: u8 = 42;
|
||||||
|
type HumbleType = ();
|
||||||
|
fn weird_function() {}
|
||||||
|
fn random_method(&self) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//- /main.rs crate:main deps:dep
|
||||||
|
fn main() {
|
||||||
|
dep::test_mod::TestStruct::hum$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#""#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn does_not_propose_names_in_scope() {
|
fn does_not_propose_names_in_scope() {
|
||||||
check(
|
check(
|
||||||
@@ -288,4 +467,61 @@ fn main() {
|
|||||||
expect![[r#""#]],
|
expect![[r#""#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn does_not_propose_traits_in_scope() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs crate:dep
|
||||||
|
pub mod test_mod {
|
||||||
|
pub trait TestTrait {
|
||||||
|
const SPECIAL_CONST: u8;
|
||||||
|
type HumbleType;
|
||||||
|
fn weird_function();
|
||||||
|
fn random_method(&self);
|
||||||
|
}
|
||||||
|
pub struct TestStruct {}
|
||||||
|
impl TestTrait for TestStruct {
|
||||||
|
const SPECIAL_CONST: u8 = 42;
|
||||||
|
type HumbleType = ();
|
||||||
|
fn weird_function() {}
|
||||||
|
fn random_method(&self) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//- /main.rs crate:main deps:dep
|
||||||
|
use dep::test_mod::{TestStruct, TestTrait};
|
||||||
|
fn main() {
|
||||||
|
dep::test_mod::TestStruct::hum$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#""#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn blanket_trait_impl_import() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs crate:dep
|
||||||
|
pub mod test_mod {
|
||||||
|
pub struct TestStruct {}
|
||||||
|
pub trait TestTrait {
|
||||||
|
fn another_function();
|
||||||
|
}
|
||||||
|
impl<T> TestTrait for T {
|
||||||
|
fn another_function() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//- /main.rs crate:main deps:dep
|
||||||
|
fn main() {
|
||||||
|
dep::test_mod::TestStruct::ano$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn another_function() (dep::test_mod::TestTrait) fn another_function()
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct CompletionConfig {
|
pub struct CompletionConfig {
|
||||||
pub enable_postfix_completions: bool,
|
pub enable_postfix_completions: bool,
|
||||||
pub enable_autoimport_completions: bool,
|
pub enable_imports_on_the_fly: bool,
|
||||||
pub add_call_parenthesis: bool,
|
pub add_call_parenthesis: bool,
|
||||||
pub add_call_argument_snippets: bool,
|
pub add_call_argument_snippets: bool,
|
||||||
pub snippet_cap: Option<SnippetCap>,
|
pub snippet_cap: Option<SnippetCap>,
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ impl CompletionItem {
|
|||||||
pub struct ImportEdit {
|
pub struct ImportEdit {
|
||||||
pub import_path: ModPath,
|
pub import_path: ModPath,
|
||||||
pub import_scope: ImportScope,
|
pub import_scope: ImportScope,
|
||||||
|
pub import_for_trait_assoc_item: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportEdit {
|
impl ImportEdit {
|
||||||
@@ -321,19 +322,21 @@ impl Builder {
|
|||||||
let mut insert_text = self.insert_text;
|
let mut insert_text = self.insert_text;
|
||||||
|
|
||||||
if let Some(import_to_add) = self.import_to_add.as_ref() {
|
if let Some(import_to_add) = self.import_to_add.as_ref() {
|
||||||
|
if import_to_add.import_for_trait_assoc_item {
|
||||||
|
lookup = lookup.or_else(|| Some(label.clone()));
|
||||||
|
insert_text = insert_text.or_else(|| Some(label.clone()));
|
||||||
|
label = format!("{} ({})", label, import_to_add.import_path);
|
||||||
|
} else {
|
||||||
let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
|
let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
|
||||||
let _ = import_path_without_last_segment.segments.pop();
|
let _ = import_path_without_last_segment.segments.pop();
|
||||||
|
|
||||||
if !import_path_without_last_segment.segments.is_empty() {
|
if !import_path_without_last_segment.segments.is_empty() {
|
||||||
if lookup.is_none() {
|
lookup = lookup.or_else(|| Some(label.clone()));
|
||||||
lookup = Some(label.clone());
|
insert_text = insert_text.or_else(|| Some(label.clone()));
|
||||||
}
|
|
||||||
if insert_text.is_none() {
|
|
||||||
insert_text = Some(label.clone());
|
|
||||||
}
|
|
||||||
label = format!("{}::{}", import_path_without_last_segment, label);
|
label = format!("{}::{}", import_path_without_last_segment, label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let text_edit = match self.text_edit {
|
let text_edit = match self.text_edit {
|
||||||
Some(it) => it,
|
Some(it) => it,
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ pub fn resolve_completion_edits(
|
|||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
full_import_path: &str,
|
full_import_path: &str,
|
||||||
imported_name: String,
|
imported_name: String,
|
||||||
|
import_for_trait_assoc_item: bool,
|
||||||
) -> Option<Vec<TextEdit>> {
|
) -> Option<Vec<TextEdit>> {
|
||||||
let ctx = CompletionContext::new(db, position, config)?;
|
let ctx = CompletionContext::new(db, position, config)?;
|
||||||
let anchor = ctx.name_ref_syntax.as_ref()?;
|
let anchor = ctx.name_ref_syntax.as_ref()?;
|
||||||
@@ -154,7 +155,7 @@ pub fn resolve_completion_edits(
|
|||||||
})
|
})
|
||||||
.find(|mod_path| mod_path.to_string() == full_import_path)?;
|
.find(|mod_path| mod_path.to_string() == full_import_path)?;
|
||||||
|
|
||||||
ImportEdit { import_path, import_scope }
|
ImportEdit { import_path, import_scope, import_for_trait_assoc_item }
|
||||||
.to_text_edit(config.insert_use.merge)
|
.to_text_edit(config.insert_use.merge)
|
||||||
.map(|edit| vec![edit])
|
.map(|edit| vec![edit])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub(crate) mod type_alias;
|
|||||||
|
|
||||||
mod builder_ext;
|
mod builder_ext;
|
||||||
|
|
||||||
use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type};
|
use hir::{Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type};
|
||||||
use ide_db::{helpers::SnippetCap, RootDatabase};
|
use ide_db::{helpers::SnippetCap, RootDatabase};
|
||||||
use syntax::TextRange;
|
use syntax::TextRange;
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
@@ -51,13 +51,13 @@ pub(crate) fn render_resolution_with_import<'a>(
|
|||||||
import_edit: ImportEdit,
|
import_edit: ImportEdit,
|
||||||
resolution: &ScopeDef,
|
resolution: &ScopeDef,
|
||||||
) -> Option<CompletionItem> {
|
) -> Option<CompletionItem> {
|
||||||
Render::new(ctx)
|
let local_name = match resolution {
|
||||||
.render_resolution(
|
ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(),
|
||||||
import_edit.import_path.segments.last()?.to_string(),
|
ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(),
|
||||||
Some(import_edit),
|
ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(),
|
||||||
resolution,
|
_ => import_edit.import_path.segments.last()?.to_string(),
|
||||||
)
|
};
|
||||||
.map(|mut item| {
|
Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| {
|
||||||
item.completion_kind = CompletionKind::Magic;
|
item.completion_kind = CompletionKind::Magic;
|
||||||
item
|
item
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
|
|||||||
|
|
||||||
pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
|
pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
|
||||||
enable_postfix_completions: true,
|
enable_postfix_completions: true,
|
||||||
enable_autoimport_completions: true,
|
enable_imports_on_the_fly: true,
|
||||||
add_call_parenthesis: true,
|
add_call_parenthesis: true,
|
||||||
add_call_argument_snippets: true,
|
add_call_argument_snippets: true,
|
||||||
snippet_cap: SnippetCap::new(true),
|
snippet_cap: SnippetCap::new(true),
|
||||||
|
|||||||
@@ -272,6 +272,15 @@ impl ModuleDef {
|
|||||||
|
|
||||||
hir_ty::diagnostics::validate_module_item(db, module.id.krate, id, sink)
|
hir_ty::diagnostics::validate_module_item(db, module.id.krate, id, sink)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_assoc_item(self, db: &dyn HirDatabase) -> Option<AssocItem> {
|
||||||
|
match self {
|
||||||
|
ModuleDef::Function(f) => f.as_assoc_item(db),
|
||||||
|
ModuleDef::Const(c) => c.as_assoc_item(db),
|
||||||
|
ModuleDef::TypeAlias(t) => t.as_assoc_item(db),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module {
|
impl Module {
|
||||||
@@ -1091,6 +1100,13 @@ impl AssocItem {
|
|||||||
AssocContainerId::ContainerId(_) => panic!("invalid AssocItem"),
|
AssocContainerId::ContainerId(_) => panic!("invalid AssocItem"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn containing_trait(self, db: &dyn HirDatabase) -> Option<Trait> {
|
||||||
|
match self.container(db) {
|
||||||
|
AssocItemContainer::Trait(t) => Some(t),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasVisibility for AssocItem {
|
impl HasVisibility for AssocItem {
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ pub enum ImportKind {
|
|||||||
Trait,
|
Trait,
|
||||||
TypeAlias,
|
TypeAlias,
|
||||||
BuiltinType,
|
BuiltinType,
|
||||||
|
AssociatedItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A way to match import map contents against the search query.
|
/// A way to match import map contents against the search query.
|
||||||
@@ -282,6 +283,7 @@ pub struct Query {
|
|||||||
query: String,
|
query: String,
|
||||||
lowercased: String,
|
lowercased: String,
|
||||||
name_only: bool,
|
name_only: bool,
|
||||||
|
assoc_items_only: bool,
|
||||||
search_mode: SearchMode,
|
search_mode: SearchMode,
|
||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
limit: usize,
|
limit: usize,
|
||||||
@@ -295,6 +297,7 @@ impl Query {
|
|||||||
query,
|
query,
|
||||||
lowercased,
|
lowercased,
|
||||||
name_only: false,
|
name_only: false,
|
||||||
|
assoc_items_only: false,
|
||||||
search_mode: SearchMode::Contains,
|
search_mode: SearchMode::Contains,
|
||||||
case_sensitive: false,
|
case_sensitive: false,
|
||||||
limit: usize::max_value(),
|
limit: usize::max_value(),
|
||||||
@@ -309,6 +312,11 @@ impl Query {
|
|||||||
Self { name_only: true, ..self }
|
Self { name_only: true, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Matches only the entries that are associated items, ignoring the rest.
|
||||||
|
pub fn assoc_items_only(self) -> Self {
|
||||||
|
Self { assoc_items_only: true, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
/// Specifies the way to search for the entries using the query.
|
/// Specifies the way to search for the entries using the query.
|
||||||
pub fn search_mode(self, search_mode: SearchMode) -> Self {
|
pub fn search_mode(self, search_mode: SearchMode) -> Self {
|
||||||
Self { search_mode, ..self }
|
Self { search_mode, ..self }
|
||||||
@@ -331,6 +339,14 @@ impl Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool {
|
fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool {
|
||||||
|
if import.is_trait_assoc_item {
|
||||||
|
if self.exclude_import_kinds.contains(&ImportKind::AssociatedItem) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if self.assoc_items_only {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let mut input = if import.is_trait_assoc_item || self.name_only {
|
let mut input = if import.is_trait_assoc_item || self.name_only {
|
||||||
import.path.segments.last().unwrap().to_string()
|
import.path.segments.last().unwrap().to_string()
|
||||||
} else {
|
} else {
|
||||||
@@ -813,6 +829,56 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assoc_items_filtering() {
|
||||||
|
let ra_fixture = r#"
|
||||||
|
//- /main.rs crate:main deps:dep
|
||||||
|
//- /dep.rs crate:dep
|
||||||
|
pub mod fmt {
|
||||||
|
pub trait Display {
|
||||||
|
type FmtTypeAlias;
|
||||||
|
const FMT_CONST: bool;
|
||||||
|
|
||||||
|
fn format_function();
|
||||||
|
fn format_method(&self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
check_search(
|
||||||
|
ra_fixture,
|
||||||
|
"main",
|
||||||
|
Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy).assoc_items_only(),
|
||||||
|
expect![[r#"
|
||||||
|
dep::fmt::Display::FMT_CONST (a)
|
||||||
|
dep::fmt::Display::format_function (a)
|
||||||
|
dep::fmt::Display::format_method (a)
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_search(
|
||||||
|
ra_fixture,
|
||||||
|
"main",
|
||||||
|
Query::new("fmt".to_string())
|
||||||
|
.search_mode(SearchMode::Fuzzy)
|
||||||
|
.exclude_import_kind(ImportKind::AssociatedItem),
|
||||||
|
expect![[r#"
|
||||||
|
dep::fmt (t)
|
||||||
|
dep::fmt::Display (t)
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_search(
|
||||||
|
ra_fixture,
|
||||||
|
"main",
|
||||||
|
Query::new("fmt".to_string())
|
||||||
|
.search_mode(SearchMode::Fuzzy)
|
||||||
|
.assoc_items_only()
|
||||||
|
.exclude_import_kind(ImportKind::AssociatedItem),
|
||||||
|
expect![[r#""#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn search_mode() {
|
fn search_mode() {
|
||||||
let ra_fixture = r#"
|
let ra_fixture = r#"
|
||||||
|
|||||||
@@ -438,10 +438,10 @@ fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem)
|
|||||||
FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
|
FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
|
||||||
FieldOrAssocItem::AssocItem(assoc) => match assoc {
|
FieldOrAssocItem::AssocItem(assoc) => match assoc {
|
||||||
AssocItem::Function(function) => {
|
AssocItem::Function(function) => {
|
||||||
let is_trait_method = matches!(
|
let is_trait_method = function
|
||||||
function.as_assoc_item(db).map(|assoc| assoc.container(db)),
|
.as_assoc_item(db)
|
||||||
Some(AssocItemContainer::Trait(..))
|
.and_then(|assoc| assoc.containing_trait(db))
|
||||||
);
|
.is_some();
|
||||||
// This distinction may get more complicated when specialization is available.
|
// This distinction may get more complicated when specialization is available.
|
||||||
// Rustdoc makes this decision based on whether a method 'has defaultness'.
|
// Rustdoc makes this decision based on whether a method 'has defaultness'.
|
||||||
// Currently this is only the case for provided trait methods.
|
// Currently this is only the case for provided trait methods.
|
||||||
|
|||||||
@@ -481,6 +481,7 @@ impl Analysis {
|
|||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
full_import_path: &str,
|
full_import_path: &str,
|
||||||
imported_name: String,
|
imported_name: String,
|
||||||
|
import_for_trait_assoc_item: bool,
|
||||||
) -> Cancelable<Vec<TextEdit>> {
|
) -> Cancelable<Vec<TextEdit>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.with_db(|db| {
|
.with_db(|db| {
|
||||||
@@ -490,6 +491,7 @@ impl Analysis {
|
|||||||
position,
|
position,
|
||||||
full_import_path,
|
full_import_path,
|
||||||
imported_name,
|
imported_name,
|
||||||
|
import_for_trait_assoc_item,
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
.unwrap_or_default())
|
.unwrap_or_default())
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
//! Look up accessible paths for items.
|
//! Look up accessible paths for items.
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
|
use hir::{AsAssocItem, AssocItem, Module, ModuleDef, PrefixKind, Semantics};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use syntax::{ast, AstNode, SyntaxNode};
|
use syntax::{ast, AstNode};
|
||||||
|
|
||||||
use crate::{imports_locator, RootDatabase};
|
use crate::{
|
||||||
|
imports_locator::{self, AssocItemSearch},
|
||||||
use super::insert_use::InsertUseConfig;
|
RootDatabase,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ImportCandidate {
|
pub enum ImportCandidate {
|
||||||
@@ -24,86 +25,141 @@ pub enum ImportCandidate {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TraitImportCandidate {
|
pub struct TraitImportCandidate {
|
||||||
pub ty: hir::Type,
|
pub receiver_ty: hir::Type,
|
||||||
pub name: ast::NameRef,
|
pub name: NameToImport,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PathImportCandidate {
|
pub struct PathImportCandidate {
|
||||||
pub qualifier: Option<ast::Path>,
|
pub qualifier: Option<ast::Path>,
|
||||||
pub name: ast::NameRef,
|
pub name: NameToImport,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NameToImport {
|
||||||
|
Exact(String),
|
||||||
|
Fuzzy(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NameToImport {
|
||||||
|
pub fn text(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
NameToImport::Exact(text) => text.as_str(),
|
||||||
|
NameToImport::Fuzzy(text) => text.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ImportAssets {
|
pub struct ImportAssets {
|
||||||
import_candidate: ImportCandidate,
|
import_candidate: ImportCandidate,
|
||||||
module_with_name_to_import: hir::Module,
|
module_with_candidate: hir::Module,
|
||||||
syntax_under_caret: SyntaxNode,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportAssets {
|
impl ImportAssets {
|
||||||
pub fn for_method_call(
|
pub fn for_method_call(
|
||||||
method_call: ast::MethodCallExpr,
|
method_call: &ast::MethodCallExpr,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let syntax_under_caret = method_call.syntax().to_owned();
|
|
||||||
let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
import_candidate: ImportCandidate::for_method_call(sema, &method_call)?,
|
import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
|
||||||
module_with_name_to_import,
|
module_with_candidate: sema.scope(method_call.syntax()).module()?,
|
||||||
syntax_under_caret,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_regular_path(
|
pub fn for_exact_path(
|
||||||
path_under_caret: ast::Path,
|
fully_qualified_path: &ast::Path,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let syntax_under_caret = path_under_caret.syntax().to_owned();
|
let syntax_under_caret = fully_qualified_path.syntax();
|
||||||
if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
|
if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?,
|
import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
|
||||||
module_with_name_to_import,
|
module_with_candidate: sema.scope(syntax_under_caret).module()?,
|
||||||
syntax_under_caret,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn syntax_under_caret(&self) -> &SyntaxNode {
|
pub fn for_fuzzy_path(
|
||||||
&self.syntax_under_caret
|
module_with_path: Module,
|
||||||
|
qualifier: Option<ast::Path>,
|
||||||
|
fuzzy_name: String,
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
) -> Option<Self> {
|
||||||
|
Some(match qualifier {
|
||||||
|
Some(qualifier) => {
|
||||||
|
let qualifier_resolution = sema.resolve_path(&qualifier)?;
|
||||||
|
match qualifier_resolution {
|
||||||
|
hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => Self {
|
||||||
|
import_candidate: ImportCandidate::TraitAssocItem(TraitImportCandidate {
|
||||||
|
receiver_ty: assoc_item_path.ty(sema.db),
|
||||||
|
name: NameToImport::Fuzzy(fuzzy_name),
|
||||||
|
}),
|
||||||
|
module_with_candidate: module_with_path,
|
||||||
|
},
|
||||||
|
_ => Self {
|
||||||
|
import_candidate: ImportCandidate::Path(PathImportCandidate {
|
||||||
|
qualifier: Some(qualifier),
|
||||||
|
name: NameToImport::Fuzzy(fuzzy_name),
|
||||||
|
}),
|
||||||
|
module_with_candidate: module_with_path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Self {
|
||||||
|
import_candidate: ImportCandidate::Path(PathImportCandidate {
|
||||||
|
qualifier: None,
|
||||||
|
name: NameToImport::Fuzzy(fuzzy_name),
|
||||||
|
}),
|
||||||
|
module_with_candidate: module_with_path,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn for_fuzzy_method_call(
|
||||||
|
module_with_method_call: Module,
|
||||||
|
receiver_ty: hir::Type,
|
||||||
|
fuzzy_method_name: String,
|
||||||
|
) -> Option<Self> {
|
||||||
|
Some(Self {
|
||||||
|
import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
|
||||||
|
receiver_ty,
|
||||||
|
name: NameToImport::Fuzzy(fuzzy_method_name),
|
||||||
|
}),
|
||||||
|
module_with_candidate: module_with_method_call,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImportAssets {
|
||||||
pub fn import_candidate(&self) -> &ImportCandidate {
|
pub fn import_candidate(&self) -> &ImportCandidate {
|
||||||
&self.import_candidate
|
&self.import_candidate
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_search_query(&self) -> &str {
|
fn name_to_import(&self) -> &NameToImport {
|
||||||
match &self.import_candidate {
|
match &self.import_candidate {
|
||||||
ImportCandidate::Path(candidate) => candidate.name.text(),
|
ImportCandidate::Path(candidate) => &candidate.name,
|
||||||
ImportCandidate::TraitAssocItem(candidate)
|
ImportCandidate::TraitAssocItem(candidate)
|
||||||
| ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
|
| ImportCandidate::TraitMethod(candidate) => &candidate.name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_for_imports(
|
pub fn search_for_imports(
|
||||||
&self,
|
&self,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
config: &InsertUseConfig,
|
prefix_kind: PrefixKind,
|
||||||
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
||||||
let _p = profile::span("import_assists::search_for_imports");
|
let _p = profile::span("import_assets::search_for_imports");
|
||||||
self.search_for(sema, Some(config.prefix_kind))
|
self.search_for(sema, Some(prefix_kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This may return non-absolute paths if a part of the returned path is already imported into scope.
|
/// This may return non-absolute paths if a part of the returned path is already imported into scope.
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn search_for_relative_paths(
|
pub fn search_for_relative_paths(
|
||||||
&self,
|
&self,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
||||||
let _p = profile::span("import_assists::search_for_relative_paths");
|
let _p = profile::span("import_assets::search_for_relative_paths");
|
||||||
self.search_for(sema, None)
|
self.search_for(sema, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,60 +170,56 @@ impl ImportAssets {
|
|||||||
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
||||||
let db = sema.db;
|
let db = sema.db;
|
||||||
let mut trait_candidates = FxHashSet::default();
|
let mut trait_candidates = FxHashSet::default();
|
||||||
let current_crate = self.module_with_name_to_import.krate();
|
let current_crate = self.module_with_candidate.krate();
|
||||||
|
|
||||||
let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| {
|
let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| {
|
||||||
trait_candidates.clear();
|
trait_candidates.clear();
|
||||||
match &self.import_candidate {
|
match &self.import_candidate {
|
||||||
ImportCandidate::TraitAssocItem(trait_candidate) => {
|
ImportCandidate::TraitAssocItem(trait_candidate) => {
|
||||||
let located_assoc_item = match candidate {
|
let canidate_assoc_item = match candidate {
|
||||||
Either::Left(ModuleDef::Function(located_function)) => {
|
Either::Left(module_def) => module_def.as_assoc_item(db),
|
||||||
located_function.as_assoc_item(db)
|
|
||||||
}
|
|
||||||
Either::Left(ModuleDef::Const(located_const)) => {
|
|
||||||
located_const.as_assoc_item(db)
|
|
||||||
}
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}?;
|
||||||
.map(|assoc| assoc.container(db))
|
trait_candidates.insert(canidate_assoc_item.containing_trait(db)?.into());
|
||||||
.and_then(Self::assoc_to_trait)?;
|
|
||||||
|
|
||||||
trait_candidates.insert(located_assoc_item.into());
|
|
||||||
|
|
||||||
trait_candidate
|
trait_candidate
|
||||||
.ty
|
.receiver_ty
|
||||||
.iterate_path_candidates(
|
.iterate_path_candidates(
|
||||||
db,
|
db,
|
||||||
current_crate,
|
current_crate,
|
||||||
&trait_candidates,
|
&trait_candidates,
|
||||||
None,
|
None,
|
||||||
|_, assoc| Self::assoc_to_trait(assoc.container(db)),
|
|_, assoc| {
|
||||||
|
if canidate_assoc_item == assoc {
|
||||||
|
Some(assoc_to_module_def(assoc))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.map(ModuleDef::from)
|
|
||||||
.map(Either::Left)
|
.map(Either::Left)
|
||||||
}
|
}
|
||||||
ImportCandidate::TraitMethod(trait_candidate) => {
|
ImportCandidate::TraitMethod(trait_candidate) => {
|
||||||
let located_assoc_item =
|
let canidate_assoc_item = match candidate {
|
||||||
if let Either::Left(ModuleDef::Function(located_function)) = candidate {
|
Either::Left(module_def) => module_def.as_assoc_item(db),
|
||||||
located_function
|
_ => None,
|
||||||
.as_assoc_item(db)
|
|
||||||
.map(|assoc| assoc.container(db))
|
|
||||||
.and_then(Self::assoc_to_trait)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}?;
|
}?;
|
||||||
|
trait_candidates.insert(canidate_assoc_item.containing_trait(db)?.into());
|
||||||
trait_candidates.insert(located_assoc_item.into());
|
|
||||||
|
|
||||||
trait_candidate
|
trait_candidate
|
||||||
.ty
|
.receiver_ty
|
||||||
.iterate_method_candidates(
|
.iterate_method_candidates(
|
||||||
db,
|
db,
|
||||||
current_crate,
|
current_crate,
|
||||||
&trait_candidates,
|
&trait_candidates,
|
||||||
None,
|
None,
|
||||||
|_, function| {
|
|_, function| {
|
||||||
Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
|
let assoc = function.as_assoc_item(db)?;
|
||||||
|
if canidate_assoc_item == assoc {
|
||||||
|
Some(assoc_to_module_def(assoc))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.map(ModuleDef::from)
|
.map(ModuleDef::from)
|
||||||
@@ -177,34 +229,69 @@ impl ImportAssets {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = imports_locator::find_exact_imports(
|
let unfiltered_imports = match self.name_to_import() {
|
||||||
|
NameToImport::Exact(exact_name) => {
|
||||||
|
imports_locator::find_exact_imports(sema, current_crate, exact_name.clone())
|
||||||
|
}
|
||||||
|
// FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items:
|
||||||
|
// instead, we need to look up all trait impls for a certain struct and search through them only
|
||||||
|
// see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032
|
||||||
|
// and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup
|
||||||
|
// for the details
|
||||||
|
NameToImport::Fuzzy(fuzzy_name) => imports_locator::find_similar_imports(
|
||||||
sema,
|
sema,
|
||||||
current_crate,
|
current_crate,
|
||||||
self.get_search_query().to_string(),
|
fuzzy_name.clone(),
|
||||||
)
|
match self.import_candidate {
|
||||||
|
ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
|
||||||
|
AssocItemSearch::AssocItemsOnly
|
||||||
|
}
|
||||||
|
_ => AssocItemSearch::Exclude,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res = unfiltered_imports
|
||||||
.filter_map(filter)
|
.filter_map(filter)
|
||||||
.filter_map(|candidate| {
|
.filter_map(|candidate| {
|
||||||
let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
|
let item: hir::ItemInNs = candidate.clone().either(Into::into, Into::into);
|
||||||
|
|
||||||
|
let item_to_search = match self.import_candidate {
|
||||||
|
ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
|
||||||
|
let canidate_trait = match candidate {
|
||||||
|
Either::Left(module_def) => {
|
||||||
|
module_def.as_assoc_item(db)?.containing_trait(db)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}?;
|
||||||
|
ModuleDef::from(canidate_trait).into()
|
||||||
|
}
|
||||||
|
_ => item,
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(prefix_kind) = prefixed {
|
if let Some(prefix_kind) = prefixed {
|
||||||
self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
|
self.module_with_candidate.find_use_path_prefixed(
|
||||||
|
db,
|
||||||
|
item_to_search,
|
||||||
|
prefix_kind,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
self.module_with_name_to_import.find_use_path(db, item)
|
self.module_with_candidate.find_use_path(db, item_to_search)
|
||||||
}
|
}
|
||||||
.map(|path| (path, item))
|
.map(|path| (path, item))
|
||||||
})
|
})
|
||||||
.filter(|(use_path, _)| use_path.len() > 1)
|
.filter(|(use_path, _)| use_path.len() > 1)
|
||||||
.take(20)
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
res.sort_by_key(|(path, _)| path.clone());
|
res.sort_by_cached_key(|(path, _)| path.clone());
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
|
fn assoc_to_module_def(assoc: AssocItem) -> ModuleDef {
|
||||||
if let AssocItemContainer::Trait(extracted_trait) = assoc {
|
match assoc {
|
||||||
Some(extracted_trait)
|
AssocItem::Function(f) => f.into(),
|
||||||
} else {
|
AssocItem::Const(c) => c.into(),
|
||||||
None
|
AssocItem::TypeAlias(t) => t.into(),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,22 +303,19 @@ impl ImportCandidate {
|
|||||||
match sema.resolve_method_call(method_call) {
|
match sema.resolve_method_call(method_call) {
|
||||||
Some(_) => None,
|
Some(_) => None,
|
||||||
None => Some(Self::TraitMethod(TraitImportCandidate {
|
None => Some(Self::TraitMethod(TraitImportCandidate {
|
||||||
ty: sema.type_of_expr(&method_call.receiver()?)?,
|
receiver_ty: sema.type_of_expr(&method_call.receiver()?)?,
|
||||||
name: method_call.name_ref()?,
|
name: NameToImport::Exact(method_call.name_ref()?.to_string()),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn for_regular_path(
|
fn for_regular_path(sema: &Semantics<RootDatabase>, path: &ast::Path) -> Option<Self> {
|
||||||
sema: &Semantics<RootDatabase>,
|
if sema.resolve_path(path).is_some() {
|
||||||
path_under_caret: &ast::Path,
|
|
||||||
) -> Option<Self> {
|
|
||||||
if sema.resolve_path(path_under_caret).is_some() {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let segment = path_under_caret.segment()?;
|
let segment = path.segment()?;
|
||||||
let candidate = if let Some(qualifier) = path_under_caret.qualifier() {
|
let candidate = if let Some(qualifier) = path.qualifier() {
|
||||||
let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
|
let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
|
||||||
let qualifier_start_path =
|
let qualifier_start_path =
|
||||||
qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
|
qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
|
||||||
@@ -244,8 +328,8 @@ impl ImportCandidate {
|
|||||||
match qualifier_resolution {
|
match qualifier_resolution {
|
||||||
hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
|
hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
|
||||||
ImportCandidate::TraitAssocItem(TraitImportCandidate {
|
ImportCandidate::TraitAssocItem(TraitImportCandidate {
|
||||||
ty: assoc_item_path.ty(sema.db),
|
receiver_ty: assoc_item_path.ty(sema.db),
|
||||||
name: segment.name_ref()?,
|
name: NameToImport::Exact(segment.name_ref()?.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
@@ -253,13 +337,15 @@ impl ImportCandidate {
|
|||||||
} else {
|
} else {
|
||||||
ImportCandidate::Path(PathImportCandidate {
|
ImportCandidate::Path(PathImportCandidate {
|
||||||
qualifier: Some(qualifier),
|
qualifier: Some(qualifier),
|
||||||
name: qualifier_start,
|
name: NameToImport::Exact(qualifier_start.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImportCandidate::Path(PathImportCandidate {
|
ImportCandidate::Path(PathImportCandidate {
|
||||||
qualifier: None,
|
qualifier: None,
|
||||||
name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
|
name: NameToImport::Exact(
|
||||||
|
segment.syntax().descendants().find_map(ast::NameRef::cast)?.to_string(),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
Some(candidate)
|
Some(candidate)
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
//! This module contains an import search functionality that is provided to the assists module.
|
//! This module contains an import search functionality that is provided to the assists module.
|
||||||
//! Later, this should be moved away to a separate crate that is accessible from the assists module.
|
//! Later, this should be moved away to a separate crate that is accessible from the assists module.
|
||||||
|
|
||||||
use hir::{import_map, AsAssocItem, Crate, MacroDef, ModuleDef, Semantics};
|
use hir::{
|
||||||
|
import_map::{self, ImportKind},
|
||||||
|
AsAssocItem, Crate, MacroDef, ModuleDef, Semantics,
|
||||||
|
};
|
||||||
use syntax::{ast, AstNode, SyntaxKind::NAME};
|
use syntax::{ast, AstNode, SyntaxKind::NAME};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -18,9 +21,9 @@ pub fn find_exact_imports<'a>(
|
|||||||
sema: &Semantics<'a, RootDatabase>,
|
sema: &Semantics<'a, RootDatabase>,
|
||||||
krate: Crate,
|
krate: Crate,
|
||||||
name_to_import: String,
|
name_to_import: String,
|
||||||
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
|
) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>>> {
|
||||||
let _p = profile::span("find_exact_imports");
|
let _p = profile::span("find_exact_imports");
|
||||||
find_imports(
|
Box::new(find_imports(
|
||||||
sema,
|
sema,
|
||||||
krate,
|
krate,
|
||||||
{
|
{
|
||||||
@@ -34,47 +37,58 @@ pub fn find_exact_imports<'a>(
|
|||||||
.name_only()
|
.name_only()
|
||||||
.search_mode(import_map::SearchMode::Equals)
|
.search_mode(import_map::SearchMode::Equals)
|
||||||
.case_sensitive(),
|
.case_sensitive(),
|
||||||
)
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AssocItemSearch {
|
||||||
|
Include,
|
||||||
|
Exclude,
|
||||||
|
AssocItemsOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_similar_imports<'a>(
|
pub fn find_similar_imports<'a>(
|
||||||
sema: &Semantics<'a, RootDatabase>,
|
sema: &Semantics<'a, RootDatabase>,
|
||||||
krate: Crate,
|
krate: Crate,
|
||||||
limit: Option<usize>,
|
|
||||||
fuzzy_search_string: String,
|
fuzzy_search_string: String,
|
||||||
ignore_assoc_items: bool,
|
assoc_item_search: AssocItemSearch,
|
||||||
name_only: bool,
|
) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> {
|
||||||
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> + 'a {
|
|
||||||
let _p = profile::span("find_similar_imports");
|
let _p = profile::span("find_similar_imports");
|
||||||
|
|
||||||
let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
|
let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
|
||||||
.search_mode(import_map::SearchMode::Fuzzy);
|
.search_mode(import_map::SearchMode::Fuzzy)
|
||||||
if name_only {
|
.name_only()
|
||||||
external_query = external_query.name_only();
|
.limit(QUERY_SEARCH_LIMIT);
|
||||||
|
|
||||||
|
match assoc_item_search {
|
||||||
|
AssocItemSearch::Include => {}
|
||||||
|
AssocItemSearch::Exclude => {
|
||||||
|
external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem);
|
||||||
|
}
|
||||||
|
AssocItemSearch::AssocItemsOnly => {
|
||||||
|
external_query = external_query.assoc_items_only();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut local_query = symbol_index::Query::new(fuzzy_search_string);
|
let mut local_query = symbol_index::Query::new(fuzzy_search_string);
|
||||||
|
local_query.limit(QUERY_SEARCH_LIMIT);
|
||||||
if let Some(limit) = limit {
|
|
||||||
local_query.limit(limit);
|
|
||||||
external_query = external_query.limit(limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
let db = sema.db;
|
let db = sema.db;
|
||||||
find_imports(sema, krate, local_query, external_query).filter(move |import_candidate| {
|
Box::new(find_imports(sema, krate, local_query, external_query).filter(
|
||||||
if ignore_assoc_items {
|
move |import_candidate| match assoc_item_search {
|
||||||
|
AssocItemSearch::Include => true,
|
||||||
|
AssocItemSearch::Exclude => !is_assoc_item(import_candidate, db),
|
||||||
|
AssocItemSearch::AssocItemsOnly => is_assoc_item(import_candidate, db),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_assoc_item(import_candidate: &Either<ModuleDef, MacroDef>, db: &RootDatabase) -> bool {
|
||||||
match import_candidate {
|
match import_candidate {
|
||||||
Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_none(),
|
Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_some(),
|
||||||
Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_none(),
|
Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_some(),
|
||||||
Either::Left(ModuleDef::TypeAlias(type_alias)) => {
|
Either::Left(ModuleDef::TypeAlias(type_alias)) => type_alias.as_assoc_item(db).is_some(),
|
||||||
type_alias.as_assoc_item(db).is_none()
|
_ => false,
|
||||||
}
|
}
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_imports<'a>(
|
fn find_imports<'a>(
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ impl BenchCmd {
|
|||||||
if is_completion {
|
if is_completion {
|
||||||
let options = CompletionConfig {
|
let options = CompletionConfig {
|
||||||
enable_postfix_completions: true,
|
enable_postfix_completions: true,
|
||||||
enable_autoimport_completions: true,
|
enable_imports_on_the_fly: true,
|
||||||
add_call_parenthesis: true,
|
add_call_parenthesis: true,
|
||||||
add_call_argument_snippets: true,
|
add_call_argument_snippets: true,
|
||||||
snippet_cap: SnippetCap::new(true),
|
snippet_cap: SnippetCap::new(true),
|
||||||
|
|||||||
@@ -559,7 +559,7 @@ impl Config {
|
|||||||
pub fn completion(&self) -> CompletionConfig {
|
pub fn completion(&self) -> CompletionConfig {
|
||||||
CompletionConfig {
|
CompletionConfig {
|
||||||
enable_postfix_completions: self.data.completion_postfix_enable,
|
enable_postfix_completions: self.data.completion_postfix_enable,
|
||||||
enable_autoimport_completions: self.data.completion_autoimport_enable
|
enable_imports_on_the_fly: self.data.completion_autoimport_enable
|
||||||
&& completion_item_edit_resolve(&self.caps),
|
&& completion_item_edit_resolve(&self.caps),
|
||||||
add_call_parenthesis: self.data.completion_addCallParenthesis,
|
add_call_parenthesis: self.data.completion_addCallParenthesis,
|
||||||
add_call_argument_snippets: self.data.completion_addCallArgumentSnippets,
|
add_call_argument_snippets: self.data.completion_addCallArgumentSnippets,
|
||||||
@@ -581,18 +581,7 @@ impl Config {
|
|||||||
AssistConfig {
|
AssistConfig {
|
||||||
snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
|
snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
|
||||||
allowed: None,
|
allowed: None,
|
||||||
insert_use: InsertUseConfig {
|
insert_use: self.insert_use_config(),
|
||||||
merge: match self.data.assist_importMergeBehavior {
|
|
||||||
MergeBehaviorDef::None => None,
|
|
||||||
MergeBehaviorDef::Full => Some(MergeBehavior::Full),
|
|
||||||
MergeBehaviorDef::Last => Some(MergeBehavior::Last),
|
|
||||||
},
|
|
||||||
prefix_kind: match self.data.assist_importPrefix {
|
|
||||||
ImportPrefixDef::Plain => PrefixKind::Plain,
|
|
||||||
ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
|
|
||||||
ImportPrefixDef::BySelf => PrefixKind::BySelf,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn call_info_full(&self) -> bool {
|
pub fn call_info_full(&self) -> bool {
|
||||||
|
|||||||
@@ -653,7 +653,7 @@ pub(crate) fn handle_completion(
|
|||||||
let mut new_completion_items =
|
let mut new_completion_items =
|
||||||
to_proto::completion_item(&line_index, line_endings, item.clone());
|
to_proto::completion_item(&line_index, line_endings, item.clone());
|
||||||
|
|
||||||
if completion_config.enable_autoimport_completions {
|
if completion_config.enable_imports_on_the_fly {
|
||||||
for new_item in &mut new_completion_items {
|
for new_item in &mut new_completion_items {
|
||||||
fill_resolve_data(&mut new_item.data, &item, &text_document_position);
|
fill_resolve_data(&mut new_item.data, &item, &text_document_position);
|
||||||
}
|
}
|
||||||
@@ -703,6 +703,7 @@ pub(crate) fn handle_completion_resolve(
|
|||||||
FilePosition { file_id, offset },
|
FilePosition { file_id, offset },
|
||||||
&resolve_data.full_import_path,
|
&resolve_data.full_import_path,
|
||||||
resolve_data.imported_name,
|
resolve_data.imported_name,
|
||||||
|
resolve_data.import_for_trait_assoc_item,
|
||||||
)?
|
)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|edit| {
|
.flat_map(|edit| {
|
||||||
@@ -1694,6 +1695,7 @@ struct CompletionResolveData {
|
|||||||
position: lsp_types::TextDocumentPositionParams,
|
position: lsp_types::TextDocumentPositionParams,
|
||||||
full_import_path: String,
|
full_import_path: String,
|
||||||
imported_name: String,
|
imported_name: String,
|
||||||
|
import_for_trait_assoc_item: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_resolve_data(
|
fn fill_resolve_data(
|
||||||
@@ -1710,6 +1712,7 @@ fn fill_resolve_data(
|
|||||||
position: position.to_owned(),
|
position: position.to_owned(),
|
||||||
full_import_path,
|
full_import_path,
|
||||||
imported_name,
|
imported_name,
|
||||||
|
import_for_trait_assoc_item: import_edit.import_for_trait_assoc_item,
|
||||||
})
|
})
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -884,7 +884,7 @@ mod tests {
|
|||||||
.completions(
|
.completions(
|
||||||
&ide::CompletionConfig {
|
&ide::CompletionConfig {
|
||||||
enable_postfix_completions: true,
|
enable_postfix_completions: true,
|
||||||
enable_autoimport_completions: true,
|
enable_imports_on_the_fly: true,
|
||||||
add_call_parenthesis: true,
|
add_call_parenthesis: true,
|
||||||
add_call_argument_snippets: true,
|
add_call_argument_snippets: true,
|
||||||
snippet_cap: SnippetCap::new(true),
|
snippet_cap: SnippetCap::new(true),
|
||||||
|
|||||||
Reference in New Issue
Block a user