Skip to content

Commit b28d411

Browse files
bors[bot]matklad
andauthored
Merge #3640
3640: Merge imports assist r=matklad a=matklad Work towards #2220 bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents 12c952f + 3f6dc20 commit b28d411

File tree

8 files changed

+274
-72
lines changed

8 files changed

+274
-72
lines changed

crates/ra_assists/src/doc_tests/generated.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,20 @@ fn main() {
417417
)
418418
}
419419

420+
#[test]
421+
fn doctest_merge_imports() {
422+
check(
423+
"merge_imports",
424+
r#####"
425+
use std::<|>fmt::Formatter;
426+
use std::io;
427+
"#####,
428+
r#####"
429+
use std::{fmt::Formatter, io};
430+
"#####,
431+
)
432+
}
433+
420434
#[test]
421435
fn doctest_merge_match_arms() {
422436
check(
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use std::iter::successors;
2+
3+
use ast::{edit::AstNodeEdit, make};
4+
use ra_syntax::{ast, AstNode, AstToken, Direction, InsertPosition, SyntaxElement, T};
5+
6+
use crate::{Assist, AssistCtx, AssistId};
7+
8+
// Assist: merge_imports
9+
//
10+
// Merges two imports with a common prefix.
11+
//
12+
// ```
13+
// use std::<|>fmt::Formatter;
14+
// use std::io;
15+
// ```
16+
// ->
17+
// ```
18+
// use std::{fmt::Formatter, io};
19+
// ```
20+
pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
21+
let tree: ast::UseTree = ctx.find_node_at_offset()?;
22+
let use_item = tree.syntax().parent().and_then(ast::UseItem::cast)?;
23+
let (merged, to_delete) = [Direction::Prev, Direction::Next]
24+
.iter()
25+
.copied()
26+
.filter_map(|dir| next_use_item(&use_item, dir))
27+
.filter_map(|it| Some((it.clone(), it.use_tree()?)))
28+
.find_map(|(use_item, use_tree)| {
29+
Some((try_merge_trees(&tree, &use_tree)?, use_item.clone()))
30+
})?;
31+
let mut offset = ctx.frange.range.start();
32+
ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| {
33+
edit.replace_ast(tree, merged);
34+
35+
let mut range = to_delete.syntax().text_range();
36+
let next_ws = to_delete
37+
.syntax()
38+
.next_sibling_or_token()
39+
.and_then(|it| it.into_token())
40+
.and_then(ast::Whitespace::cast);
41+
if let Some(ws) = next_ws {
42+
range = range.extend_to(&ws.syntax().text_range())
43+
}
44+
edit.delete(range);
45+
if range.end() <= offset {
46+
offset -= range.len();
47+
}
48+
edit.set_cursor(offset);
49+
})
50+
}
51+
52+
fn next_use_item(this_use_item: &ast::UseItem, direction: Direction) -> Option<ast::UseItem> {
53+
this_use_item.syntax().siblings(direction).skip(1).find_map(ast::UseItem::cast)
54+
}
55+
56+
fn try_merge_trees(old: &ast::UseTree, new: &ast::UseTree) -> Option<ast::UseTree> {
57+
let lhs_path = old.path()?;
58+
let rhs_path = new.path()?;
59+
60+
let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
61+
62+
let lhs = old.split_prefix(&lhs_prefix);
63+
let rhs = new.split_prefix(&rhs_prefix);
64+
65+
let mut to_insert: Vec<SyntaxElement> = Vec::new();
66+
to_insert.push(make::token(T![,]).into());
67+
to_insert.push(make::tokens::single_space().into());
68+
to_insert.extend(
69+
rhs.use_tree_list()?
70+
.syntax()
71+
.children_with_tokens()
72+
.filter(|it| it.kind() != T!['{'] && it.kind() != T!['}']),
73+
);
74+
let use_tree_list = lhs.use_tree_list()?;
75+
let pos = InsertPosition::Before(use_tree_list.r_curly()?.into());
76+
let use_tree_list = use_tree_list.insert_children(pos, to_insert);
77+
Some(lhs.with_use_tree_list(use_tree_list))
78+
}
79+
80+
fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
81+
let mut res = None;
82+
let mut lhs_curr = first_path(&lhs);
83+
let mut rhs_curr = first_path(&rhs);
84+
loop {
85+
match (lhs_curr.segment(), rhs_curr.segment()) {
86+
(Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
87+
_ => break,
88+
}
89+
res = Some((lhs_curr.clone(), rhs_curr.clone()));
90+
91+
match (lhs_curr.parent_path(), rhs_curr.parent_path()) {
92+
(Some(lhs), Some(rhs)) => {
93+
lhs_curr = lhs;
94+
rhs_curr = rhs;
95+
}
96+
_ => break,
97+
}
98+
}
99+
100+
res
101+
}
102+
103+
fn first_path(path: &ast::Path) -> ast::Path {
104+
successors(Some(path.clone()), |it| it.qualifier()).last().unwrap()
105+
}
106+
107+
#[cfg(test)]
108+
mod tests {
109+
use crate::helpers::check_assist;
110+
111+
use super::*;
112+
113+
#[test]
114+
fn test_merge_first() {
115+
check_assist(
116+
merge_imports,
117+
r"
118+
use std::fmt<|>::Debug;
119+
use std::fmt::Display;
120+
",
121+
r"
122+
use std::fmt<|>::{Debug, Display};
123+
",
124+
)
125+
}
126+
127+
#[test]
128+
fn test_merge_second() {
129+
check_assist(
130+
merge_imports,
131+
r"
132+
use std::fmt::Debug;
133+
use std::fmt<|>::Display;
134+
",
135+
r"
136+
use std::fmt<|>::{Display, Debug};
137+
",
138+
)
139+
}
140+
141+
#[test]
142+
#[ignore]
143+
fn test_merge_nested() {
144+
check_assist(
145+
merge_imports,
146+
r"
147+
use std::{fmt<|>::Debug, fmt::Display};
148+
",
149+
r"
150+
use std::{fmt::{Debug, Display}};
151+
",
152+
)
153+
}
154+
}

