diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2019-09-25 21:44:20 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2019-09-25 21:44:20 +0100 |
commit | 870ce4b1a50a07e3a536ab26215804acdfc9ba8a (patch) | |
tree | 2ea0c53b6d7b09998dbc0b32d4552aeb30d210ed /crates/ra_assists/src | |
parent | 0d277faf6c4052dcc80037fc43b4986980d0814b (diff) | |
parent | a525e830a62272d21fbb0fb1c20bfa865791512d (diff) |
Merge #1912
1912: add new editing API, suitable for modifying several nodes at once r=viorina a=matklad
r? @viorina
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/assists/move_bounds.rs | 39 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_editor.rs | 68 |
2 files changed, 76 insertions, 31 deletions
diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs index 6fd2fb72b..671826013 100644 --- a/crates/ra_assists/src/assists/move_bounds.rs +++ b/crates/ra_assists/src/assists/move_bounds.rs | |||
@@ -3,10 +3,9 @@ use ra_syntax::{ | |||
3 | ast::{self, AstNode, NameOwner, TypeBoundsOwner}, | 3 | ast::{self, AstNode, NameOwner, TypeBoundsOwner}, |
4 | SyntaxElement, | 4 | SyntaxElement, |
5 | SyntaxKind::*, | 5 | SyntaxKind::*, |
6 | TextRange, | ||
7 | }; | 6 | }; |
8 | 7 | ||
9 | use crate::{ast_builder::Make, Assist, AssistCtx, AssistId}; | 8 | use crate::{ast_builder::Make, ast_editor::AstEditor, Assist, AssistCtx, AssistId}; |
10 | 9 | ||
11 | pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 10 | pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
12 | let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?; | 11 | let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?; |
@@ -36,23 +35,23 @@ pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) | |||
36 | AssistId("move_bounds_to_where_clause"), | 35 | AssistId("move_bounds_to_where_clause"), |
37 | "move_bounds_to_where_clause", | 36 | "move_bounds_to_where_clause", |
38 | |edit| { | 37 | |edit| { |
39 | let type_params = type_param_list.type_params().collect::<Vec<_>>(); | 38 | let new_params = type_param_list |
40 | 39 | .type_params() | |
41 | for param in &type_params { | 40 | .filter(|it| it.type_bound_list().is_some()) |
42 | if let Some(bounds) = param.type_bound_list() { | 41 | .map(|type_param| { |
43 | let colon = param | 42 | let without_bounds = |
44 | .syntax() | 43 | AstEditor::new(type_param.clone()).remove_bounds().ast().clone(); |
45 | .children_with_tokens() | 44 | (type_param, without_bounds) |
46 | .find(|it| it.kind() == COLON) | 45 | }); |
47 | .unwrap(); | 46 | |
48 | let start = colon.text_range().start(); | 47 | let mut ast_editor = AstEditor::new(type_param_list.clone()); |
49 | let end = bounds.syntax().text_range().end(); | 48 | ast_editor.replace_descendants(new_params); |
50 | edit.delete(TextRange::from_to(start, end)); | 49 | ast_editor.into_text_edit(edit.text_edit_builder()); |
51 | } | 50 | |
52 | } | 51 | let where_clause = { |
53 | 52 | let predicates = type_param_list.type_params().filter_map(build_predicate); | |
54 | let predicates = type_params.iter().filter_map(build_predicate); | 53 | Make::<ast::WhereClause>::from_predicates(predicates) |
55 | let where_clause = Make::<ast::WhereClause>::from_predicates(predicates); | 54 | }; |
56 | 55 | ||
57 | let to_insert = match anchor.prev_sibling_or_token() { | 56 | let to_insert = match anchor.prev_sibling_or_token() { |
58 | Some(ref elem) if elem.kind() == WHITESPACE => { | 57 | Some(ref elem) if elem.kind() == WHITESPACE => { |
@@ -68,7 +67,7 @@ pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) | |||
68 | ctx.build() | 67 | ctx.build() |
69 | } | 68 | } |
70 | 69 | ||
71 | fn build_predicate(param: &ast::TypeParam) -> Option<ast::WherePred> { | 70 | fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { |
72 | let path = Make::<ast::Path>::from_name(param.name()?); | 71 | let path = Make::<ast::Path>::from_name(param.name()?); |
73 | let predicate = Make::<ast::WherePred>::from(path, param.type_bound_list()?.bounds()); | 72 | let predicate = Make::<ast::WherePred>::from(path, param.type_bound_list()?.bounds()); |
74 | Some(predicate) | 73 | Some(predicate) |
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 55c0aa59f..4e253f0a4 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs | |||
@@ -1,11 +1,13 @@ | |||
1 | use std::{iter, ops::RangeInclusive}; | 1 | use std::{iter, ops::RangeInclusive}; |
2 | 2 | ||
3 | use arrayvec::ArrayVec; | 3 | use arrayvec::ArrayVec; |
4 | use rustc_hash::FxHashMap; | ||
4 | 5 | ||
5 | use ra_fmt::leading_indent; | 6 | use ra_fmt::leading_indent; |
6 | use ra_syntax::{ | 7 | use ra_syntax::{ |
7 | algo::{insert_children, replace_children}, | 8 | algo, |
8 | ast, AstNode, Direction, InsertPosition, SyntaxElement, | 9 | ast::{self, TypeBoundsOwner}, |
10 | AstNode, Direction, InsertPosition, NodeOrToken, SyntaxElement, | ||
9 | SyntaxKind::*, | 11 | SyntaxKind::*, |
10 | T, | 12 | T, |
11 | }; | 13 | }; |
@@ -27,26 +29,55 @@ impl<N: AstNode> AstEditor<N> { | |||
27 | } | 29 | } |
28 | 30 | ||
29 | pub fn into_text_edit(self, builder: &mut TextEditBuilder) { | 31 | pub fn into_text_edit(self, builder: &mut TextEditBuilder) { |
30 | // FIXME: compute a more fine-grained diff here. | 32 | // FIXME: this is both horrible inefficient and gives larger than |
31 | // If *you* know a nice algorithm to compute diff between two syntax | 33 | // necessary diff. I bet there's a cool algorithm to diff trees properly. |
32 | // tree, tell me about it! | 34 | go(builder, self.original_ast.syntax().clone().into(), self.ast().syntax().clone().into()); |
33 | builder.replace( | 35 | |
34 | self.original_ast.syntax().text_range(), | 36 | fn go(buf: &mut TextEditBuilder, lhs: SyntaxElement, rhs: SyntaxElement) { |
35 | self.ast().syntax().text().to_string(), | 37 | if lhs.kind() == rhs.kind() && lhs.text_range().len() == rhs.text_range().len() { |
36 | ); | 38 | if match (&lhs, &rhs) { |
39 | (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => lhs.text() == rhs.text(), | ||
40 | (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(), | ||
41 | _ => false, | ||
42 | } { | ||
43 | return; | ||
44 | } | ||
45 | } | ||
46 | if let (Some(lhs), Some(rhs)) = (lhs.as_node(), rhs.as_node()) { | ||
47 | if lhs.children_with_tokens().count() == rhs.children_with_tokens().count() { | ||
48 | for (lhs, rhs) in lhs.children_with_tokens().zip(rhs.children_with_tokens()) { | ||
49 | go(buf, lhs, rhs) | ||
50 | } | ||
51 | return; | ||
52 | } | ||
53 | } | ||
54 | buf.replace(lhs.text_range(), rhs.to_string()) | ||
55 | } | ||
37 | } | 56 | } |
38 | 57 | ||
39 | pub fn ast(&self) -> &N { | 58 | pub fn ast(&self) -> &N { |
40 | &self.ast | 59 | &self.ast |
41 | } | 60 | } |
42 | 61 | ||
62 | pub fn replace_descendants<T: AstNode>( | ||
63 | &mut self, | ||
64 | replacement_map: impl Iterator<Item = (T, T)>, | ||
65 | ) -> &mut Self { | ||
66 | let map = replacement_map | ||
67 | .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into())) | ||
68 | .collect::<FxHashMap<_, _>>(); | ||
69 | let new_syntax = algo::replace_descendants(self.ast.syntax(), &map); | ||
70 | self.ast = N::cast(new_syntax).unwrap(); | ||
71 | self | ||
72 | } | ||
73 | |||
43 | #[must_use] | 74 | #[must_use] |
44 | fn insert_children( | 75 | fn insert_children( |
45 | &self, | 76 | &self, |
46 | position: InsertPosition<SyntaxElement>, | 77 | position: InsertPosition<SyntaxElement>, |
47 | mut to_insert: impl Iterator<Item = SyntaxElement>, | 78 | mut to_insert: impl Iterator<Item = SyntaxElement>, |
48 | ) -> N { | 79 | ) -> N { |
49 | let new_syntax = insert_children(self.ast().syntax(), position, &mut to_insert); | 80 | let new_syntax = algo::insert_children(self.ast().syntax(), position, &mut to_insert); |
50 | N::cast(new_syntax).unwrap() | 81 | N::cast(new_syntax).unwrap() |
51 | } | 82 | } |
52 | 83 | ||
@@ -56,7 +87,7 @@ impl<N: AstNode> AstEditor<N> { | |||
56 | to_delete: RangeInclusive<SyntaxElement>, | 87 | to_delete: RangeInclusive<SyntaxElement>, |
57 | mut to_insert: impl Iterator<Item = SyntaxElement>, | 88 | mut to_insert: impl Iterator<Item = SyntaxElement>, |
58 | ) -> N { | 89 | ) -> N { |
59 | let new_syntax = replace_children(self.ast().syntax(), to_delete, &mut to_insert); | 90 | let new_syntax = algo::replace_children(self.ast().syntax(), to_delete, &mut to_insert); |
60 | N::cast(new_syntax).unwrap() | 91 | N::cast(new_syntax).unwrap() |
61 | } | 92 | } |
62 | 93 | ||
@@ -240,3 +271,18 @@ impl AstEditor<ast::FnDef> { | |||
240 | self.ast = self.replace_children(replace_range, to_insert.into_iter()) | 271 | self.ast = self.replace_children(replace_range, to_insert.into_iter()) |
241 | } | 272 | } |
242 | } | 273 | } |
274 | |||
275 | impl AstEditor<ast::TypeParam> { | ||
276 | pub fn remove_bounds(&mut self) -> &mut Self { | ||
277 | let colon = match self.ast.colon_token() { | ||
278 | Some(it) => it, | ||
279 | None => return self, | ||
280 | }; | ||
281 | let end = match self.ast.type_bound_list() { | ||
282 | Some(it) => it.syntax().clone().into(), | ||
283 | None => colon.clone().into(), | ||
284 | }; | ||
285 | self.ast = self.replace_children(RangeInclusive::new(colon.into(), end), iter::empty()); | ||
286 | self | ||
287 | } | ||
288 | } | ||