diff options
-rw-r--r-- | crates/ra_assists/src/doc_tests/generated.rs | 14 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/merge_imports.rs | 154 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/move_bounds.rs | 4 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/split_import.rs | 29 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 116 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/extensions.rs | 14 | ||||
-rw-r--r-- | docs/user/assists.md | 13 |
8 files changed, 274 insertions, 72 deletions
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index 3f56dd508..aef6793e8 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs | |||
@@ -418,6 +418,20 @@ fn main() { | |||
418 | } | 418 | } |
419 | 419 | ||
420 | #[test] | 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 | |||
434 | #[test] | ||
421 | fn doctest_merge_match_arms() { | 435 | fn doctest_merge_match_arms() { |
422 | check( | 436 | check( |
423 | "merge_match_arms", | 437 | "merge_match_arms", |
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs new file mode 100644 index 000000000..96b1ab86a --- /dev/null +++ b/crates/ra_assists/src/handlers/merge_imports.rs | |||
@@ -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 | } | ||
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs index 0b501f3e5..342a770ec 100644 --- a/crates/ra_assists/src/handlers/move_bounds.rs +++ b/crates/ra_assists/src/handlers/move_bounds.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use ra_syntax::{ | 1 | use ra_syntax::{ |
2 | ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner}, | 2 | ast::{self, edit::AstNodeEdit, make, AstNode, NameOwner, TypeBoundsOwner}, |
3 | SyntaxElement, | 3 | SyntaxElement, |
4 | SyntaxKind::*, | 4 | SyntaxKind::*, |
5 | }; | 5 | }; |
@@ -54,7 +54,7 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> { | |||
54 | (type_param, without_bounds) | 54 | (type_param, without_bounds) |
55 | }); | 55 | }); |
56 | 56 | ||
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); |
58 | edit.replace_ast(type_param_list.clone(), new_type_param_list); | 58 | edit.replace_ast(type_param_list.clone(), new_type_param_list); |
59 | 59 | ||
60 | let where_clause = { | 60 | let where_clause = { |
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs index 292c39f59..d9244f22d 100644 --- a/crates/ra_assists/src/handlers/split_import.rs +++ b/crates/ra_assists/src/handlers/split_import.rs | |||
@@ -1,9 +1,6 @@ | |||
1 | use std::iter::{once, successors}; | 1 | use std::iter::successors; |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::{ast, AstNode, T}; |
4 | ast::{self, make}, | ||
5 | AstNode, T, | ||
6 | }; | ||
7 | 4 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 5 | use crate::{Assist, AssistCtx, AssistId}; |
9 | 6 | ||
@@ -25,7 +22,10 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> { | |||
25 | 22 | ||
26 | let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast)?; | 23 | let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast)?; |
27 | 24 | ||
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 | } | ||
29 | let cursor = ctx.frange.range.start(); | 29 | let cursor = ctx.frange.range.start(); |
30 | 30 | ||
31 | ctx.add_assist(AssistId("split_import"), "Split import", |edit| { | 31 | ctx.add_assist(AssistId("split_import"), "Split import", |edit| { |
@@ -35,23 +35,6 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> { | |||
35 | }) | 35 | }) |
36 | } | 36 | } |
37 | 37 | ||
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 | |||
55 | #[cfg(test)] | 38 | #[cfg(test)] |
56 | mod tests { | 39 | mod tests { |
57 | use crate::helpers::{check_assist, check_assist_target}; | 40 | use crate::helpers::{check_assist, check_assist_target}; |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 260ada716..b8704ea7d 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -110,6 +110,7 @@ mod handlers { | |||
110 | mod inline_local_variable; | 110 | mod inline_local_variable; |
111 | mod introduce_variable; | 111 | mod introduce_variable; |
112 | mod invert_if; | 112 | mod invert_if; |
113 | mod merge_imports; | ||
113 | mod merge_match_arms; | 114 | mod merge_match_arms; |
114 | mod move_bounds; | 115 | mod move_bounds; |
115 | mod move_guard; | 116 | mod move_guard; |
@@ -140,6 +141,7 @@ mod handlers { | |||
140 | inline_local_variable::inline_local_variable, | 141 | inline_local_variable::inline_local_variable, |
141 | introduce_variable::introduce_variable, | 142 | introduce_variable::introduce_variable, |
142 | invert_if::invert_if, | 143 | invert_if::invert_if, |
144 | merge_imports::merge_imports, | ||
143 | merge_match_arms::merge_match_arms, | 145 | merge_match_arms::merge_match_arms, |
144 | move_bounds::move_bounds_to_where_clause, | 146 | move_bounds::move_bounds_to_where_clause, |
145 | move_guard::move_arm_cond_to_match_guard, | 147 | move_guard::move_arm_cond_to_match_guard, |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 40a04b9c5..68dae008f 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -23,7 +23,7 @@ impl ast::BinExpr { | |||
23 | pub fn replace_op(&self, op: SyntaxKind) -> Option<ast::BinExpr> { | 23 | pub fn replace_op(&self, op: SyntaxKind) -> Option<ast::BinExpr> { |
24 | let op_node: SyntaxElement = self.op_details()?.0.into(); | 24 | let op_node: SyntaxElement = self.op_details()?.0.into(); |
25 | let to_insert: Option<SyntaxElement> = Some(make::token(op).into()); | 25 | let to_insert: Option<SyntaxElement> = Some(make::token(op).into()); |
26 | Some(replace_children(self, single_node(op_node), to_insert)) | 26 | Some(self.replace_children(single_node(op_node), to_insert)) |
27 | } | 27 | } |
28 | } | 28 | } |
29 | 29 | ||
@@ -39,10 +39,10 @@ impl ast::FnDef { | |||
39 | } else { | 39 | } else { |
40 | to_insert.push(make::tokens::single_space().into()); | 40 | to_insert.push(make::tokens::single_space().into()); |
41 | to_insert.push(body.syntax().clone().into()); | 41 | to_insert.push(body.syntax().clone().into()); |
42 | return insert_children(self, InsertPosition::Last, to_insert); | 42 | return self.insert_children(InsertPosition::Last, to_insert); |
43 | }; | 43 | }; |
44 | to_insert.push(body.syntax().clone().into()); | 44 | to_insert.push(body.syntax().clone().into()); |
45 | replace_children(self, single_node(old_body_or_semi), to_insert) | 45 | self.replace_children(single_node(old_body_or_semi), to_insert) |
46 | } | 46 | } |
47 | } | 47 | } |
48 | 48 | ||
@@ -75,7 +75,7 @@ impl ast::ItemList { | |||
75 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | 75 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); |
76 | let to_insert: ArrayVec<[SyntaxElement; 2]> = | 76 | let to_insert: ArrayVec<[SyntaxElement; 2]> = |
77 | [ws.ws().into(), item.syntax().clone().into()].into(); | 77 | [ws.ws().into(), item.syntax().clone().into()].into(); |
78 | insert_children(self, position, to_insert) | 78 | self.insert_children(position, to_insert) |
79 | } | 79 | } |
80 | 80 | ||
81 | fn l_curly(&self) -> Option<SyntaxElement> { | 81 | fn l_curly(&self) -> Option<SyntaxElement> { |
@@ -106,8 +106,8 @@ impl ast::ItemList { | |||
106 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | 106 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); |
107 | let to_insert = iter::once(ws.ws().into()); | 107 | let to_insert = iter::once(ws.ws().into()); |
108 | match existing_ws { | 108 | match existing_ws { |
109 | None => insert_children(self, InsertPosition::After(l_curly), to_insert), | 109 | None => self.insert_children(InsertPosition::After(l_curly), to_insert), |
110 | Some(ws) => replace_children(self, single_node(ws), to_insert), | 110 | Some(ws) => self.replace_children(single_node(ws), to_insert), |
111 | } | 111 | } |
112 | } | 112 | } |
113 | } | 113 | } |
@@ -184,7 +184,7 @@ impl ast::RecordFieldList { | |||
184 | InsertPosition::After(anchor) => after_field!(anchor), | 184 | InsertPosition::After(anchor) => after_field!(anchor), |
185 | }; | 185 | }; |
186 | 186 | ||
187 | insert_children(self, position, to_insert) | 187 | self.insert_children(position, to_insert) |
188 | } | 188 | } |
189 | 189 | ||
190 | fn l_curly(&self) -> Option<SyntaxElement> { | 190 | fn l_curly(&self) -> Option<SyntaxElement> { |
@@ -203,7 +203,7 @@ impl ast::TypeParam { | |||
203 | Some(it) => it.syntax().clone().into(), | 203 | Some(it) => it.syntax().clone().into(), |
204 | None => colon.clone().into(), | 204 | None => colon.clone().into(), |
205 | }; | 205 | }; |
206 | replace_children(self, colon.into()..=end, iter::empty()) | 206 | self.replace_children(colon.into()..=end, iter::empty()) |
207 | } | 207 | } |
208 | } | 208 | } |
209 | 209 | ||
@@ -211,8 +211,7 @@ impl ast::Path { | |||
211 | #[must_use] | 211 | #[must_use] |
212 | pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { | 212 | pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { |
213 | if let Some(old) = self.segment() { | 213 | if let Some(old) = self.segment() { |
214 | return replace_children( | 214 | return self.replace_children( |
215 | self, | ||
216 | single_node(old.syntax().clone()), | 215 | single_node(old.syntax().clone()), |
217 | iter::once(segment.syntax().clone().into()), | 216 | iter::once(segment.syntax().clone().into()), |
218 | ); | 217 | ); |
@@ -234,8 +233,7 @@ impl ast::PathSegment { | |||
234 | 233 | ||
235 | fn _with_type_args(&self, type_args: ast::TypeArgList, turbo: bool) -> ast::PathSegment { | 234 | fn _with_type_args(&self, type_args: ast::TypeArgList, turbo: bool) -> ast::PathSegment { |
236 | if let Some(old) = self.type_arg_list() { | 235 | if let Some(old) = self.type_arg_list() { |
237 | return replace_children( | 236 | return self.replace_children( |
238 | self, | ||
239 | single_node(old.syntax().clone()), | 237 | single_node(old.syntax().clone()), |
240 | iter::once(type_args.syntax().clone().into()), | 238 | iter::once(type_args.syntax().clone().into()), |
241 | ); | 239 | ); |
@@ -245,7 +243,7 @@ impl ast::PathSegment { | |||
245 | to_insert.push(make::token(T![::]).into()); | 243 | to_insert.push(make::token(T![::]).into()); |
246 | } | 244 | } |
247 | to_insert.push(type_args.syntax().clone().into()); | 245 | to_insert.push(type_args.syntax().clone().into()); |
248 | insert_children(self, InsertPosition::Last, to_insert) | 246 | self.insert_children(InsertPosition::Last, to_insert) |
249 | } | 247 | } |
250 | } | 248 | } |
251 | 249 | ||
@@ -253,7 +251,7 @@ impl ast::UseItem { | |||
253 | #[must_use] | 251 | #[must_use] |
254 | pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::UseItem { | 252 | pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::UseItem { |
255 | if let Some(old) = self.use_tree() { | 253 | if let Some(old) = self.use_tree() { |
256 | return replace_descendants(self, iter::once((old, use_tree))); | 254 | return self.replace_descendants(iter::once((old, use_tree))); |
257 | } | 255 | } |
258 | self.clone() | 256 | self.clone() |
259 | } | 257 | } |
@@ -263,7 +261,7 @@ impl ast::UseTree { | |||
263 | #[must_use] | 261 | #[must_use] |
264 | pub fn with_path(&self, path: ast::Path) -> ast::UseTree { | 262 | pub fn with_path(&self, path: ast::Path) -> ast::UseTree { |
265 | if let Some(old) = self.path() { | 263 | if let Some(old) = self.path() { |
266 | return replace_descendants(self, iter::once((old, path))); | 264 | return self.replace_descendants(iter::once((old, path))); |
267 | } | 265 | } |
268 | self.clone() | 266 | self.clone() |
269 | } | 267 | } |
@@ -271,10 +269,30 @@ impl ast::UseTree { | |||
271 | #[must_use] | 269 | #[must_use] |
272 | pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree { | 270 | pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree { |
273 | if let Some(old) = self.use_tree_list() { | 271 | if let Some(old) = self.use_tree_list() { |
274 | return replace_descendants(self, iter::once((old, use_tree_list))); | 272 | return self.replace_descendants(iter::once((old, use_tree_list))); |
275 | } | 273 | } |
276 | self.clone() | 274 | self.clone() |
277 | } | 275 | } |
276 | |||
277 | #[must_use] | ||
278 | pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree { | ||
279 | let suffix = match split_path_prefix(&prefix) { | ||
280 | Some(it) => it, | ||
281 | None => return self.clone(), | ||
282 | }; | ||
283 | let use_tree = make::use_tree(suffix.clone(), self.use_tree_list(), self.alias()); | ||
284 | let nested = make::use_tree_list(iter::once(use_tree)); | ||
285 | return make::use_tree(prefix.clone(), Some(nested), None); | ||
286 | |||
287 | fn split_path_prefix(prefix: &ast::Path) -> Option<ast::Path> { | ||
288 | let parent = prefix.parent_path()?; | ||
289 | let mut res = make::path_unqualified(parent.segment()?); | ||
290 | for p in iter::successors(parent.parent_path(), |it| it.parent_path()) { | ||
291 | res = make::path_qualified(res, p.segment()?); | ||
292 | } | ||
293 | Some(res) | ||
294 | } | ||
295 | } | ||
278 | } | 296 | } |
279 | 297 | ||
280 | #[must_use] | 298 | #[must_use] |
@@ -295,19 +313,6 @@ fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { | |||
295 | node | 313 | node |
296 | } | 314 | } |
297 | 315 | ||
298 | #[must_use] | ||
299 | pub fn replace_descendants<N: AstNode, D: AstNode>( | ||
300 | parent: &N, | ||
301 | replacement_map: impl IntoIterator<Item = (D, D)>, | ||
302 | ) -> N { | ||
303 | let map = replacement_map | ||
304 | .into_iter() | ||
305 | .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into())) | ||
306 | .collect::<FxHashMap<SyntaxElement, _>>(); | ||
307 | let new_syntax = algo::replace_descendants(parent.syntax(), |n| map.get(n).cloned()); | ||
308 | N::cast(new_syntax).unwrap() | ||
309 | } | ||
310 | |||
311 | #[derive(Debug, Clone, Copy)] | 316 | #[derive(Debug, Clone, Copy)] |
312 | pub struct IndentLevel(pub u8); | 317 | pub struct IndentLevel(pub u8); |
313 | 318 | ||
@@ -411,31 +416,48 @@ fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { | |||
411 | iter::successors(Some(token), |token| token.prev_token()) | 416 | iter::successors(Some(token), |token| token.prev_token()) |
412 | } | 417 | } |
413 | 418 | ||
414 | #[must_use] | 419 | pub trait AstNodeEdit: AstNode + Sized { |
415 | fn insert_children<N: AstNode>( | 420 | #[must_use] |
416 | parent: &N, | 421 | fn insert_children( |
417 | position: InsertPosition<SyntaxElement>, | 422 | &self, |
418 | to_insert: impl IntoIterator<Item = SyntaxElement>, | 423 | position: InsertPosition<SyntaxElement>, |
419 | ) -> N { | 424 | to_insert: impl IntoIterator<Item = SyntaxElement>, |
420 | let new_syntax = algo::insert_children(parent.syntax(), position, to_insert); | 425 | ) -> Self { |
421 | N::cast(new_syntax).unwrap() | 426 | let new_syntax = algo::insert_children(self.syntax(), position, to_insert); |
427 | Self::cast(new_syntax).unwrap() | ||
428 | } | ||
429 | |||
430 | #[must_use] | ||
431 | fn replace_children( | ||
432 | &self, | ||
433 | to_replace: RangeInclusive<SyntaxElement>, | ||
434 | to_insert: impl IntoIterator<Item = SyntaxElement>, | ||
435 | ) -> Self { | ||
436 | let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert); | ||
437 | Self::cast(new_syntax).unwrap() | ||
438 | } | ||
439 | |||
440 | #[must_use] | ||
441 | fn replace_descendants<D: AstNode>( | ||
442 | &self, | ||
443 | replacement_map: impl IntoIterator<Item = (D, D)>, | ||
444 | ) -> Self { | ||
445 | let map = replacement_map | ||
446 | .into_iter() | ||
447 | .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into())) | ||
448 | .collect::<FxHashMap<SyntaxElement, _>>(); | ||
449 | let new_syntax = algo::replace_descendants(self.syntax(), |n| map.get(n).cloned()); | ||
450 | Self::cast(new_syntax).unwrap() | ||
451 | } | ||
422 | } | 452 | } |
423 | 453 | ||
454 | impl<N: AstNode> AstNodeEdit for N {} | ||
455 | |||
424 | fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElement> { | 456 | fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElement> { |
425 | let element = element.into(); | 457 | let element = element.into(); |
426 | element.clone()..=element | 458 | element.clone()..=element |
427 | } | 459 | } |
428 | 460 | ||
429 | #[must_use] | ||
430 | fn replace_children<N: AstNode>( | ||
431 | parent: &N, | ||
432 | to_replace: RangeInclusive<SyntaxElement>, | ||
433 | to_insert: impl IntoIterator<Item = SyntaxElement>, | ||
434 | ) -> N { | ||
435 | let new_syntax = algo::replace_children(parent.syntax(), to_replace, to_insert); | ||
436 | N::cast(new_syntax).unwrap() | ||
437 | } | ||
438 | |||
439 | #[test] | 461 | #[test] |
440 | fn test_increase_indent() { | 462 | fn test_increase_indent() { |
441 | let arm_list = { | 463 | let arm_list = { |
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs index d5986e8b4..c3ae8f90e 100644 --- a/crates/ra_syntax/src/ast/extensions.rs +++ b/crates/ra_syntax/src/ast/extensions.rs | |||
@@ -167,6 +167,20 @@ impl ast::UseTreeList { | |||
167 | .and_then(ast::UseTree::cast) | 167 | .and_then(ast::UseTree::cast) |
168 | .expect("UseTreeLists are always nested in UseTrees") | 168 | .expect("UseTreeLists are always nested in UseTrees") |
169 | } | 169 | } |
170 | pub fn l_curly(&self) -> Option<SyntaxToken> { | ||
171 | self.token(T!['{']) | ||
172 | } | ||
173 | |||
174 | pub fn r_curly(&self) -> Option<SyntaxToken> { | ||
175 | self.token(T!['}']) | ||
176 | } | ||
177 | |||
178 | fn token(&self, kind: SyntaxKind) -> Option<SyntaxToken> { | ||
179 | self.syntax() | ||
180 | .children_with_tokens() | ||
181 | .filter_map(|it| it.into_token()) | ||
182 | .find(|it| it.kind() == kind) | ||
183 | } | ||
170 | } | 184 | } |
171 | 185 | ||
172 | impl ast::ImplDef { | 186 | impl ast::ImplDef { |
diff --git a/docs/user/assists.md b/docs/user/assists.md index 9861332af..e2850b4dd 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md | |||
@@ -402,6 +402,19 @@ fn main() { | |||
402 | } | 402 | } |
403 | ``` | 403 | ``` |
404 | 404 | ||
405 | ## `merge_imports` | ||
406 | |||
407 | Merges two imports with a common prefix. | ||
408 | |||
409 | ```rust | ||
410 | // BEFORE | ||
411 | use std::┃fmt::Formatter; | ||
412 | use std::io; | ||
413 | |||
414 | // AFTER | ||
415 | use std::{fmt::Formatter, io}; | ||
416 | ``` | ||
417 | |||
405 | ## `merge_match_arms` | 418 | ## `merge_match_arms` |
406 | 419 | ||
407 | Merges identical match arms. | 420 | Merges identical match arms. |