aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/ast_editor.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/ast_editor.rs')
-rw-r--r--crates/ra_assists/src/ast_editor.rs140
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 @@
1use std::iter;
2
1use arrayvec::ArrayVec; 3use arrayvec::ArrayVec;
2use ra_text_edit::{TextEdit, TextEditBuilder}; 4use ra_text_edit::TextEditBuilder;
3use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction}; 5use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction};
6use ra_fmt::leading_indent;
4 7
5pub struct AstEditor<N: AstNode> { 8pub 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
29impl AstEditor<ast::NamedFieldList> { 39impl 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
84fn 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
93pub struct AstBuilder<N: AstNode> { 128pub 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)]