diff options
author | Aleksey Kladov <[email protected]> | 2019-04-21 20:13:52 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-04-21 20:13:52 +0100 |
commit | f313ac45af0b9c81cf411a8a4a6873e742bb8562 (patch) | |
tree | 61f3e898b7e86c64b113e690ee55a94882c25911 /crates/ra_assists/src/ast_editor.rs | |
parent | 7cc845e88d870173e1baa39ce4d3885a5b1f7043 (diff) |
use structured editing API for fill struct assist
Diffstat (limited to 'crates/ra_assists/src/ast_editor.rs')
-rw-r--r-- | crates/ra_assists/src/ast_editor.rs | 140 |
1 files changed, 98 insertions, 42 deletions
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 7347c1738..5bf1fc0ed 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs | |||
@@ -1,6 +1,9 @@ | |||
1 | use std::iter; | ||
2 | |||
1 | use arrayvec::ArrayVec; | 3 | use arrayvec::ArrayVec; |
2 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 4 | use ra_text_edit::TextEditBuilder; |
3 | use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction}; | 5 | use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction}; |
6 | use ra_fmt::leading_indent; | ||
4 | 7 | ||
5 | pub struct AstEditor<N: AstNode> { | 8 | pub struct AstEditor<N: AstNode> { |
6 | original_ast: TreeArc<N>, | 9 | original_ast: TreeArc<N>, |
@@ -12,18 +15,25 @@ impl<N: AstNode> AstEditor<N> { | |||
12 | AstEditor { original_ast: node.to_owned(), ast: node.to_owned() } | 15 | AstEditor { original_ast: node.to_owned(), ast: node.to_owned() } |
13 | } | 16 | } |
14 | 17 | ||
15 | pub fn into_text_edit(self) -> TextEdit { | 18 | pub fn into_text_edit(self, builder: &mut TextEditBuilder) { |
16 | // FIXME: compute a more fine-grained diff here. | 19 | // FIXME: compute a more fine-grained diff here. |
17 | // If *you* know a nice algorithm to compute diff between two syntax | 20 | // If *you* know a nice algorithm to compute diff between two syntax |
18 | // tree, tell me about it! | 21 | // tree, tell me about it! |
19 | let mut builder = TextEditBuilder::default(); | ||
20 | builder.replace(self.original_ast.syntax().range(), self.ast().syntax().text().to_string()); | 22 | builder.replace(self.original_ast.syntax().range(), self.ast().syntax().text().to_string()); |
21 | builder.finish() | ||
22 | } | 23 | } |
23 | 24 | ||
24 | pub fn ast(&self) -> &N { | 25 | pub fn ast(&self) -> &N { |
25 | &*self.ast | 26 | &*self.ast |
26 | } | 27 | } |
28 | |||
29 | fn insert_children<'a>( | ||
30 | &self, | ||
31 | position: InsertPosition<SyntaxElement<'_>>, | ||
32 | to_insert: impl Iterator<Item = SyntaxElement<'a>>, | ||
33 | ) -> TreeArc<N> { | ||
34 | let new_syntax = self.ast().syntax().insert_children(position, to_insert); | ||
35 | N::cast(&new_syntax).unwrap().to_owned() | ||
36 | } | ||
27 | } | 37 | } |
28 | 38 | ||
29 | impl AstEditor<ast::NamedFieldList> { | 39 | impl AstEditor<ast::NamedFieldList> { |
@@ -31,41 +41,61 @@ impl AstEditor<ast::NamedFieldList> { | |||
31 | self.insert_field(InsertPosition::Last, field) | 41 | self.insert_field(InsertPosition::Last, field) |
32 | } | 42 | } |
33 | 43 | ||
44 | pub fn make_multiline(&mut self) { | ||
45 | let l_curly = match self.l_curly() { | ||
46 | Some(it) => it, | ||
47 | None => return, | ||
48 | }; | ||
49 | let sibling = match l_curly.next_sibling_or_token() { | ||
50 | Some(it) => it, | ||
51 | None => return, | ||
52 | }; | ||
53 | if sibling.as_token().map(|it| it.text().contains('\n')) == Some(true) { | ||
54 | return; | ||
55 | } | ||
56 | |||
57 | let ws = tokens::WsBuilder::new(&format!( | ||
58 | "\n{}", | ||
59 | leading_indent(self.ast().syntax()).unwrap_or("") | ||
60 | )); | ||
61 | self.ast = self.insert_children(InsertPosition::After(l_curly), iter::once(ws.ws().into())); | ||
62 | } | ||
63 | |||
34 | pub fn insert_field( | 64 | pub fn insert_field( |
35 | &mut self, | 65 | &mut self, |
36 | position: InsertPosition<&'_ ast::NamedField>, | 66 | position: InsertPosition<&'_ ast::NamedField>, |
37 | field: &ast::NamedField, | 67 | field: &ast::NamedField, |
38 | ) { | 68 | ) { |
39 | let mut to_insert: ArrayVec<[SyntaxElement; 2]> = | 69 | let is_multiline = self.ast().syntax().text().contains('\n'); |
40 | [field.syntax().into(), tokens::comma().into()].into(); | 70 | let ws; |
41 | let position = match position { | 71 | let space = if is_multiline { |
42 | InsertPosition::First => { | 72 | ws = tokens::WsBuilder::new(&format!( |
43 | let anchor = match self | 73 | "\n{} ", |
44 | .ast() | 74 | leading_indent(self.ast().syntax()).unwrap_or("") |
45 | .syntax() | 75 | )); |
46 | .children_with_tokens() | 76 | ws.ws() |
47 | .find(|it| it.kind() == L_CURLY) | 77 | } else { |
48 | { | 78 | tokens::single_space() |
79 | }; | ||
80 | |||
81 | let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new(); | ||
82 | to_insert.push(space.into()); | ||
83 | to_insert.push(field.syntax().into()); | ||
84 | to_insert.push(tokens::comma().into()); | ||
85 | |||
86 | macro_rules! after_l_curly { | ||
87 | () => {{ | ||
88 | let anchor = match self.l_curly() { | ||
49 | Some(it) => it, | 89 | Some(it) => it, |
50 | None => return, | 90 | None => return, |
51 | }; | 91 | }; |
52 | InsertPosition::After(anchor) | 92 | InsertPosition::After(anchor) |
53 | } | 93 | }}; |
54 | InsertPosition::Last => { | 94 | } |
55 | let anchor = match self | 95 | |
56 | .ast() | 96 | macro_rules! after_field { |
57 | .syntax() | 97 | ($anchor:expr) => { |
58 | .children_with_tokens() | 98 | if let Some(comma) = $anchor |
59 | .find(|it| it.kind() == R_CURLY) | ||
60 | { | ||
61 | Some(it) => it, | ||
62 | None => return, | ||
63 | }; | ||
64 | InsertPosition::Before(anchor) | ||
65 | } | ||
66 | InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()), | ||
67 | InsertPosition::After(anchor) => { | ||
68 | if let Some(comma) = anchor | ||
69 | .syntax() | 99 | .syntax() |
70 | .siblings_with_tokens(Direction::Next) | 100 | .siblings_with_tokens(Direction::Next) |
71 | .find(|it| it.kind() == COMMA) | 101 | .find(|it| it.kind() == COMMA) |
@@ -73,21 +103,26 @@ impl AstEditor<ast::NamedFieldList> { | |||
73 | InsertPosition::After(comma) | 103 | InsertPosition::After(comma) |
74 | } else { | 104 | } else { |
75 | to_insert.insert(0, tokens::comma().into()); | 105 | to_insert.insert(0, tokens::comma().into()); |
76 | InsertPosition::After(anchor.syntax().into()) | 106 | InsertPosition::After($anchor.syntax().into()) |
77 | } | 107 | } |
78 | } | 108 | }; |
109 | }; | ||
110 | |||
111 | let position = match position { | ||
112 | InsertPosition::First => after_l_curly!(), | ||
113 | InsertPosition::Last => match self.ast().fields().last() { | ||
114 | Some(it) => after_field!(it), | ||
115 | None => after_l_curly!(), | ||
116 | }, | ||
117 | InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()), | ||
118 | InsertPosition::After(anchor) => after_field!(anchor), | ||
79 | }; | 119 | }; |
80 | self.ast = insert_children_into_ast(self.ast(), position, to_insert.iter().cloned()); | 120 | self.ast = self.insert_children(position, to_insert.iter().cloned()); |
81 | } | 121 | } |
82 | } | ||
83 | 122 | ||
84 | fn insert_children_into_ast<'a, N: AstNode>( | 123 | fn l_curly(&self) -> Option<SyntaxElement> { |
85 | node: &N, | 124 | self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY) |
86 | position: InsertPosition<SyntaxElement<'_>>, | 125 | } |
87 | to_insert: impl Iterator<Item = SyntaxElement<'a>>, | ||
88 | ) -> TreeArc<N> { | ||
89 | let new_syntax = node.syntax().insert_children(position, to_insert); | ||
90 | N::cast(&new_syntax).unwrap().to_owned() | ||
91 | } | 126 | } |
92 | 127 | ||
93 | pub struct AstBuilder<N: AstNode> { | 128 | pub struct AstBuilder<N: AstNode> { |
@@ -111,7 +146,7 @@ mod tokens { | |||
111 | use ra_syntax::{AstNode, SourceFile, TreeArc, SyntaxToken, SyntaxKind::*}; | 146 | use ra_syntax::{AstNode, SourceFile, TreeArc, SyntaxToken, SyntaxKind::*}; |
112 | 147 | ||
113 | lazy_static! { | 148 | lazy_static! { |
114 | static ref SOURCE_FILE: TreeArc<SourceFile> = SourceFile::parse(","); | 149 | static ref SOURCE_FILE: TreeArc<SourceFile> = SourceFile::parse(",\n; ;"); |
115 | } | 150 | } |
116 | 151 | ||
117 | pub(crate) fn comma() -> SyntaxToken<'static> { | 152 | pub(crate) fn comma() -> SyntaxToken<'static> { |
@@ -122,6 +157,27 @@ mod tokens { | |||
122 | .find(|it| it.kind() == COMMA) | 157 | .find(|it| it.kind() == COMMA) |
123 | .unwrap() | 158 | .unwrap() |
124 | } | 159 | } |
160 | |||
161 | pub(crate) fn single_space() -> SyntaxToken<'static> { | ||
162 | SOURCE_FILE | ||
163 | .syntax() | ||
164 | .descendants_with_tokens() | ||
165 | .filter_map(|it| it.as_token()) | ||
166 | .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ") | ||
167 | .unwrap() | ||
168 | } | ||
169 | |||
170 | pub(crate) struct WsBuilder(TreeArc<SourceFile>); | ||
171 | |||
172 | impl WsBuilder { | ||
173 | pub(crate) fn new(text: &str) -> WsBuilder { | ||
174 | WsBuilder(SourceFile::parse(text)) | ||
175 | } | ||
176 | pub(crate) fn ws(&self) -> SyntaxToken<'_> { | ||
177 | self.0.syntax().first_child_or_token().unwrap().as_token().unwrap() | ||
178 | } | ||
179 | } | ||
180 | |||
125 | } | 181 | } |
126 | 182 | ||
127 | #[cfg(test)] | 183 | #[cfg(test)] |