crates/ra_assists/src/handlers/move_bounds.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use ra_syntax::{
2-
ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner},
2+
ast::{self, edit::AstNodeEdit, make, AstNode, NameOwner, TypeBoundsOwner},
33
SyntaxElement,
44
SyntaxKind::*,
55
};
@@ -54,7 +54,7 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
5454
(type_param, without_bounds)
5555
});
5656

57-
let new_type_param_list = edit::replace_descendants(&type_param_list, new_params);
57+
let new_type_param_list = type_param_list.replace_descendants(new_params);
5858
edit.replace_ast(type_param_list.clone(), new_type_param_list);
5959

6060
let where_clause = {

crates/ra_assists/src/handlers/split_import.rs

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
use std::iter::{once, successors};
1+
use std::iter::successors;
22

3-
use ra_syntax::{
4-
ast::{self, make},
5-
AstNode, T,
6-
};
3+
use ra_syntax::{ast, AstNode, T};
74

85
use crate::{Assist, AssistCtx, AssistId};
96

@@ -25,7 +22,10 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
2522

2623
let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast)?;
2724

28-
let new_tree = split_use_tree_prefix(&use_tree, &path)?;
25+
let new_tree = use_tree.split_prefix(&path);
26+
if new_tree == use_tree {
27+
return None;
28+
}
2929
let cursor = ctx.frange.range.start();
3030

3131
ctx.add_assist(AssistId("split_import"), "Split import", |edit| {
@@ -35,23 +35,6 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
3535
})
3636
}
3737

38-
fn split_use_tree_prefix(use_tree: &ast::UseTree, prefix: &ast::Path) -> Option<ast::UseTree> {
39-
let suffix = split_path_prefix(&prefix)?;
40-
let use_tree = make::use_tree(suffix.clone(), use_tree.use_tree_list(), use_tree.alias());
41-
let nested = make::use_tree_list(once(use_tree));
42-
let res = make::use_tree(prefix.clone(), Some(nested), None);
43-
Some(res)
44-
}
45-
46-
fn split_path_prefix(prefix: &ast::Path) -> Option<ast::Path> {
47-
let parent = prefix.parent_path()?;
48-
let mut res = make::path_unqualified(parent.segment()?);
49-
for p in successors(parent.parent_path(), |it| it.parent_path()) {
50-
res = make::path_qualified(res, p.segment()?);
51-
}
52-
Some(res)
53-
}
54-
5538
#[cfg(test)]
5639
mod tests {
5740
use crate::helpers::{check_assist, check_assist_target};

crates/ra_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ mod handlers {
110110
mod inline_local_variable;
111111
mod introduce_variable;
112112
mod invert_if;
113+
mod merge_imports;
113114
mod merge_match_arms;
114115
mod move_bounds;
115116
mod move_guard;
@@ -140,6 +141,7 @@ mod handlers {
140141
inline_local_variable::inline_local_variable,
141142
introduce_variable::introduce_variable,
142143
invert_if::invert_if,
144+
merge_imports::merge_imports,
143145
merge_match_arms::merge_match_arms,
144146
move_bounds::move_bounds_to_where_clause,
145147
move_guard::move_arm_cond_to_match_guard,

0 commit comments

Comments
 (0)