aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-09-25 21:44:20 +0100
committerGitHub <[email protected]>2019-09-25 21:44:20 +0100
commit870ce4b1a50a07e3a536ab26215804acdfc9ba8a (patch)
tree2ea0c53b6d7b09998dbc0b32d4552aeb30d210ed
parent0d277faf6c4052dcc80037fc43b4986980d0814b (diff)
parenta525e830a62272d21fbb0fb1c20bfa865791512d (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]>
-rw-r--r--Cargo.lock2
-rw-r--r--crates/ra_assists/Cargo.toml1
-rw-r--r--crates/ra_assists/src/assists/move_bounds.rs39
-rw-r--r--crates/ra_assists/src/ast_editor.rs68
-rw-r--r--crates/ra_syntax/Cargo.toml1
-rw-r--r--crates/ra_syntax/src/algo.rs32
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs9
7 files changed, 121 insertions, 31 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 74570a978..275b27775 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -903,6 +903,7 @@ dependencies = [
903 "ra_hir 0.1.0", 903 "ra_hir 0.1.0",
904 "ra_syntax 0.1.0", 904 "ra_syntax 0.1.0",
905 "ra_text_edit 0.1.0", 905 "ra_text_edit 0.1.0",
906 "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
906 "test_utils 0.1.0", 907 "test_utils 0.1.0",
907] 908]
908 909
@@ -1088,6 +1089,7 @@ dependencies = [
1088 "ra_parser 0.1.0", 1089 "ra_parser 0.1.0",
1089 "ra_text_edit 0.1.0", 1090 "ra_text_edit 0.1.0",
1090 "rowan 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 1091 "rowan 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
1092 "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
1091 "rustc_lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1093 "rustc_lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
1092 "smol_str 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 1094 "smol_str 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
1093 "test_utils 0.1.0", 1095 "test_utils 0.1.0",
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"
10join_to_string = "0.1.3" 10join_to_string = "0.1.3"
11itertools = "0.8.0" 11itertools = "0.8.0"
12arrayvec = "0.4.10" 12arrayvec = "0.4.10"
13rustc-hash = "1.0.1"
13 14
14ra_syntax = { path = "../ra_syntax" } 15ra_syntax = { path = "../ra_syntax" }
15ra_text_edit = { path = "../ra_text_edit" } 16ra_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
9use crate::{ast_builder::Make, Assist, AssistCtx, AssistId}; 8use crate::{ast_builder::Make, ast_editor::AstEditor, Assist, AssistCtx, AssistId};
10 9
11pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10pub(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
71fn build_predicate(param: &ast::TypeParam) -> Option<ast::WherePred> { 70fn 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 @@
1use std::{iter, ops::RangeInclusive}; 1use std::{iter, ops::RangeInclusive};
2 2
3use arrayvec::ArrayVec; 3use arrayvec::ArrayVec;
4use rustc_hash::FxHashMap;
4 5
5use ra_fmt::leading_indent; 6use ra_fmt::leading_indent;
6use ra_syntax::{ 7use 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
275impl 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"
11itertools = "0.8.0" 11itertools = "0.8.0"
12rowan = "0.6.1" 12rowan = "0.6.1"
13rustc_lexer = "0.1.0" 13rustc_lexer = "0.1.0"
14rustc-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;
3use std::ops::RangeInclusive; 3use std::ops::RangeInclusive;
4 4
5use itertools::Itertools; 5use itertools::Itertools;
6use rustc_hash::FxHashMap;
6 7
7use crate::{ 8use 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.
131pub 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
126fn with_children( 158fn 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
376impl 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
376impl ast::WherePred { 385impl ast::WherePred {
377 pub fn lifetime_token(&self) -> Option<SyntaxToken> { 386 pub fn lifetime_token(&self) -> Option<SyntaxToken> {
378 self.syntax() 387 self.syntax()