mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-14 22:24:14 +00:00
Merge #4268
4268: Support auto-import in macro r=SomeoneToIgnore a=edwin0cheng Fixed: #3854 Co-authored-by: Edwin Cheng <edwin0cheng@gmail.com>
This commit is contained in:
commit
6a48a94d47
5 changed files with 66 additions and 19 deletions
|
@ -105,7 +105,7 @@ impl<'a> AssistCtx<'a> {
|
||||||
let mut info = AssistInfo::new(label);
|
let mut info = AssistInfo::new(label);
|
||||||
if self.should_compute_edit {
|
if self.should_compute_edit {
|
||||||
let action = {
|
let action = {
|
||||||
let mut edit = ActionBuilder::default();
|
let mut edit = ActionBuilder::new(&self);
|
||||||
f(&mut edit);
|
f(&mut edit);
|
||||||
edit.build()
|
edit.build()
|
||||||
};
|
};
|
||||||
|
@ -130,6 +130,12 @@ impl<'a> AssistCtx<'a> {
|
||||||
pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
|
pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
|
||||||
find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
|
find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
|
||||||
|
self.sema
|
||||||
|
.find_node_at_offset_with_descend(self.source_file.syntax(), self.frange.range.start())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn covering_element(&self) -> SyntaxElement {
|
pub(crate) fn covering_element(&self) -> SyntaxElement {
|
||||||
find_covering_element(self.source_file.syntax(), self.frange.range)
|
find_covering_element(self.source_file.syntax(), self.frange.range)
|
||||||
}
|
}
|
||||||
|
@ -156,7 +162,7 @@ impl<'a> AssistGroup<'a> {
|
||||||
let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone()));
|
let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone()));
|
||||||
if self.ctx.should_compute_edit {
|
if self.ctx.should_compute_edit {
|
||||||
let action = {
|
let action = {
|
||||||
let mut edit = ActionBuilder::default();
|
let mut edit = ActionBuilder::new(&self.ctx);
|
||||||
f(&mut edit);
|
f(&mut edit);
|
||||||
edit.build()
|
edit.build()
|
||||||
};
|
};
|
||||||
|
@ -175,15 +181,29 @@ impl<'a> AssistGroup<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
pub(crate) struct ActionBuilder<'a, 'b> {
|
||||||
pub(crate) struct ActionBuilder {
|
|
||||||
edit: TextEditBuilder,
|
edit: TextEditBuilder,
|
||||||
cursor_position: Option<TextSize>,
|
cursor_position: Option<TextSize>,
|
||||||
target: Option<TextRange>,
|
target: Option<TextRange>,
|
||||||
file: AssistFile,
|
file: AssistFile,
|
||||||
|
ctx: &'a AssistCtx<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> ActionBuilder<'a, 'b> {
|
||||||
|
fn new(ctx: &'a AssistCtx<'b>) -> Self {
|
||||||
|
Self {
|
||||||
|
edit: TextEditBuilder::default(),
|
||||||
|
cursor_position: None,
|
||||||
|
target: None,
|
||||||
|
file: AssistFile::default(),
|
||||||
|
ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ctx(&self) -> &AssistCtx<'b> {
|
||||||
|
&self.ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActionBuilder {
|
|
||||||
/// Replaces specified `range` of text with a given string.
|
/// Replaces specified `range` of text with a given string.
|
||||||
pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
|
pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
|
||||||
self.edit.replace(range, replace_with.into())
|
self.edit.replace(range, replace_with.into())
|
||||||
|
|
|
@ -45,15 +45,12 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
|
||||||
let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message());
|
let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message());
|
||||||
for import in proposed_imports {
|
for import in proposed_imports {
|
||||||
group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| {
|
group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| {
|
||||||
edit.target(auto_import_assets.syntax_under_caret.text_range());
|
edit.target(range);
|
||||||
insert_use_statement(
|
insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit);
|
||||||
&auto_import_assets.syntax_under_caret,
|
|
||||||
&import,
|
|
||||||
edit.text_edit_builder(),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
group.finish()
|
group.finish()
|
||||||
|
@ -68,10 +65,10 @@ struct AutoImportAssets {
|
||||||
|
|
||||||
impl AutoImportAssets {
|
impl AutoImportAssets {
|
||||||
fn new(ctx: &AssistCtx) -> Option<Self> {
|
fn new(ctx: &AssistCtx) -> Option<Self> {
|
||||||
if let Some(path_under_caret) = ctx.find_node_at_offset::<ast::Path>() {
|
if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
|
||||||
Self::for_regular_path(path_under_caret, &ctx)
|
Self::for_regular_path(path_under_caret, &ctx)
|
||||||
} else {
|
} else {
|
||||||
Self::for_method_call(ctx.find_node_at_offset()?, &ctx)
|
Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,6 +302,35 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn applicable_when_found_an_import_in_macros() {
|
||||||
|
check_assist(
|
||||||
|
auto_import,
|
||||||
|
r"
|
||||||
|
macro_rules! foo {
|
||||||
|
($i:ident) => { fn foo(a: $i) {} }
|
||||||
|
}
|
||||||
|
foo!(Pub<|>Struct);
|
||||||
|
|
||||||
|
pub mod PubMod {
|
||||||
|
pub struct PubStruct;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r"
|
||||||
|
use PubMod::PubStruct;
|
||||||
|
|
||||||
|
macro_rules! foo {
|
||||||
|
($i:ident) => { fn foo(a: $i) {} }
|
||||||
|
}
|
||||||
|
foo!(Pub<|>Struct);
|
||||||
|
|
||||||
|
pub mod PubMod {
|
||||||
|
pub struct PubStruct;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn auto_imports_are_merged() {
|
fn auto_imports_are_merged() {
|
||||||
check_assist(
|
check_assist(
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist>
|
||||||
"Replace qualified path with use",
|
"Replace qualified path with use",
|
||||||
|edit| {
|
|edit| {
|
||||||
let path_to_import = hir_path.mod_path().clone();
|
let path_to_import = hir_path.mod_path().clone();
|
||||||
insert_use_statement(path.syntax(), &path_to_import, edit.text_edit_builder());
|
insert_use_statement(path.syntax(), &path_to_import, edit);
|
||||||
|
|
||||||
if let Some(last) = path.segment() {
|
if let Some(last) = path.segment() {
|
||||||
// Here we are assuming the assist will provide a correct use statement
|
// Here we are assuming the assist will provide a correct use statement
|
||||||
|
|
|
@ -11,7 +11,7 @@ use ra_syntax::{
|
||||||
};
|
};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
pub use insert_use::insert_use_statement;
|
pub(crate) use insert_use::insert_use_statement;
|
||||||
|
|
||||||
pub fn get_missing_impl_items(
|
pub fn get_missing_impl_items(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// FIXME: rewrite according to the plan, outlined in
|
// FIXME: rewrite according to the plan, outlined in
|
||||||
// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553
|
// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553
|
||||||
|
|
||||||
|
use crate::assist_ctx::ActionBuilder;
|
||||||
use hir::{self, ModPath};
|
use hir::{self, ModPath};
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
ast::{self, NameOwner},
|
ast::{self, NameOwner},
|
||||||
|
@ -14,14 +15,14 @@ use ra_text_edit::TextEditBuilder;
|
||||||
/// Creates and inserts a use statement for the given path to import.
|
/// Creates and inserts a use statement for the given path to import.
|
||||||
/// The use statement is inserted in the scope most appropriate to the
|
/// The use statement is inserted in the scope most appropriate to the
|
||||||
/// the cursor position given, additionally merged with the existing use imports.
|
/// the cursor position given, additionally merged with the existing use imports.
|
||||||
pub fn insert_use_statement(
|
pub(crate) fn insert_use_statement(
|
||||||
// Ideally the position of the cursor, used to
|
// Ideally the position of the cursor, used to
|
||||||
position: &SyntaxNode,
|
position: &SyntaxNode,
|
||||||
path_to_import: &ModPath,
|
path_to_import: &ModPath,
|
||||||
edit: &mut TextEditBuilder,
|
edit: &mut ActionBuilder,
|
||||||
) {
|
) {
|
||||||
let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
|
let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
|
||||||
let container = position.ancestors().find_map(|n| {
|
let container = edit.ctx().sema.ancestors_with_macros(position.clone()).find_map(|n| {
|
||||||
if let Some(module) = ast::Module::cast(n.clone()) {
|
if let Some(module) = ast::Module::cast(n.clone()) {
|
||||||
return module.item_list().map(|it| it.syntax().clone());
|
return module.item_list().map(|it| it.syntax().clone());
|
||||||
}
|
}
|
||||||
|
@ -30,7 +31,7 @@ pub fn insert_use_statement(
|
||||||
|
|
||||||
if let Some(container) = container {
|
if let Some(container) = container {
|
||||||
let action = best_action_for_target(container, position.clone(), &target);
|
let action = best_action_for_target(container, position.clone(), &target);
|
||||||
make_assist(&action, &target, edit);
|
make_assist(&action, &target, edit.text_edit_builder());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue