From 561b4b11ff1d87ea1ff2477dcba6ae1f396573a3 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 7 Feb 2020 15:53:31 +0100 Subject: Name assist handlers --- crates/ra_assists/src/handlers/auto_import.rs | 258 ++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 crates/ra_assists/src/handlers/auto_import.rs (limited to 'crates/ra_assists/src/handlers/auto_import.rs') diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs new file mode 100644 index 000000000..84b5474f9 --- /dev/null +++ b/crates/ra_assists/src/handlers/auto_import.rs @@ -0,0 +1,258 @@ +use hir::ModPath; +use ra_ide_db::imports_locator::ImportsLocator; +use ra_syntax::{ + ast::{self, AstNode}, + SyntaxNode, +}; + +use crate::{ + assist_ctx::{ActionBuilder, Assist, AssistCtx}, + auto_import_text_edit, AssistId, +}; +use std::collections::BTreeSet; + +// Assist: auto_import +// +// If the name is unresolved, provides all possible imports for it. +// +// ``` +// fn main() { +// let map = HashMap<|>::new(); +// } +// # pub mod std { pub mod collections { pub struct HashMap { } } } +// ``` +// -> +// ``` +// use std::collections::HashMap; +// +// fn main() { +// let map = HashMap::new(); +// } +// # pub mod std { pub mod collections { pub struct HashMap { } } } +// ``` +pub(crate) fn auto_import(ctx: AssistCtx) -> Option { + let path_to_import: ast::Path = ctx.find_node_at_offset()?; + let path_to_import_syntax = path_to_import.syntax(); + if path_to_import_syntax.ancestors().find_map(ast::UseItem::cast).is_some() { + return None; + } + let name_to_import = + path_to_import_syntax.descendants().find_map(ast::NameRef::cast)?.syntax().to_string(); + + let module = path_to_import_syntax.ancestors().find_map(ast::Module::cast); + let position = match module.and_then(|it| it.item_list()) { + Some(item_list) => item_list.syntax().clone(), + None => { + let current_file = path_to_import_syntax.ancestors().find_map(ast::SourceFile::cast)?; + current_file.syntax().clone() + } + }; + let source_analyzer = ctx.source_analyzer(&position, None); + let module_with_name_to_import = source_analyzer.module()?; + if source_analyzer.resolve_path(ctx.db, &path_to_import).is_some() { + return None; + } + + let mut imports_locator = ImportsLocator::new(ctx.db); + + let proposed_imports = imports_locator + .find_imports(&name_to_import) + .into_iter() + .filter_map(|module_def| module_with_name_to_import.find_use_path(ctx.db, module_def)) + .filter(|use_path| !use_path.segments.is_empty()) + .take(20) + .collect::>(); + + if proposed_imports.is_empty() { + return None; + } + + ctx.add_assist_group(AssistId("auto_import"), format!("Import {}", name_to_import), || { + proposed_imports + .into_iter() + .map(|import| import_to_action(import, &position, &path_to_import_syntax)) + .collect() + }) +} + +fn import_to_action(import: ModPath, position: &SyntaxNode, anchor: &SyntaxNode) -> ActionBuilder { + let mut action_builder = ActionBuilder::default(); + action_builder.label(format!("Import `{}`", &import)); + auto_import_text_edit(position, anchor, &import, action_builder.text_edit_builder()); + action_builder +} + +#[cfg(test)] +mod tests { + use crate::helpers::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn applicable_when_found_an_import() { + check_assist( + auto_import, + r" + <|>PubStruct + + pub mod PubMod { + pub struct PubStruct; + } + ", + r" + <|>use PubMod::PubStruct; + + PubStruct + + pub mod PubMod { + pub struct PubStruct; + } + ", + ); + } + + #[test] + fn auto_imports_are_merged() { + check_assist( + auto_import, + r" + use PubMod::PubStruct1; + + struct Test { + test: Pub<|>Struct2, + } + + pub mod PubMod { + pub struct PubStruct1; + pub struct PubStruct2 { + _t: T, + } + } + ", + r" + use PubMod::{PubStruct2, PubStruct1}; + + struct Test { + test: Pub<|>Struct2, + } + + pub mod PubMod { + pub struct PubStruct1; + pub struct PubStruct2 { + _t: T, + } + } + ", + ); + } + + #[test] + fn applicable_when_found_multiple_imports() { + check_assist( + auto_import, + r" + PubSt<|>ruct + + pub mod PubMod1 { + pub struct PubStruct; + } + pub mod PubMod2 { + pub struct PubStruct; + } + pub mod PubMod3 { + pub struct PubStruct; + } + ", + r" + use PubMod1::PubStruct; + + PubSt<|>ruct + + pub mod PubMod1 { + pub struct PubStruct; + } + pub mod PubMod2 { + pub struct PubStruct; + } + pub mod PubMod3 { + pub struct PubStruct; + } + ", + ); + } + + #[test] + fn not_applicable_for_already_imported_types() { + check_assist_not_applicable( + auto_import, + r" + use PubMod::PubStruct; + + PubStruct<|> + + pub mod PubMod { + pub struct PubStruct; + } + ", + ); + } + + #[test] + fn not_applicable_for_types_with_private_paths() { + check_assist_not_applicable( + auto_import, + r" + PrivateStruct<|> + + pub mod PubMod { + struct PrivateStruct; + } + ", + ); + } + + #[test] + fn not_applicable_when_no_imports_found() { + check_assist_not_applicable( + auto_import, + " + PubStruct<|>", + ); + } + + #[test] + fn not_applicable_in_import_statements() { + check_assist_not_applicable( + auto_import, + r" + use PubStruct<|>; + + pub mod PubMod { + pub struct PubStruct; + }", + ); + } + + #[test] + fn function_import() { + check_assist( + auto_import, + r" + test_function<|> + + pub mod PubMod { + pub fn test_function() {}; + } + ", + r" + use PubMod::test_function; + + test_function<|> + + pub mod PubMod { + pub fn test_function() {}; + } + ", + ); + } +} -- cgit v1.2.3 From 740a26b7d26a68cc46becda3cca39091e8da67fc Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 7 Feb 2020 23:35:34 +0200 Subject: Rename add import assist --- crates/ra_assists/src/handlers/auto_import.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'crates/ra_assists/src/handlers/auto_import.rs') diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index 84b5474f9..4514b8691 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs @@ -7,7 +7,7 @@ use ra_syntax::{ use crate::{ assist_ctx::{ActionBuilder, Assist, AssistCtx}, - auto_import_text_edit, AssistId, + insert_use_statement, AssistId, }; use std::collections::BTreeSet; @@ -78,7 +78,7 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option { fn import_to_action(import: ModPath, position: &SyntaxNode, anchor: &SyntaxNode) -> ActionBuilder { let mut action_builder = ActionBuilder::default(); action_builder.label(format!("Import `{}`", &import)); - auto_import_text_edit(position, anchor, &import, action_builder.text_edit_builder()); + insert_use_statement(position, anchor, &import, action_builder.text_edit_builder()); action_builder } -- cgit v1.2.3 From fb99831cb044e5fbf171bdd950a7486a01ce0d51 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 9 Feb 2020 14:30:27 +0100 Subject: Slightly simpler API for groups --- crates/ra_assists/src/handlers/auto_import.rs | 33 ++++++++++++--------------- 1 file changed, 14 insertions(+), 19 deletions(-) (limited to 'crates/ra_assists/src/handlers/auto_import.rs') diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index 4514b8691..d13332f37 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs @@ -1,12 +1,8 @@ -use hir::ModPath; use ra_ide_db::imports_locator::ImportsLocator; -use ra_syntax::{ - ast::{self, AstNode}, - SyntaxNode, -}; +use ra_syntax::ast::{self, AstNode}; use crate::{ - assist_ctx::{ActionBuilder, Assist, AssistCtx}, + assist_ctx::{Assist, AssistCtx}, insert_use_statement, AssistId, }; use std::collections::BTreeSet; @@ -67,19 +63,18 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option { return None; } - ctx.add_assist_group(AssistId("auto_import"), format!("Import {}", name_to_import), || { - proposed_imports - .into_iter() - .map(|import| import_to_action(import, &position, &path_to_import_syntax)) - .collect() - }) -} - -fn import_to_action(import: ModPath, position: &SyntaxNode, anchor: &SyntaxNode) -> ActionBuilder { - let mut action_builder = ActionBuilder::default(); - action_builder.label(format!("Import `{}`", &import)); - insert_use_statement(position, anchor, &import, action_builder.text_edit_builder()); - action_builder + let mut group = ctx.add_assist_group(format!("Import {}", name_to_import)); + for import in proposed_imports { + group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { + insert_use_statement( + &position, + path_to_import_syntax, + &import, + edit.text_edit_builder(), + ); + }); + } + group.finish() } #[cfg(test)] -- cgit v1.2.3 From fe141a8c10eb31c8793a6f6ecec522011d59a653 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 9 Feb 2020 16:13:29 +0100 Subject: Set auto-import target closes #3067 --- crates/ra_assists/src/handlers/auto_import.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'crates/ra_assists/src/handlers/auto_import.rs') diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index d13332f37..e88c121eb 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs @@ -66,6 +66,7 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option { let mut group = ctx.add_assist_group(format!("Import {}", name_to_import)); for import in proposed_imports { group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { + edit.target(path_to_import_syntax.text_range()); insert_use_statement( &position, path_to_import_syntax, @@ -79,7 +80,7 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option { #[cfg(test)] mod tests { - use crate::helpers::{check_assist, check_assist_not_applicable}; + use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; use super::*; @@ -250,4 +251,19 @@ mod tests { ", ); } + + #[test] + fn auto_import_target() { + check_assist_target( + auto_import, + r" + struct AssistInfo { + group_label: Option<<|>GroupLabel>, + } + + mod m { pub struct GroupLabel; } + ", + "GroupLabel", + ) + } } -- cgit v1.2.3 From 48abcaaabe5cba0cfbada902652c97146a047372 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 9 Feb 2020 01:56:17 +0200 Subject: Do not import anything if first segment of FQN resolves --- crates/ra_assists/src/handlers/auto_import.rs | 48 ++++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) (limited to 'crates/ra_assists/src/handlers/auto_import.rs') diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index e88c121eb..1081c9a5b 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs @@ -27,31 +27,34 @@ use std::collections::BTreeSet; // # pub mod std { pub mod collections { pub struct HashMap { } } } // ``` pub(crate) fn auto_import(ctx: AssistCtx) -> Option { - let path_to_import: ast::Path = ctx.find_node_at_offset()?; - let path_to_import_syntax = path_to_import.syntax(); - if path_to_import_syntax.ancestors().find_map(ast::UseItem::cast).is_some() { + let path_under_caret: ast::Path = ctx.find_node_at_offset()?; + if path_under_caret.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { return None; } - let name_to_import = - path_to_import_syntax.descendants().find_map(ast::NameRef::cast)?.syntax().to_string(); - let module = path_to_import_syntax.ancestors().find_map(ast::Module::cast); + let module = path_under_caret.syntax().ancestors().find_map(ast::Module::cast); let position = match module.and_then(|it| it.item_list()) { Some(item_list) => item_list.syntax().clone(), None => { - let current_file = path_to_import_syntax.ancestors().find_map(ast::SourceFile::cast)?; + let current_file = + path_under_caret.syntax().ancestors().find_map(ast::SourceFile::cast)?; current_file.syntax().clone() } }; let source_analyzer = ctx.source_analyzer(&position, None); let module_with_name_to_import = source_analyzer.module()?; - if source_analyzer.resolve_path(ctx.db, &path_to_import).is_some() { + + let name_ref_to_import = + path_under_caret.syntax().descendants().find_map(ast::NameRef::cast)?; + if source_analyzer + .resolve_path(ctx.db, &name_ref_to_import.syntax().ancestors().find_map(ast::Path::cast)?) + .is_some() + { return None; } - let mut imports_locator = ImportsLocator::new(ctx.db); - - let proposed_imports = imports_locator + let name_to_import = name_ref_to_import.syntax().to_string(); + let proposed_imports = ImportsLocator::new(ctx.db) .find_imports(&name_to_import) .into_iter() .filter_map(|module_def| module_with_name_to_import.find_use_path(ctx.db, module_def)) @@ -69,7 +72,7 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option { edit.target(path_to_import_syntax.text_range()); insert_use_statement( &position, - path_to_import_syntax, + &path_under_caret.syntax(), &import, edit.text_edit_builder(), ); @@ -266,4 +269,25 @@ mod tests { "GroupLabel", ) } + + #[test] + fn not_applicable_when_path_start_is_imported() { + check_assist_not_applicable( + auto_import, + r" + pub mod mod1 { + pub mod mod2 { + pub mod mod3 { + pub struct TestStruct; + } + } + } + + use mod1::mod2; + fn main() { + mod2::mod3::TestStruct<|> + } + ", + ); + } } -- cgit v1.2.3 From d39d4016121d19c77e3d8c417a5bcc58a3b06530 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 9 Feb 2020 17:25:05 +0200 Subject: Fix rebase leftovers --- crates/ra_assists/src/handlers/auto_import.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'crates/ra_assists/src/handlers/auto_import.rs') diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index 1081c9a5b..1fb701da5 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs @@ -69,10 +69,10 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option { let mut group = ctx.add_assist_group(format!("Import {}", name_to_import)); for import in proposed_imports { group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { - edit.target(path_to_import_syntax.text_range()); + edit.target(path_under_caret.syntax().text_range()); insert_use_statement( &position, - &path_under_caret.syntax(), + path_under_caret.syntax(), &import, edit.text_edit_builder(), ); -- cgit v1.2.3