Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-assists/src/handlers/auto_import.rs260
-rw-r--r--crates/ide-db/src/imports/insert_use.rs28
2 files changed, 276 insertions, 12 deletions
diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs
index 7acf2ea0a0..cafd57a977 100644
--- a/crates/ide-assists/src/handlers/auto_import.rs
+++ b/crates/ide-assists/src/handlers/auto_import.rs
@@ -5,7 +5,7 @@ use ide_db::{
helpers::mod_path_to_ast,
imports::{
import_assets::{ImportAssets, ImportCandidate, LocatedImport},
- insert_use::{insert_use, ImportScope},
+ insert_use::{insert_use, insert_use_as_alias, ImportScope},
},
};
use syntax::{ast, AstNode, NodeOrToken, SyntaxElement};
@@ -129,10 +129,12 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
for import in proposed_imports {
let import_path = import.import_path;
+ let (assist_id, import_name) =
+ (AssistId("auto_import", AssistKind::QuickFix), import_path.display(ctx.db()));
acc.add_group(
&group_label,
- AssistId("auto_import", AssistKind::QuickFix),
- format!("Import `{}`", import_path.display(ctx.db())),
+ assist_id,
+ format!("Import `{}`", import_name),
range,
|builder| {
let scope = match scope.clone() {
@@ -143,6 +145,38 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
insert_use(&scope, mod_path_to_ast(&import_path), &ctx.config.insert_use);
},
);
+
+ match import_assets.import_candidate() {
+ ImportCandidate::TraitAssocItem(name) | ImportCandidate::TraitMethod(name) => {
+ let is_method =
+ matches!(import_assets.import_candidate(), ImportCandidate::TraitMethod(_));
+ let type_ = if is_method { "method" } else { "item" };
+ let group_label = GroupLabel(format!(
+ "Import a trait for {} {} by alias",
+ type_,
+ name.assoc_item_name.text()
+ ));
+ acc.add_group(
+ &group_label,
+ assist_id,
+ format!("Import `{} as _`", import_name),
+ range,
+ |builder| {
+ let scope = match scope.clone() {
+ ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
+ ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
+ ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
+ };
+ insert_use_as_alias(
+ &scope,
+ mod_path_to_ast(&import_path),
+ &ctx.config.insert_use,
+ );
+ },
+ );
+ }
+ _ => {}
+ }
}
Some(())
}
@@ -253,7 +287,8 @@ mod tests {
};
use crate::tests::{
- check_assist, check_assist_not_applicable, check_assist_target, TEST_CONFIG,
+ check_assist, check_assist_by_label, check_assist_not_applicable, check_assist_target,
+ TEST_CONFIG,
};
fn check_auto_import_order(before: &str, order: &[&str]) {
@@ -705,7 +740,7 @@ fn main() {
#[test]
fn associated_trait_function() {
- check_assist(
+ check_assist_by_label(
auto_import,
r"
mod test_mod {
@@ -739,6 +774,44 @@ fn main() {
test_mod::TestStruct::test_function
}
",
+ "Import `test_mod::TestTrait`",
+ );
+
+ check_assist_by_label(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::test_function$0
+ }
+ ",
+ r"
+ use test_mod::TestTrait as _;
+
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::test_function
+ }
+ ",
+ "Import `test_mod::TestTrait as _`",
);
}
@@ -776,7 +849,44 @@ fn main() {
#[test]
fn associated_trait_const() {
- check_assist(
+ check_assist_by_label(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::TEST_CONST$0
+ }
+ ",
+ r"
+ use test_mod::TestTrait as _;
+
+ mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::TEST_CONST
+ }
+ ",
+ "Import `test_mod::TestTrait as _`",
+ );
+
+ check_assist_by_label(
auto_import,
r"
mod test_mod {
@@ -810,6 +920,7 @@ fn main() {
test_mod::TestStruct::TEST_CONST
}
",
+ "Import `test_mod::TestTrait`",
);
}
@@ -847,7 +958,46 @@ fn main() {
#[test]
fn trait_method() {
- check_assist(
+ check_assist_by_label(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+ }
+
+ fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+ }
+ ",
+ r"
+ use test_mod::TestTrait as _;
+
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+ }
+
+ fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_method()
+ }
+ ",
+ "Import `test_mod::TestTrait as _`",
+ );
+
+ check_assist_by_label(
auto_import,
r"
mod test_mod {
@@ -883,12 +1033,43 @@ fn main() {
test_struct.test_method()
}
",
+ "Import `test_mod::TestTrait`",
);
}
#[test]
fn trait_method_cross_crate() {
- check_assist(
+ check_assist_by_label(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+ }
+ ",
+ r"
+ use dep::test_mod::TestTrait as _;
+
+ fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_method()
+ }
+ ",
+ "Import `dep::test_mod::TestTrait as _`",
+ );
+
+ check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
@@ -915,12 +1096,41 @@ fn main() {
test_struct.test_method()
}
",
+ "Import `dep::test_mod::TestTrait`",
);
}
#[test]
fn assoc_fn_cross_crate() {
- check_assist(
+ check_assist_by_label(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ dep::test_mod::TestStruct::test_func$0tion
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+ }
+ ",
+ r"
+ use dep::test_mod::TestTrait as _;
+
+ fn main() {
+ dep::test_mod::TestStruct::test_function
+ }
+ ",
+ "Import `dep::test_mod::TestTrait as _`",
+ );
+
+ check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
@@ -945,12 +1155,41 @@ fn main() {
dep::test_mod::TestStruct::test_function
}
",
+ "Import `dep::test_mod::TestTrait`",
);
}
#[test]
fn assoc_const_cross_crate() {
- check_assist(
+ check_assist_by_label(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ dep::test_mod::TestStruct::CONST$0
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ const CONST: bool;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const CONST: bool = true;
+ }
+ }
+ ",
+ r"
+ use dep::test_mod::TestTrait as _;
+
+ fn main() {
+ dep::test_mod::TestStruct::CONST
+ }
+ ",
+ "Import `dep::test_mod::TestTrait as _`",
+ );
+
+ check_assist_by_label(
auto_import,
r"
//- /main.rs crate:main deps:dep
@@ -975,6 +1214,7 @@ fn main() {
dep::test_mod::TestStruct::CONST
}
",
+ "Import `dep::test_mod::TestTrait`",
);
}
diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs
index 9be1d36634..a0cfd3836d 100644
--- a/crates/ide-db/src/imports/insert_use.rs
+++ b/crates/ide-db/src/imports/insert_use.rs
@@ -9,7 +9,7 @@ use syntax::{
algo,
ast::{
self, edit_in_place::Removable, make, AstNode, HasAttrs, HasModuleItem, HasVisibility,
- PathSegmentKind,
+ PathSegmentKind, UseTree,
},
ted, Direction, NodeOrToken, SyntaxKind, SyntaxNode,
};
@@ -157,6 +157,29 @@ impl ImportScope {
/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
+ insert_use_with_alias_option(scope, path, cfg, None);
+}
+
+pub fn insert_use_as_alias(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
+ let text: &str = "use foo as _";
+ let parse = syntax::SourceFile::parse(text);
+ let node = parse
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(UseTree::cast)
+ .expect("Failed to make ast node `Rename`");
+ let alias = node.rename();
+
+ insert_use_with_alias_option(scope, path, cfg, alias);
+}
+
+fn insert_use_with_alias_option(
+ scope: &ImportScope,
+ path: ast::Path,
+ cfg: &InsertUseConfig,
+ alias: Option<ast::Rename>,
+) {
let _p = profile::span("insert_use");
let mut mb = match cfg.granularity {
ImportGranularity::Crate => Some(MergeBehavior::Crate),
@@ -176,7 +199,8 @@ pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
}
let use_item =
- make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
+ make::use_(None, make::use_tree(path.clone(), None, alias, false)).clone_for_update();
+
// merge into existing imports if possible
if let Some(mb) = mb {
let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));