Skip to content

Commit b50fb10

Browse files
authored
Merge pull request #19469 from snprajwal/merge-imports
refactor: `merge_imports` and `unmerge_imports` to editor
2 parents 5adee2a + d25c175 commit b50fb10

File tree

7 files changed

+175
-101
lines changed

7 files changed

+175
-101
lines changed

crates/ide-assists/src/handlers/merge_imports.rs

+25-43
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use either::Either;
22
use ide_db::imports::{
33
insert_use::{ImportGranularity, InsertUseConfig},
4-
merge_imports::{MergeBehavior, try_merge_imports, try_merge_trees, try_normalize_use_tree},
4+
merge_imports::{MergeBehavior, try_merge_imports, try_merge_trees},
55
};
6-
use itertools::Itertools;
76
use syntax::{
87
AstNode, SyntaxElement, SyntaxNode,
98
algo::neighbor,
10-
ast::{self, edit_in_place::Removable},
11-
match_ast, ted,
9+
ast::{self, syntax_factory::SyntaxFactory},
10+
match_ast,
11+
syntax_editor::Removable,
1212
};
1313

1414
use crate::{
@@ -69,49 +69,32 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
6969
(selection_range, edits?)
7070
};
7171

72+
let parent_node = match ctx.covering_element() {
73+
SyntaxElement::Node(n) => n,
74+
SyntaxElement::Token(t) => t.parent()?,
75+
};
76+
7277
acc.add(AssistId::refactor_rewrite("merge_imports"), "Merge imports", target, |builder| {
73-
let edits_mut: Vec<Edit> = edits
74-
.into_iter()
75-
.map(|it| match it {
76-
Remove(Either::Left(it)) => Remove(Either::Left(builder.make_mut(it))),
77-
Remove(Either::Right(it)) => Remove(Either::Right(builder.make_mut(it))),
78-
Replace(old, new) => Replace(builder.make_syntax_mut(old), new),
79-
})
80-
.collect();
81-
for edit in edits_mut {
78+
let make = SyntaxFactory::with_mappings();
79+
let mut editor = builder.make_editor(&parent_node);
80+
81+
for edit in edits {
8282
match edit {
83-
Remove(it) => it.as_ref().either(Removable::remove, Removable::remove),
84-
Replace(old, new) => {
85-
ted::replace(old, &new);
86-
87-
// If there's a selection and we're replacing a use tree in a tree list,
88-
// normalize the parent use tree if it only contains the merged subtree.
89-
if !ctx.has_empty_selection() {
90-
let normalized_use_tree = ast::UseTree::cast(new)
91-
.as_ref()
92-
.and_then(ast::UseTree::parent_use_tree_list)
93-
.and_then(|use_tree_list| {
94-
if use_tree_list.use_trees().collect_tuple::<(_,)>().is_some() {
95-
Some(use_tree_list.parent_use_tree())
96-
} else {
97-
None
98-
}
99-
})
100-
.and_then(|target_tree| {
101-
try_normalize_use_tree(
102-
&target_tree,
103-
ctx.config.insert_use.granularity.into(),
104-
)
105-
.map(|top_use_tree_flat| (target_tree, top_use_tree_flat))
106-
});
107-
if let Some((old_tree, new_tree)) = normalized_use_tree {
108-
cov_mark::hit!(replace_parent_with_normalized_use_tree);
109-
ted::replace(old_tree.syntax(), new_tree.syntax());
110-
}
83+
Remove(it) => {
84+
let node = it.as_ref();
85+
if let Some(left) = node.left() {
86+
left.remove(&mut editor);
87+
} else if let Some(right) = node.right() {
88+
right.remove(&mut editor);
11189
}
11290
}
91+
Replace(old, new) => {
92+
editor.replace(old, &new);
93+
}
11394
}
11495
}
96+
editor.add_mappings(make.finish_with_mappings());
97+
builder.add_file_edits(ctx.vfs_file_id(), editor);
11598
})
11699
}
117100

@@ -723,11 +706,10 @@ use std::{
723706
);
724707

725708
cov_mark::check!(merge_with_selected_use_tree_neighbors);
726-
cov_mark::check!(replace_parent_with_normalized_use_tree);
727709
check_assist(
728710
merge_imports,
729711
r"use std::$0{fmt::Display, fmt::Debug}$0;",
730-
r"use std::fmt::{Debug, Display};",
712+
r"use std::{fmt::{Debug, Display}};",
731713
);
732714
}
733715

crates/ide-assists/src/handlers/unmerge_use.rs renamed to crates/ide-assists/src/handlers/unmerge_imports.rs

+70-41
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
use syntax::{
22
AstNode, SyntaxKind,
3-
ast::{self, HasVisibility, edit_in_place::Removable, make},
4-
ted::{self, Position},
3+
ast::{
4+
self, HasAttrs, HasVisibility, edit::IndentLevel, edit_in_place::AttrsOwnerEdit, make,
5+
syntax_factory::SyntaxFactory,
6+
},
7+
syntax_editor::{Element, Position, Removable},
58
};
69

710
use crate::{
811
AssistId,
912
assist_context::{AssistContext, Assists},
1013
};
1114

12-
// Assist: unmerge_use
15+
// Assist: unmerge_imports
1316
//
14-
// Extracts single use item from use list.
17+
// Extracts a use item from a use list into a standalone use list.
1518
//
1619
// ```
1720
// use std::fmt::{Debug, Display$0};
@@ -21,39 +24,50 @@ use crate::{
2124
// use std::fmt::{Debug};
2225
// use std::fmt::Display;
2326
// ```
24-
pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
25-
let tree: ast::UseTree = ctx.find_node_at_offset::<ast::UseTree>()?.clone_for_update();
27+
pub(crate) fn unmerge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
28+
let tree = ctx.find_node_at_offset::<ast::UseTree>()?;
2629

2730
let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
2831
if tree_list.use_trees().count() < 2 {
29-
cov_mark::hit!(skip_single_use_item);
32+
cov_mark::hit!(skip_single_import);
3033
return None;
3134
}
3235

33-
let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
36+
let use_ = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
3437
let path = resolve_full_path(&tree)?;
3538

36-
let old_parent_range = use_.syntax().parent()?.text_range();
37-
let new_parent = use_.syntax().parent()?;
38-
3939
// If possible, explain what is going to be done.
4040
let label = match tree.path().and_then(|path| path.first_segment()) {
4141
Some(name) => format!("Unmerge use of `{name}`"),
4242
None => "Unmerge use".into(),
4343
};
4444

4545
let target = tree.syntax().text_range();
46-
acc.add(AssistId::refactor_rewrite("unmerge_use"), label, target, |builder| {
47-
let new_use = make::use_(
46+
acc.add(AssistId::refactor_rewrite("unmerge_imports"), label, target, |builder| {
47+
let make = SyntaxFactory::with_mappings();
48+
let new_use = make.use_(
4849
use_.visibility(),
49-
make::use_tree(path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some()),
50-
)
51-
.clone_for_update();
52-
53-
tree.remove();
54-
ted::insert(Position::after(use_.syntax()), new_use.syntax());
50+
make.use_tree(path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some()),
51+
);
52+
// Add any attributes that are present on the use tree
53+
use_.attrs().for_each(|attr| {
54+
new_use.add_attr(attr.clone_for_update());
55+
});
5556

56-
builder.replace(old_parent_range, new_parent.to_string());
57+
let mut editor = builder.make_editor(use_.syntax());
58+
// Remove the use tree from the current use item
59+
tree.remove(&mut editor);
60+
// Insert a newline and indentation, followed by the new use item
61+
editor.insert_all(
62+
Position::after(use_.syntax()),
63+
vec![
64+
make.whitespace(&format!("\n{}", IndentLevel::from_node(use_.syntax())))
65+
.syntax_element(),
66+
new_use.syntax().syntax_element(),
67+
],
68+
);
69+
editor.add_mappings(make.finish_with_mappings());
70+
builder.add_file_edits(ctx.vfs_file_id(), editor);
5771
})
5872
}
5973

@@ -80,22 +94,22 @@ mod tests {
8094
use super::*;
8195

8296
#[test]
83-
fn skip_single_use_item() {
84-
cov_mark::check!(skip_single_use_item);
97+
fn skip_single_import() {
98+
cov_mark::check!(skip_single_import);
8599
check_assist_not_applicable(
86-
unmerge_use,
100+
unmerge_imports,
87101
r"
88102
use std::fmt::Debug$0;
89103
",
90104
);
91105
check_assist_not_applicable(
92-
unmerge_use,
106+
unmerge_imports,
93107
r"
94108
use std::fmt::{Debug$0};
95109
",
96110
);
97111
check_assist_not_applicable(
98-
unmerge_use,
112+
unmerge_imports,
99113
r"
100114
use std::fmt::Debug as Dbg$0;
101115
",
@@ -105,17 +119,17 @@ use std::fmt::Debug as Dbg$0;
105119
#[test]
106120
fn skip_single_glob_import() {
107121
check_assist_not_applicable(
108-
unmerge_use,
122+
unmerge_imports,
109123
r"
110124
use std::fmt::*$0;
111125
",
112126
);
113127
}
114128

115129
#[test]
116-
fn unmerge_use_item() {
130+
fn unmerge_import() {
117131
check_assist(
118-
unmerge_use,
132+
unmerge_imports,
119133
r"
120134
use std::fmt::{Debug, Display$0};
121135
",
@@ -126,7 +140,7 @@ use std::fmt::Display;
126140
);
127141

128142
check_assist(
129-
unmerge_use,
143+
unmerge_imports,
130144
r"
131145
use std::fmt::{Debug, format$0, Display};
132146
",
@@ -140,7 +154,7 @@ use std::fmt::format;
140154
#[test]
141155
fn unmerge_glob_import() {
142156
check_assist(
143-
unmerge_use,
157+
unmerge_imports,
144158
r"
145159
use std::fmt::{*$0, Display};
146160
",
@@ -152,9 +166,9 @@ use std::fmt::*;
152166
}
153167

154168
#[test]
155-
fn unmerge_renamed_use_item() {
169+
fn unmerge_renamed_import() {
156170
check_assist(
157-
unmerge_use,
171+
unmerge_imports,
158172
r"
159173
use std::fmt::{Debug, Display as Disp$0};
160174
",
@@ -166,9 +180,9 @@ use std::fmt::Display as Disp;
166180
}
167181

168182
#[test]
169-
fn unmerge_indented_use_item() {
183+
fn unmerge_indented_import() {
170184
check_assist(
171-
unmerge_use,
185+
unmerge_imports,
172186
r"
173187
mod format {
174188
use std::fmt::{Debug, Display$0 as Disp, format};
@@ -184,9 +198,9 @@ mod format {
184198
}
185199

186200
#[test]
187-
fn unmerge_nested_use_item() {
201+
fn unmerge_nested_import() {
188202
check_assist(
189-
unmerge_use,
203+
unmerge_imports,
190204
r"
191205
use foo::bar::{baz::{qux$0, foobar}, barbaz};
192206
",
@@ -196,7 +210,7 @@ use foo::bar::baz::qux;
196210
",
197211
);
198212
check_assist(
199-
unmerge_use,
213+
unmerge_imports,
200214
r"
201215
use foo::bar::{baz$0::{qux, foobar}, barbaz};
202216
",
@@ -208,9 +222,9 @@ use foo::bar::baz::{qux, foobar};
208222
}
209223

210224
#[test]
211-
fn unmerge_use_item_with_visibility() {
225+
fn unmerge_import_with_visibility() {
212226
check_assist(
213-
unmerge_use,
227+
unmerge_imports,
214228
r"
215229
pub use std::fmt::{Debug, Display$0};
216230
",
@@ -222,12 +236,27 @@ pub use std::fmt::Display;
222236
}
223237

224238
#[test]
225-
fn unmerge_use_item_on_self() {
239+
fn unmerge_import_on_self() {
226240
check_assist(
227-
unmerge_use,
241+
unmerge_imports,
228242
r"use std::process::{Command, self$0};",
229243
r"use std::process::{Command};
230244
use std::process;",
231245
);
232246
}
247+
248+
#[test]
249+
fn unmerge_import_with_attributes() {
250+
check_assist(
251+
unmerge_imports,
252+
r"
253+
#[allow(deprecated)]
254+
use foo::{bar, baz$0};",
255+
r"
256+
#[allow(deprecated)]
257+
use foo::{bar};
258+
#[allow(deprecated)]
259+
use foo::baz;",
260+
);
261+
}
233262
}

crates/ide-assists/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ mod handlers {
222222
mod toggle_async_sugar;
223223
mod toggle_ignore;
224224
mod toggle_macro_delimiter;
225+
mod unmerge_imports;
225226
mod unmerge_match_arm;
226-
mod unmerge_use;
227227
mod unnecessary_async;
228228
mod unqualify_method_call;
229229
mod unwrap_block;
@@ -363,7 +363,7 @@ mod handlers {
363363
toggle_ignore::toggle_ignore,
364364
toggle_macro_delimiter::toggle_macro_delimiter,
365365
unmerge_match_arm::unmerge_match_arm,
366-
unmerge_use::unmerge_use,
366+
unmerge_imports::unmerge_imports,
367367
unnecessary_async::unnecessary_async,
368368
unqualify_method_call::unqualify_method_call,
369369
unwrap_block::unwrap_block,

0 commit comments

Comments
 (0)