diff options
author | Aleksey Kladov <[email protected]> | 2019-09-25 15:57:12 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-09-25 15:57:12 +0100 |
commit | a525e830a62272d21fbb0fb1c20bfa865791512d (patch) | |
tree | 528212ba4d092c961b68e3591f53988c5b5e6a7d /crates | |
parent | a452e50e0e89390b8a055f0c7c64100e9872edac (diff) |
add new editing API, suitable for modifying several nodes at once
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_assists/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/move_bounds.rs | 39 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_editor.rs | 68 | ||||
-rw-r--r-- | crates/ra_syntax/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_syntax/src/algo.rs | 32 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/extensions.rs | 9 |
6 files changed, 119 insertions, 31 deletions
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index 635d87611..02966bbda 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml | |||
@@ -10,6 +10,7 @@ once_cell = "1.0.1" | |||
10 | join_to_string = "0.1.3" | 10 | join_to_string = "0.1.3" |
11 | itertools = "0.8.0" | 11 | itertools = "0.8.0" |
12 | arrayvec = "0.4.10" | 12 | arrayvec = "0.4.10" |
13 | rustc-hash = "1.0.1" | ||
13 | 14 | ||
14 | ra_syntax = { path = "../ra_syntax" } | 15 | ra_syntax = { path = "../ra_syntax" } |
15 | ra_text_edit = { path = "../ra_text_edit" } | 16 | ra_text_edit = { path = "../ra_text_edit" } |
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 | } | ||
diff --git a/crates/ra_syntax/Cargo.toml b/crates/ra_syntax/Cargo.toml index d3a8b516a..724c38e17 100644 --- a/crates/ra_syntax/Cargo.toml +++ b/crates/ra_syntax/Cargo.toml | |||
@@ -11,6 +11,7 @@ repository = "https://github.com/rust-analyzer/rust-analyzer" | |||
11 | itertools = "0.8.0" | 11 | itertools = "0.8.0" |
12 | rowan = "0.6.1" | 12 | rowan = "0.6.1" |
13 | rustc_lexer = "0.1.0" | 13 | rustc_lexer = "0.1.0" |
14 | rustc-hash = "1.0.1" | ||
14 | 15 | ||
15 | # ideally, `serde` should be enabled by `ra_lsp_server`, but we enable it here | 16 | # ideally, `serde` should be enabled by `ra_lsp_server`, but we enable it here |
16 | # to reduce number of compilations | 17 | # to reduce number of compilations |
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 7ee5aa85b..f0ed96a17 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs | |||
@@ -3,6 +3,7 @@ pub mod visit; | |||
3 | use std::ops::RangeInclusive; | 3 | use std::ops::RangeInclusive; |
4 | 4 | ||
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use rustc_hash::FxHashMap; | ||
6 | 7 | ||
7 | use crate::{ | 8 | use crate::{ |
8 | AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxNodePtr, TextRange, TextUnit, | 9 | AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxNodePtr, TextRange, TextUnit, |
@@ -123,6 +124,37 @@ pub fn replace_children( | |||
123 | with_children(parent, new_children) | 124 | with_children(parent, new_children) |
124 | } | 125 | } |
125 | 126 | ||
127 | /// Replaces descendants in the node, according to the mapping. | ||
128 | /// | ||
129 | /// This is a type-unsafe low-level editing API, if you need to use it, prefer | ||
130 | /// to create a type-safe abstraction on top of it instead. | ||
131 | pub fn replace_descendants( | ||
132 | parent: &SyntaxNode, | ||
133 | map: &FxHashMap<SyntaxElement, SyntaxElement>, | ||
134 | ) -> SyntaxNode { | ||
135 | // FIXME: this could be made much faster. | ||
136 | let new_children = parent.children_with_tokens().map(|it| go(map, it)).collect::<Box<[_]>>(); | ||
137 | return with_children(parent, new_children); | ||
138 | |||
139 | fn go( | ||
140 | map: &FxHashMap<SyntaxElement, SyntaxElement>, | ||
141 | element: SyntaxElement, | ||
142 | ) -> NodeOrToken<rowan::GreenNode, rowan::GreenToken> { | ||
143 | if let Some(replacement) = map.get(&element) { | ||
144 | return match replacement { | ||
145 | NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()), | ||
146 | NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), | ||
147 | }; | ||
148 | } | ||
149 | match element { | ||
150 | NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), | ||
151 | NodeOrToken::Node(it) => { | ||
152 | NodeOrToken::Node(replace_descendants(&it, map).green().clone()) | ||
153 | } | ||
154 | } | ||
155 | } | ||
156 | } | ||
157 | |||
126 | fn with_children( | 158 | fn with_children( |
127 | parent: &SyntaxNode, | 159 | parent: &SyntaxNode, |
128 | new_children: Box<[NodeOrToken<rowan::GreenNode, rowan::GreenToken>]>, | 160 | new_children: Box<[NodeOrToken<rowan::GreenNode, rowan::GreenToken>]>, |
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs index d3a375f87..5f7e9f5b1 100644 --- a/crates/ra_syntax/src/ast/extensions.rs +++ b/crates/ra_syntax/src/ast/extensions.rs | |||
@@ -373,6 +373,15 @@ impl ast::LifetimeParam { | |||
373 | } | 373 | } |
374 | } | 374 | } |
375 | 375 | ||
376 | impl ast::TypeParam { | ||
377 | pub fn colon_token(&self) -> Option<SyntaxToken> { | ||
378 | self.syntax() | ||
379 | .children_with_tokens() | ||
380 | .filter_map(|it| it.into_token()) | ||
381 | .find(|it| it.kind() == T![:]) | ||
382 | } | ||
383 | } | ||
384 | |||
376 | impl ast::WherePred { | 385 | impl ast::WherePred { |
377 | pub fn lifetime_token(&self) -> Option<SyntaxToken> { | 386 | pub fn lifetime_token(&self) -> Option<SyntaxToken> { |
378 | self.syntax() | 387 | self.syntax() |