Skip to content

Commit a7d440e

Browse files
bors[bot]feniljain
andauthored
Merge #11662
11662: fix: extract_module selection inside impl r=Veykril a=feniljain Should close: #11508 From issue: Concern 1: Seems to be fixed in latest `rust-analyzer` build Concern 2 and 3: Should be fixed by this PR Concern 4: Got fixed in #11472 Points to note: - Here I have seperated use items and other items, this is becuase the new `impl` block which we will be creating cannot contain use items as immediate children. As they are the only one item that can be generated by our assist, so seperating them helps in handling their inclusion in new `impl` block inside new `module` - There's also a new method added which helps in removing remaning left over indentation after removing `impl` or other `item` Co-authored-by: vi_mi <[email protected]>
2 parents 5b51cb8 + 5789caf commit a7d440e

File tree

1 file changed

+167
-14
lines changed

1 file changed

+167
-14
lines changed

crates/ide_assists/src/handlers/extract_module.rs

+167-14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use ide_db::{
77
defs::{Definition, NameClass, NameRefClass},
88
search::{FileReference, SearchScope},
99
};
10+
use parser::SyntaxKind::WHITESPACE;
1011
use stdx::format_to;
1112
use syntax::{
1213
algo::find_node_at_range,
@@ -59,6 +60,20 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext) -> Option<(
5960
syntax::NodeOrToken::Token(t) => t.parent()?,
6061
};
6162

