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.rs333
1 files changed, 333 insertions, 0 deletions
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs
new file mode 100644
index 000000000..6854294ae
--- /dev/null
+++ b/crates/ra_assists/src/ast_editor.rs
@@ -0,0 +1,333 @@
1use std::{iter, ops::RangeInclusive};
2
3use arrayvec::ArrayVec;
4use ra_text_edit::TextEditBuilder;
5use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction};
6use ra_fmt::leading_indent;
7
8pub struct AstEditor<N: AstNode> {
9 original_ast: TreeArc<N>,
10 ast: TreeArc<N>,
11}
12
13impl<N: AstNode> AstEditor<N> {
14 pub fn new(node: &N) -> AstEditor<N> {
15 AstEditor { original_ast: node.to_owned(), ast: node.to_owned() }
16 }
17
18 pub fn into_text_edit(self, builder: &mut TextEditBuilder) {
19 // FIXME: compute a more fine-grained diff here.
20 // If *you* know a nice algorithm to compute diff between two syntax
21 // tree, tell me about it!
22 builder.replace(self.original_ast.syntax().range(), self.ast().syntax().text().to_string());
23 }
24
25 pub fn ast(&self) -> &N {
26 &*self.ast
27 }
28
29 #[must_use]
30 fn insert_children<'a>(
31 &self,
32 position: InsertPosition<SyntaxElement<'_>>,
33 to_insert: impl Iterator<Item = SyntaxElement<'a>>,
34 ) -> TreeArc<N> {
35 let new_syntax = self.ast().syntax().insert_children(position, to_insert);
36 N::cast(&new_syntax).unwrap().to_owned()
37 }
38
39 #[must_use]
40 fn replace_children<'a>(
41 &self,
42 to_delete: RangeInclusive<SyntaxElement<'_>>,
43 to_insert: impl Iterator<Item = SyntaxElement<'a>>,
44 ) -> TreeArc<N> {
45 let new_syntax = self.ast().syntax().replace_children(to_delete, to_insert);
46 N::cast(&new_syntax).unwrap().to_owned()
47 }
48
49 fn do_make_multiline(&mut self) {
50 let l_curly =
51 match self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY) {
52 Some(it) => it,
53 None => return,
54 };
55 let sibling = match l_curly.next_sibling_or_token() {
56 Some(it) => it,
57 None => return,
58 };
59 let existing_ws = match sibling.as_token() {
60 None => None,
61 Some(tok) if tok.kind() != WHITESPACE => None,
62 Some(ws) => {
63 if ws.text().contains('\n') {
64 return;
65 }
66 Some(ws)
67 }
68 };
69
70 let indent = leading_indent(self.ast().syntax()).unwrap_or("");
71 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
72 let to_insert = iter::once(ws.ws().into());
73 self.ast = match existing_ws {
74 None => self.insert_children(InsertPosition::After(l_curly), to_insert),
75 Some(ws) => self.replace_children(RangeInclusive::new(ws.into(), ws.into()), to_insert),
76 };
77 }
78}
79
80impl AstEditor<ast::NamedFieldList> {
81 pub fn append_field(&mut self, field: &ast::NamedField) {
82 self.insert_field(InsertPosition::Last, field)
83 }
84
85 pub fn make_multiline(&mut self) {
86 self.do_make_multiline()
87 }
88
89 pub fn insert_field(
90 &mut self,
91 position: InsertPosition<&'_ ast::NamedField>,
92 field: &ast::NamedField,
93 ) {
94 let is_multiline = self.ast().syntax().text().contains('\n');
95 let ws;
96 let space = if is_multiline {
97 ws = tokens::WsBuilder::new(&format!(
98 "\n{} ",
99 leading_indent(self.ast().syntax()).unwrap_or("")
100 ));
101 ws.ws()
102 } else {
103 tokens::single_space()
104 };
105
106 let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
107 to_insert.push(space.into());
108 to_insert.push(field.syntax().into());
109 to_insert.push(tokens::comma().into());
110
111 macro_rules! after_l_curly {
112 () => {{
113 let anchor = match self.l_curly() {
114 Some(it) => it,
115 None => return,
116 };
117 InsertPosition::After(anchor)
118 }};
119 }
120
121 macro_rules! after_field {
122 ($anchor:expr) => {
123 if let Some(comma) = $anchor
124 .syntax()
125 .siblings_with_tokens(Direction::Next)
126 .find(|it| it.kind() == COMMA)
127 {
128 InsertPosition::After(comma)
129 } else {
130 to_insert.insert(0, tokens::comma().into());
131 InsertPosition::After($anchor.syntax().into())
132 }
133 };
134 };
135
136 let position = match position {
137 InsertPosition::First => after_l_curly!(),
138 InsertPosition::Last => {
139 if !is_multiline {
140 // don't insert comma before curly
141 to_insert.pop();
142 }
143 match self.ast().fields().last() {
144 Some(it) => after_field!(it),
145 None => after_l_curly!(),
146 }
147 }
148 InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()),
149 InsertPosition::After(anchor) => after_field!(anchor),
150 };
151
152 self.ast = self.insert_children(position, to_insert.iter().cloned());
153 }
154
155 fn l_curly(&self) -> Option<SyntaxElement> {
156 self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY)
157 }
158}
159
160impl AstEditor<ast::ItemList> {
161 pub fn make_multiline(&mut self) {
162 self.do_make_multiline()
163 }
164
165 pub fn append_functions<'a>(&mut self, fns: impl Iterator<Item = &'a ast::FnDef>) {
166 fns.for_each(|it| self.append_function(it))
167 }
168
169 pub fn append_function(&mut self, fn_def: &ast::FnDef) {
170 let (indent, position) = match self.ast().impl_items().last() {
171 Some(it) => (
172 leading_indent(it.syntax()).unwrap_or("").to_string(),
173 InsertPosition::After(it.syntax().into()),
174 ),
175 None => match self.l_curly() {
176 Some(it) => (
177 " ".to_string() + leading_indent(self.ast().syntax()).unwrap_or(""),
178 InsertPosition::After(it),
179 ),
180 None => return,
181 },
182 };
183 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
184 let to_insert: ArrayVec<[SyntaxElement; 2]> =
185 [ws.ws().into(), fn_def.syntax().into()].into();
186 self.ast = self.insert_children(position, to_insert.into_iter());
187 }
188
189 fn l_curly(&self) -> Option<SyntaxElement> {
190 self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY)
191 }
192}
193
194impl AstEditor<ast::FnDef> {
195 pub fn set_body(&mut self, body: &ast::Block) {
196 let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
197 let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.ast().body() {
198 old_body.syntax().into()
199 } else if let Some(semi) = self.ast().semicolon_token() {
200 to_insert.push(tokens::single_space().into());
201 semi.into()
202 } else {
203 to_insert.push(tokens::single_space().into());
204 to_insert.push(body.syntax().into());
205 self.ast = self.insert_children(InsertPosition::Last, to_insert.into_iter());
206 return;
207 };
208 to_insert.push(body.syntax().into());
209 let replace_range = RangeInclusive::new(old_body_or_semi, old_body_or_semi);
210 self.ast = self.replace_children(replace_range, to_insert.into_iter())
211 }
212
213 pub fn strip_attrs_and_docs(&mut self) {
214 loop {
215 if let Some(start) = self
216 .ast()
217 .syntax()
218 .children_with_tokens()
219 .find(|it| it.kind() == ATTR || it.kind() == COMMENT)
220 {
221 let end = match start.next_sibling_or_token() {
222 Some(el) if el.kind() == WHITESPACE => el,
223 Some(_) | None => start,
224 };
225 self.ast = self.replace_children(RangeInclusive::new(start, end), iter::empty());
226 } else {
227 break;
228 }
229 }
230 }
231}
232
233pub struct AstBuilder<N: AstNode> {
234 _phantom: std::marker::PhantomData<N>,
235}
236
237impl AstBuilder<ast::NamedField> {
238 fn from_text(text: &str) -> TreeArc<ast::NamedField> {
239 ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text))
240 }
241
242 pub fn from_pieces(name: &ast::NameRef, expr: Option<&ast::Expr>) -> TreeArc<ast::NamedField> {
243 match expr {
244 Some(expr) => Self::from_text(&format!("{}: {}", name.syntax(), expr.syntax())),
245 None => Self::from_text(&name.syntax().to_string()),
246 }
247 }
248}
249
250impl AstBuilder<ast::Block> {
251 fn from_text(text: &str) -> TreeArc<ast::Block> {
252 ast_node_from_file_text(&format!("fn f() {}", text))
253 }
254
255 pub fn single_expr(e: &ast::Expr) -> TreeArc<ast::Block> {
256 Self::from_text(&format!("{{ {} }}", e.syntax()))
257 }
258}
259
260impl AstBuilder<ast::Expr> {
261 fn from_text(text: &str) -> TreeArc<ast::Expr> {
262 ast_node_from_file_text(&format!("fn f() {{ {}; }}", text))
263 }
264
265 pub fn unit() -> TreeArc<ast::Expr> {
266 Self::from_text("()")
267 }
268
269 pub fn unimplemented() -> TreeArc<ast::Expr> {
270 Self::from_text("unimplemented!()")
271 }
272}
273
274impl AstBuilder<ast::NameRef> {
275 pub fn new(text: &str) -> TreeArc<ast::NameRef> {
276 ast_node_from_file_text(&format!("fn f() {{ {}; }}", text))
277 }
278}
279
280fn ast_node_from_file_text<N: AstNode>(text: &str) -> TreeArc<N> {
281 let file = SourceFile::parse(text);
282 let res = file.syntax().descendants().find_map(N::cast).unwrap().to_owned();
283 res
284}
285
286mod tokens {
287 use lazy_static::lazy_static;
288 use ra_syntax::{AstNode, SourceFile, TreeArc, SyntaxToken, SyntaxKind::*};
289
290 lazy_static! {
291 static ref SOURCE_FILE: TreeArc<SourceFile> = SourceFile::parse(",\n; ;");
292 }
293
294 pub(crate) fn comma() -> SyntaxToken<'static> {
295 SOURCE_FILE
296 .syntax()
297 .descendants_with_tokens()
298 .filter_map(|it| it.as_token())
299 .find(|it| it.kind() == COMMA)
300 .unwrap()
301 }
302
303 pub(crate) fn single_space() -> SyntaxToken<'static> {
304 SOURCE_FILE
305 .syntax()
306 .descendants_with_tokens()
307 .filter_map(|it| it.as_token())
308 .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ")
309 .unwrap()
310 }
311
312 #[allow(unused)]
313 pub(crate) fn single_newline() -> SyntaxToken<'static> {
314 SOURCE_FILE
315 .syntax()
316 .descendants_with_tokens()
317 .filter_map(|it| it.as_token())
318 .find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n")
319 .unwrap()
320 }
321
322 pub(crate) struct WsBuilder(TreeArc<SourceFile>);
323
324 impl WsBuilder {
325 pub(crate) fn new(text: &str) -> WsBuilder {
326 WsBuilder(SourceFile::parse(text))
327 }
328 pub(crate) fn ws(&self) -> SyntaxToken<'_> {
329 self.0.syntax().first_child_or_token().unwrap().as_token().unwrap()
330 }
331 }
332
333}