63+
//If the selection is inside impl block, we need to place new module outside impl block,
64+
//as impl blocks cannot contain modules
65+
66+
let mut impl_parent: Option<ast::Impl> = None;
67+
let mut impl_child_count: usize = 0;
68+
if let Some(parent_assoc_list) = node.parent() {
69+
if let Some(parent_impl) = parent_assoc_list.parent() {
70+
if let Some(impl_) = ast::Impl::cast(parent_impl) {
71+
impl_child_count = parent_assoc_list.children().count();
72+
impl_parent = Some(impl_);
73+
}
74+
}
75+
}
76+
6277
let mut curr_parent_module: Option<ast::Module> = None;
6378
if let Some(mod_syn_opt) = node.ancestors().find(|it| ast::Module::can_cast(it.kind())) {
6479
curr_parent_module = ast::Module::cast(mod_syn_opt);
@@ -98,18 +113,55 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext) -> Option<(
98113
"Extract Module",
99114
module.text_range,
100115
|builder| {
101-
let _ = &module;
116+
let mut body_items: Vec<String> = Vec::new();
117+
let mut items_to_be_processed: Vec<ast::Item> = module.body_items.clone();
118+
let mut new_item_indent = old_item_indent + 1;
102119

103-
let mut body_items = Vec::new();
104-
let new_item_indent = old_item_indent + 1;
105-
for item in module.body_items {
120+
if impl_parent.is_some() {
121+
new_item_indent = old_item_indent + 2;
122+
} else {
123+
items_to_be_processed = [module.use_items.clone(), items_to_be_processed].concat();
124+
}
125+
126+
for item in items_to_be_processed {
106127
let item = item.indent(IndentLevel(1));
107128
let mut indented_item = String::new();
108129
format_to!(indented_item, "{}{}", new_item_indent, item.to_string());
109130
body_items.push(indented_item);
110131
}
111132

112-
let body = body_items.join("\n\n");
133+
let mut body = body_items.join("\n\n");
134+
135+
if let Some(impl_) = &impl_parent {
136+
let mut impl_body_def = String::new();
137+
138+
if let Some(self_ty) = impl_.self_ty() {
139+
format_to!(
140+
impl_body_def,
141+
"{}impl {} {{\n{}\n{}}}",
142+
old_item_indent + 1,
143+
self_ty.to_string(),
144+
body,
145+
old_item_indent + 1
146+
);
147+
148+
body = impl_body_def;
149+
150+
// Add the import for enum/struct corresponding to given impl block
151+
if let Some(_) = module.make_use_stmt_of_node_with_super(self_ty.syntax()) {
152+
for item in module.use_items {
153+
let mut indented_item = String::new();
154+
format_to!(
155+
indented_item,
156+
"{}{}",
157+
old_item_indent + 1,
158+
item.to_string()
159+
);
160+
body = format!("{}\n\n{}", indented_item, body);
161+
}
162+
}
163+
}
164+
}
113165

114166
let mut module_def = String::new();
115167

@@ -135,7 +187,29 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext) -> Option<(
135187
for import_path_text_range in import_paths_to_be_removed {
136188
builder.delete(import_path_text_range);
137189
}
138-
builder.replace(module.text_range, module_def)
190+
191+
if let Some(impl_) = impl_parent {
192+
let node_to_be_removed;
193+
194+
// Remove complete impl block if it has only one child (as such it will be empty
195+
// after deleting that child)
196+
if impl_child_count == 1 {
197+
node_to_be_removed = impl_.syntax()
198+
} else {
199+
//Remove selected node
200+
node_to_be_removed = &node;
201+
}
202+
203+
builder.delete(node_to_be_removed.text_range());
204+
// Remove preceding indentation from node
205+
if let Some(range) = indent_range_before_given_node(&node_to_be_removed) {
206+
builder.delete(range);
207+
}
208+
209+
builder.insert(impl_.syntax().text_range().end(), format!("\n\n{}", module_def));
210+
} else {
211+
builder.replace(module.text_range, module_def)
212+
}
139213
},
140214
)
141215
}
@@ -144,16 +218,24 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext) -> Option<(
144218
struct Module {
145219
text_range: TextRange,
146220
name: String,
147-
body_items: Vec<ast::Item>,
221+
body_items: Vec<ast::Item>, // All items except use items
222+
use_items: Vec<ast::Item>, // Use items are kept separately as they help when the selection is inside an impl block, we can directly take these items and keep them outside generated impl block inside generated module
148223
}
149224

150225
fn extract_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Module> {
226+
let mut use_items = vec![];
227+
151228
let mut body_items: Vec<ast::Item> = node
152229
.children()
153230
.filter_map(|child| {
154-
if let Some(item) = ast::Item::cast(child) {
155-
if selection_range.contains_range(item.syntax().text_range()) {
156-
return Some(item);
231+
if selection_range.contains_range(child.text_range()) {
232+
let child_kind = child.kind();
233+
if let Some(item) = ast::Item::cast(child) {
234+
if ast::Use::can_cast(child_kind) {
235+
use_items.push(item);
236+
} else {
237+
return Some(item);
238+
}
157239
}
158240
return None;
159241
}
@@ -165,7 +247,7 @@ fn extract_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Modul
165247
body_items.push(node_item);
166248
}
167249

168-
Some(Module { text_range: selection_range, name: "modname".to_string(), body_items })
250+
Some(Module { text_range: selection_range, name: "modname".to_string(), body_items, use_items })
169251
}
170252

171253
impl Module {
@@ -543,23 +625,27 @@ impl Module {
543625
let use_ =
544626
make::use_(None, make::use_tree(make::join_paths(use_tree_str), None, None, false));
545627
if let Some(item) = ast::Item::cast(use_.syntax().clone()) {
546-
self.body_items.insert(0, item);
628+
self.use_items.insert(0, item);
547629
}
548630
}
549631

550632
import_path_to_be_removed
551633
}
552634

553-
fn make_use_stmt_of_node_with_super(&mut self, node_syntax: &SyntaxNode) {
635+
fn make_use_stmt_of_node_with_super(&mut self, node_syntax: &SyntaxNode) -> Option<ast::Item> {
554636
let super_path = make::ext::ident_path("super");
555637
let node_path = make::ext::ident_path(&node_syntax.to_string());
556638
let use_ = make::use_(
557639
None,
558640
make::use_tree(make::join_paths(vec![super_path, node_path]), None, None, false),
559641
);
642+
560643
if let Some(item) = ast::Item::cast(use_.syntax().clone()) {
561-
self.body_items.insert(0, item);
644+
self.use_items.insert(0, item.clone());
645+
return Some(item);
562646
}
647+
648+
return None;
563649
}
564650

565651
fn process_use_stmt_for_import_resolve(
@@ -859,6 +945,14 @@ fn compare_hir_and_ast_module(
859945
return Some(());
860946
}
861947

948+
fn indent_range_before_given_node(node: &SyntaxNode) -> Option<TextRange> {
949+
let x = node.siblings_with_tokens(syntax::Direction::Prev).find(|x| {
950+
return x.kind() == WHITESPACE;
951+
})?;
952+
953+
return Some(x.text_range());
954+
}
955+
862956
#[cfg(test)]
863957
mod tests {
864958
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -1435,4 +1529,63 @@ mod modname {
14351529
",
14361530
)
14371531
}
1532+
1533+
#[test]
1534+
fn test_if_inside_impl_block_generate_module_outside() {
1535+
check_assist(
1536+
extract_module,
1537+
r"
1538+
struct A {}
1539+
1540+
impl A {
1541+
$0fn foo() {}$0
1542+
fn bar() {}
1543+
}
1544+
",
1545+
r"
1546+
struct A {}
1547+
1548+
impl A {
1549+
fn bar() {}
1550+
}
1551+
1552+
mod modname {
1553+
use super::A;
1554+
1555+
impl A {
1556+
pub(crate) fn foo() {}
1557+
}
1558+
}
1559+
",
1560+
)
1561+
}
1562+
1563+
#[test]
1564+
fn test_if_inside_impl_block_generate_module_outside_but_impl_block_having_one_child() {
1565+
check_assist(
1566+
extract_module,
1567+
r"
1568+
struct A {}
1569+
struct B {}
1570+
1571+
impl A {
1572+
$0fn foo(x: B) {}$0
1573+
}
1574+
",
1575+
r"
1576+
struct A {}
1577+
struct B {}
1578+
1579+
mod modname {
1580+
use super::B;
1581+
1582+
use super::A;
1583+
1584+
impl A {
1585+
pub(crate) fn foo(x: B) {}
1586+
}
1587+
}
1588+
",
1589+
)
1590+
}
14381591
}

0 commit comments

Comments
 (0)