diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:19:53 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:20:13 +0100 |
commit | 7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch) | |
tree | bdb47765991cb973b2cd5481a088fac636bd326c /crates/syntax/src/ast/edit.rs | |
parent | ca464650eeaca6195891199a93f4f76cf3e7e697 (diff) | |
parent | e65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff) |
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/syntax/src/ast/edit.rs')
-rw-r--r-- | crates/syntax/src/ast/edit.rs | 673 |
1 files changed, 673 insertions, 0 deletions
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs new file mode 100644 index 000000000..060b20966 --- /dev/null +++ b/crates/syntax/src/ast/edit.rs | |||
@@ -0,0 +1,673 @@ | |||
1 | //! This module contains functions for editing syntax trees. As the trees are | ||
2 | //! immutable, all function here return a fresh copy of the tree, instead of | ||
3 | //! doing an in-place modification. | ||
4 | use std::{ | ||
5 | fmt, iter, | ||
6 | ops::{self, RangeInclusive}, | ||
7 | }; | ||
8 | |||
9 | use arrayvec::ArrayVec; | ||
10 | |||
11 | use crate::{ | ||
12 | algo::{self, neighbor, SyntaxRewriter}, | ||
13 | ast::{ | ||
14 | self, | ||
15 | make::{self, tokens}, | ||
16 | AstNode, TypeBoundsOwner, | ||
17 | }, | ||
18 | AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind, | ||
19 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, | ||
20 | SyntaxNode, SyntaxToken, T, | ||
21 | }; | ||
22 | |||
23 | impl ast::BinExpr { | ||
24 | #[must_use] | ||
25 | pub fn replace_op(&self, op: SyntaxKind) -> Option<ast::BinExpr> { | ||
26 | let op_node: SyntaxElement = self.op_details()?.0.into(); | ||
27 | let to_insert: Option<SyntaxElement> = Some(make::token(op).into()); | ||
28 | Some(self.replace_children(single_node(op_node), to_insert)) | ||
29 | } | ||
30 | } | ||
31 | |||
32 | impl ast::Fn { | ||
33 | #[must_use] | ||
34 | pub fn with_body(&self, body: ast::BlockExpr) -> ast::Fn { | ||
35 | let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new(); | ||
36 | let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.body() { | ||
37 | old_body.syntax().clone().into() | ||
38 | } else if let Some(semi) = self.semicolon_token() { | ||
39 | to_insert.push(make::tokens::single_space().into()); | ||
40 | semi.into() | ||
41 | } else { | ||
42 | to_insert.push(make::tokens::single_space().into()); | ||
43 | to_insert.push(body.syntax().clone().into()); | ||
44 | return self.insert_children(InsertPosition::Last, to_insert); | ||
45 | }; | ||
46 | to_insert.push(body.syntax().clone().into()); | ||
47 | self.replace_children(single_node(old_body_or_semi), to_insert) | ||
48 | } | ||
49 | } | ||
50 | |||
51 | fn make_multiline<N>(node: N) -> N | ||
52 | where | ||
53 | N: AstNode + Clone, | ||
54 | { | ||
55 | let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { | ||
56 | Some(it) => it, | ||
57 | None => return node, | ||
58 | }; | ||
59 | let sibling = match l_curly.next_sibling_or_token() { | ||
60 | Some(it) => it, | ||
61 | None => return node, | ||
62 | }; | ||
63 | let existing_ws = match sibling.as_token() { | ||
64 | None => None, | ||
65 | Some(tok) if tok.kind() != WHITESPACE => None, | ||
66 | Some(ws) => { | ||
67 | if ws.text().contains('\n') { | ||
68 | return node; | ||
69 | } | ||
70 | Some(ws.clone()) | ||
71 | } | ||
72 | }; | ||
73 | |||
74 | let indent = leading_indent(node.syntax()).unwrap_or_default(); | ||
75 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
76 | let to_insert = iter::once(ws.ws().into()); | ||
77 | match existing_ws { | ||
78 | None => node.insert_children(InsertPosition::After(l_curly), to_insert), | ||
79 | Some(ws) => node.replace_children(single_node(ws), to_insert), | ||
80 | } | ||
81 | } | ||
82 | |||
83 | impl ast::AssocItemList { | ||
84 | #[must_use] | ||
85 | pub fn append_items( | ||
86 | &self, | ||
87 | items: impl IntoIterator<Item = ast::AssocItem>, | ||
88 | ) -> ast::AssocItemList { | ||
89 | let mut res = self.clone(); | ||
90 | if !self.syntax().text().contains_char('\n') { | ||
91 | res = make_multiline(res); | ||
92 | } | ||
93 | items.into_iter().for_each(|it| res = res.append_item(it)); | ||
94 | res.fixup_trailing_whitespace().unwrap_or(res) | ||
95 | } | ||
96 | |||
97 | #[must_use] | ||
98 | pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList { | ||
99 | let (indent, position, whitespace) = match self.assoc_items().last() { | ||
100 | Some(it) => ( | ||
101 | leading_indent(it.syntax()).unwrap_or_default().to_string(), | ||
102 | InsertPosition::After(it.syntax().clone().into()), | ||
103 | "\n\n", | ||
104 | ), | ||
105 | None => match self.l_curly_token() { | ||
106 | Some(it) => ( | ||
107 | " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(), | ||
108 | InsertPosition::After(it.into()), | ||
109 | "\n", | ||
110 | ), | ||
111 | None => return self.clone(), | ||
112 | }, | ||
113 | }; | ||
114 | let ws = tokens::WsBuilder::new(&format!("{}{}", whitespace, indent)); | ||
115 | let to_insert: ArrayVec<[SyntaxElement; 2]> = | ||
116 | [ws.ws().into(), item.syntax().clone().into()].into(); | ||
117 | self.insert_children(position, to_insert) | ||
118 | } | ||
119 | |||
120 | /// Remove extra whitespace between last item and closing curly brace. | ||
121 | fn fixup_trailing_whitespace(&self) -> Option<ast::AssocItemList> { | ||
122 | let first_token_after_items = | ||
123 | self.assoc_items().last()?.syntax().next_sibling_or_token()?; | ||
124 | let last_token_before_curly = self.r_curly_token()?.prev_sibling_or_token()?; | ||
125 | if last_token_before_curly != first_token_after_items { | ||
126 | // there is something more between last item and | ||
127 | // right curly than just whitespace - bail out | ||
128 | return None; | ||
129 | } | ||
130 | let whitespace = | ||
131 | last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?; | ||
132 | let text = whitespace.syntax().text(); | ||
133 | let newline = text.rfind("\n")?; | ||
134 | let keep = tokens::WsBuilder::new(&text[newline..]); | ||
135 | Some(self.replace_children( | ||
136 | first_token_after_items..=last_token_before_curly, | ||
137 | std::iter::once(keep.ws().into()), | ||
138 | )) | ||
139 | } | ||
140 | } | ||
141 | |||
142 | impl ast::RecordExprFieldList { | ||
143 | #[must_use] | ||
144 | pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList { | ||
145 | self.insert_field(InsertPosition::Last, field) | ||
146 | } | ||
147 | |||
148 | #[must_use] | ||
149 | pub fn insert_field( | ||
150 | &self, | ||
151 | position: InsertPosition<&'_ ast::RecordExprField>, | ||
152 | field: &ast::RecordExprField, | ||
153 | ) -> ast::RecordExprFieldList { | ||
154 | let is_multiline = self.syntax().text().contains_char('\n'); | ||
155 | let ws; | ||
156 | let space = if is_multiline { | ||
157 | ws = tokens::WsBuilder::new(&format!( | ||
158 | "\n{} ", | ||
159 | leading_indent(self.syntax()).unwrap_or_default() | ||
160 | )); | ||
161 | ws.ws() | ||
162 | } else { | ||
163 | tokens::single_space() | ||
164 | }; | ||
165 | |||
166 | let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new(); | ||
167 | to_insert.push(space.into()); | ||
168 | to_insert.push(field.syntax().clone().into()); | ||
169 | to_insert.push(make::token(T![,]).into()); | ||
170 | |||
171 | macro_rules! after_l_curly { | ||
172 | () => {{ | ||
173 | let anchor = match self.l_curly_token() { | ||
174 | Some(it) => it.into(), | ||
175 | None => return self.clone(), | ||
176 | }; | ||
177 | InsertPosition::After(anchor) | ||
178 | }}; | ||
179 | } | ||
180 | |||
181 | macro_rules! after_field { | ||
182 | ($anchor:expr) => { | ||
183 | if let Some(comma) = $anchor | ||
184 | .syntax() | ||
185 | .siblings_with_tokens(Direction::Next) | ||
186 | .find(|it| it.kind() == T![,]) | ||
187 | { | ||
188 | InsertPosition::After(comma) | ||
189 | } else { | ||
190 | to_insert.insert(0, make::token(T![,]).into()); | ||
191 | InsertPosition::After($anchor.syntax().clone().into()) | ||
192 | } | ||
193 | }; | ||
194 | }; | ||
195 | |||
196 | let position = match position { | ||
197 | InsertPosition::First => after_l_curly!(), | ||
198 | InsertPosition::Last => { | ||
199 | if !is_multiline { | ||
200 | // don't insert comma before curly | ||
201 | to_insert.pop(); | ||
202 | } | ||
203 | match self.fields().last() { | ||
204 | Some(it) => after_field!(it), | ||
205 | None => after_l_curly!(), | ||
206 | } | ||
207 | } | ||
208 | InsertPosition::Before(anchor) => { | ||
209 | InsertPosition::Before(anchor.syntax().clone().into()) | ||
210 | } | ||
211 | InsertPosition::After(anchor) => after_field!(anchor), | ||
212 | }; | ||
213 | |||
214 | self.insert_children(position, to_insert) | ||
215 | } | ||
216 | } | ||
217 | |||
218 | impl ast::TypeAlias { | ||
219 | #[must_use] | ||
220 | pub fn remove_bounds(&self) -> ast::TypeAlias { | ||
221 | let colon = match self.colon_token() { | ||
222 | Some(it) => it, | ||
223 | None => return self.clone(), | ||
224 | }; | ||
225 | let end = match self.type_bound_list() { | ||
226 | Some(it) => it.syntax().clone().into(), | ||
227 | None => colon.clone().into(), | ||
228 | }; | ||
229 | self.replace_children(colon.into()..=end, iter::empty()) | ||
230 | } | ||
231 | } | ||
232 | |||
233 | impl ast::TypeParam { | ||
234 | #[must_use] | ||
235 | pub fn remove_bounds(&self) -> ast::TypeParam { | ||
236 | let colon = match self.colon_token() { | ||
237 | Some(it) => it, | ||
238 | None => return self.clone(), | ||
239 | }; | ||
240 | let end = match self.type_bound_list() { | ||
241 | Some(it) => it.syntax().clone().into(), | ||
242 | None => colon.clone().into(), | ||
243 | }; | ||
244 | self.replace_children(colon.into()..=end, iter::empty()) | ||
245 | } | ||
246 | } | ||
247 | |||
248 | impl ast::Path { | ||
249 | #[must_use] | ||
250 | pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { | ||
251 | if let Some(old) = self.segment() { | ||
252 | return self.replace_children( | ||
253 | single_node(old.syntax().clone()), | ||
254 | iter::once(segment.syntax().clone().into()), | ||
255 | ); | ||
256 | } | ||
257 | self.clone() | ||
258 | } | ||
259 | } | ||
260 | |||
261 | impl ast::PathSegment { | ||
262 | #[must_use] | ||
263 | pub fn with_type_args(&self, type_args: ast::GenericArgList) -> ast::PathSegment { | ||
264 | self._with_type_args(type_args, false) | ||
265 | } | ||
266 | |||
267 | #[must_use] | ||
268 | pub fn with_turbo_fish(&self, type_args: ast::GenericArgList) -> ast::PathSegment { | ||
269 | self._with_type_args(type_args, true) | ||
270 | } | ||
271 | |||
272 | fn _with_type_args(&self, type_args: ast::GenericArgList, turbo: bool) -> ast::PathSegment { | ||
273 | if let Some(old) = self.generic_arg_list() { | ||
274 | return self.replace_children( | ||
275 | single_node(old.syntax().clone()), | ||
276 | iter::once(type_args.syntax().clone().into()), | ||
277 | ); | ||
278 | } | ||
279 | let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new(); | ||
280 | if turbo { | ||
281 | to_insert.push(make::token(T![::]).into()); | ||
282 | } | ||
283 | to_insert.push(type_args.syntax().clone().into()); | ||
284 | self.insert_children(InsertPosition::Last, to_insert) | ||
285 | } | ||
286 | } | ||
287 | |||
288 | impl ast::Use { | ||
289 | #[must_use] | ||
290 | pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::Use { | ||
291 | if let Some(old) = self.use_tree() { | ||
292 | return self.replace_descendant(old, use_tree); | ||
293 | } | ||
294 | self.clone() | ||
295 | } | ||
296 | |||
297 | pub fn remove(&self) -> SyntaxRewriter<'static> { | ||
298 | let mut res = SyntaxRewriter::default(); | ||
299 | res.delete(self.syntax()); | ||
300 | let next_ws = self | ||
301 | .syntax() | ||
302 | .next_sibling_or_token() | ||
303 | .and_then(|it| it.into_token()) | ||
304 | .and_then(ast::Whitespace::cast); | ||
305 | if let Some(next_ws) = next_ws { | ||
306 | let ws_text = next_ws.syntax().text(); | ||
307 | if ws_text.starts_with('\n') { | ||
308 | let rest = &ws_text[1..]; | ||
309 | if rest.is_empty() { | ||
310 | res.delete(next_ws.syntax()) | ||
311 | } else { | ||
312 | res.replace(next_ws.syntax(), &make::tokens::whitespace(rest)); | ||
313 | } | ||
314 | } | ||
315 | } | ||
316 | res | ||
317 | } | ||
318 | } | ||
319 | |||
320 | impl ast::UseTree { | ||
321 | #[must_use] | ||
322 | pub fn with_path(&self, path: ast::Path) -> ast::UseTree { | ||
323 | if let Some(old) = self.path() { | ||
324 | return self.replace_descendant(old, path); | ||
325 | } | ||
326 | self.clone() | ||
327 | } | ||
328 | |||
329 | #[must_use] | ||
330 | pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree { | ||
331 | if let Some(old) = self.use_tree_list() { | ||
332 | return self.replace_descendant(old, use_tree_list); | ||
333 | } | ||
334 | self.clone() | ||
335 | } | ||
336 | |||
337 | #[must_use] | ||
338 | pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree { | ||
339 | let suffix = if self.path().as_ref() == Some(prefix) && self.use_tree_list().is_none() { | ||
340 | make::path_unqualified(make::path_segment_self()) | ||
341 | } else { | ||
342 | match split_path_prefix(&prefix) { | ||
343 | Some(it) => it, | ||
344 | None => return self.clone(), | ||
345 | } | ||
346 | }; | ||
347 | |||
348 | let use_tree = make::use_tree( | ||
349 | suffix, | ||
350 | self.use_tree_list(), | ||
351 | self.rename(), | ||
352 | self.star_token().is_some(), | ||
353 | ); | ||
354 | let nested = make::use_tree_list(iter::once(use_tree)); | ||
355 | return make::use_tree(prefix.clone(), Some(nested), None, false); | ||
356 | |||
357 | fn split_path_prefix(prefix: &ast::Path) -> Option<ast::Path> { | ||
358 | let parent = prefix.parent_path()?; | ||
359 | let segment = parent.segment()?; | ||
360 | if algo::has_errors(segment.syntax()) { | ||
361 | return None; | ||
362 | } | ||
363 | let mut res = make::path_unqualified(segment); | ||
364 | for p in iter::successors(parent.parent_path(), |it| it.parent_path()) { | ||
365 | res = make::path_qualified(res, p.segment()?); | ||
366 | } | ||
367 | Some(res) | ||
368 | } | ||
369 | } | ||
370 | |||
371 | pub fn remove(&self) -> SyntaxRewriter<'static> { | ||
372 | let mut res = SyntaxRewriter::default(); | ||
373 | res.delete(self.syntax()); | ||
374 | for &dir in [Direction::Next, Direction::Prev].iter() { | ||
375 | if let Some(nb) = neighbor(self, dir) { | ||
376 | self.syntax() | ||
377 | .siblings_with_tokens(dir) | ||
378 | .skip(1) | ||
379 | .take_while(|it| it.as_node() != Some(nb.syntax())) | ||
380 | .for_each(|el| res.delete(&el)); | ||
381 | return res; | ||
382 | } | ||
383 | } | ||
384 | res | ||
385 | } | ||
386 | } | ||
387 | |||
388 | impl ast::MatchArmList { | ||
389 | #[must_use] | ||
390 | pub fn append_arms(&self, items: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList { | ||
391 | let mut res = self.clone(); | ||
392 | res = res.strip_if_only_whitespace(); | ||
393 | if !res.syntax().text().contains_char('\n') { | ||
394 | res = make_multiline(res); | ||
395 | } | ||
396 | items.into_iter().for_each(|it| res = res.append_arm(it)); | ||
397 | res | ||
398 | } | ||
399 | |||
400 | fn strip_if_only_whitespace(&self) -> ast::MatchArmList { | ||
401 | let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']); | ||
402 | iter.next(); // Eat the curly | ||
403 | let mut inner = iter.take_while(|it| it.kind() != T!['}']); | ||
404 | if !inner.clone().all(|it| it.kind() == WHITESPACE) { | ||
405 | return self.clone(); | ||
406 | } | ||
407 | let start = match inner.next() { | ||
408 | Some(s) => s, | ||
409 | None => return self.clone(), | ||
410 | }; | ||
411 | let end = match inner.last() { | ||
412 | Some(s) => s, | ||
413 | None => start.clone(), | ||
414 | }; | ||
415 | self.replace_children(start..=end, &mut iter::empty()) | ||
416 | } | ||
417 | |||
418 | #[must_use] | ||
419 | pub fn remove_placeholder(&self) -> ast::MatchArmList { | ||
420 | let placeholder = | ||
421 | self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_)))); | ||
422 | if let Some(placeholder) = placeholder { | ||
423 | self.remove_arm(&placeholder) | ||
424 | } else { | ||
425 | self.clone() | ||
426 | } | ||
427 | } | ||
428 | |||
429 | #[must_use] | ||
430 | fn remove_arm(&self, arm: &ast::MatchArm) -> ast::MatchArmList { | ||
431 | let start = arm.syntax().clone(); | ||
432 | let end = if let Some(comma) = start | ||
433 | .siblings_with_tokens(Direction::Next) | ||
434 | .skip(1) | ||
435 | .skip_while(|it| it.kind().is_trivia()) | ||
436 | .next() | ||
437 | .filter(|it| it.kind() == T![,]) | ||
438 | { | ||
439 | comma | ||
440 | } else { | ||
441 | start.clone().into() | ||
442 | }; | ||
443 | self.replace_children(start.into()..=end, None) | ||
444 | } | ||
445 | |||
446 | #[must_use] | ||
447 | pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList { | ||
448 | let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) { | ||
449 | Some(t) => t, | ||
450 | None => return self.clone(), | ||
451 | }; | ||
452 | let position = InsertPosition::Before(r_curly.into()); | ||
453 | let arm_ws = tokens::WsBuilder::new(" "); | ||
454 | let match_indent = &leading_indent(self.syntax()).unwrap_or_default(); | ||
455 | let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent)); | ||
456 | let to_insert: ArrayVec<[SyntaxElement; 3]> = | ||
457 | [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into(); | ||
458 | self.insert_children(position, to_insert) | ||
459 | } | ||
460 | } | ||
461 | |||
462 | #[must_use] | ||
463 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { | ||
464 | N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() | ||
465 | } | ||
466 | |||
467 | fn remove_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { | ||
468 | while let Some(start) = | ||
469 | node.children_with_tokens().find(|it| it.kind() == ATTR || it.kind() == COMMENT) | ||
470 | { | ||
471 | let end = match &start.next_sibling_or_token() { | ||
472 | Some(el) if el.kind() == WHITESPACE => el.clone(), | ||
473 | Some(_) | None => start.clone(), | ||
474 | }; | ||
475 | node = algo::replace_children(&node, start..=end, &mut iter::empty()); | ||
476 | } | ||
477 | node | ||
478 | } | ||
479 | |||
480 | #[derive(Debug, Clone, Copy)] | ||
481 | pub struct IndentLevel(pub u8); | ||
482 | |||
483 | impl From<u8> for IndentLevel { | ||
484 | fn from(level: u8) -> IndentLevel { | ||
485 | IndentLevel(level) | ||
486 | } | ||
487 | } | ||
488 | |||
489 | impl fmt::Display for IndentLevel { | ||
490 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
491 | let spaces = " "; | ||
492 | let buf; | ||
493 | let len = self.0 as usize * 4; | ||
494 | let indent = if len <= spaces.len() { | ||
495 | &spaces[..len] | ||
496 | } else { | ||
497 | buf = iter::repeat(' ').take(len).collect::<String>(); | ||
498 | &buf | ||
499 | }; | ||
500 | fmt::Display::fmt(indent, f) | ||
501 | } | ||
502 | } | ||
503 | |||
504 | impl ops::Add<u8> for IndentLevel { | ||
505 | type Output = IndentLevel; | ||
506 | fn add(self, rhs: u8) -> IndentLevel { | ||
507 | IndentLevel(self.0 + rhs) | ||
508 | } | ||
509 | } | ||
510 | |||
511 | impl IndentLevel { | ||
512 | pub fn from_node(node: &SyntaxNode) -> IndentLevel { | ||
513 | let first_token = match node.first_token() { | ||
514 | Some(it) => it, | ||
515 | None => return IndentLevel(0), | ||
516 | }; | ||
517 | for ws in prev_tokens(first_token).filter_map(ast::Whitespace::cast) { | ||
518 | let text = ws.syntax().text(); | ||
519 | if let Some(pos) = text.rfind('\n') { | ||
520 | let level = text[pos + 1..].chars().count() / 4; | ||
521 | return IndentLevel(level as u8); | ||
522 | } | ||
523 | } | ||
524 | IndentLevel(0) | ||
525 | } | ||
526 | |||
527 | /// XXX: this intentionally doesn't change the indent of the very first token. | ||
528 | /// Ie, in something like | ||
529 | /// ``` | ||
530 | /// fn foo() { | ||
531 | /// 92 | ||
532 | /// } | ||
533 | /// ``` | ||
534 | /// if you indent the block, the `{` token would stay put. | ||
535 | fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { | ||
536 | let mut rewriter = SyntaxRewriter::default(); | ||
537 | node.descendants_with_tokens() | ||
538 | .filter_map(|el| el.into_token()) | ||
539 | .filter_map(ast::Whitespace::cast) | ||
540 | .filter(|ws| { | ||
541 | let text = ws.syntax().text(); | ||
542 | text.contains('\n') | ||
543 | }) | ||
544 | .for_each(|ws| { | ||
545 | let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,)); | ||
546 | rewriter.replace(ws.syntax(), &new_ws) | ||
547 | }); | ||
548 | rewriter.rewrite(&node) | ||
549 | } | ||
550 | |||
551 | fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode { | ||
552 | let mut rewriter = SyntaxRewriter::default(); | ||
553 | node.descendants_with_tokens() | ||
554 | .filter_map(|el| el.into_token()) | ||
555 | .filter_map(ast::Whitespace::cast) | ||
556 | .filter(|ws| { | ||
557 | let text = ws.syntax().text(); | ||
558 | text.contains('\n') | ||
559 | }) | ||
560 | .for_each(|ws| { | ||
561 | let new_ws = make::tokens::whitespace( | ||
562 | &ws.syntax().text().replace(&format!("\n{}", self), "\n"), | ||
563 | ); | ||
564 | rewriter.replace(ws.syntax(), &new_ws) | ||
565 | }); | ||
566 | rewriter.rewrite(&node) | ||
567 | } | ||
568 | } | ||
569 | |||
570 | // FIXME: replace usages with IndentLevel above | ||
571 | fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> { | ||
572 | for token in prev_tokens(node.first_token()?) { | ||
573 | if let Some(ws) = ast::Whitespace::cast(token.clone()) { | ||
574 | let ws_text = ws.text(); | ||
575 | if let Some(pos) = ws_text.rfind('\n') { | ||
576 | return Some(ws_text[pos + 1..].into()); | ||
577 | } | ||
578 | } | ||
579 | if token.text().contains('\n') { | ||
580 | break; | ||
581 | } | ||
582 | } | ||
583 | None | ||
584 | } | ||
585 | |||
586 | fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { | ||
587 | iter::successors(Some(token), |token| token.prev_token()) | ||
588 | } | ||
589 | |||
590 | pub trait AstNodeEdit: AstNode + Clone + Sized { | ||
591 | #[must_use] | ||
592 | fn insert_children( | ||
593 | &self, | ||
594 | position: InsertPosition<SyntaxElement>, | ||
595 | to_insert: impl IntoIterator<Item = SyntaxElement>, | ||
596 | ) -> Self { | ||
597 | let new_syntax = algo::insert_children(self.syntax(), position, to_insert); | ||
598 | Self::cast(new_syntax).unwrap() | ||
599 | } | ||
600 | |||
601 | #[must_use] | ||
602 | fn replace_children( | ||
603 | &self, | ||
604 | to_replace: RangeInclusive<SyntaxElement>, | ||
605 | to_insert: impl IntoIterator<Item = SyntaxElement>, | ||
606 | ) -> Self { | ||
607 | let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert); | ||
608 | Self::cast(new_syntax).unwrap() | ||
609 | } | ||
610 | |||
611 | #[must_use] | ||
612 | fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self { | ||
613 | self.replace_descendants(iter::once((old, new))) | ||
614 | } | ||
615 | |||
616 | #[must_use] | ||
617 | fn replace_descendants<D: AstNode>( | ||
618 | &self, | ||
619 | replacement_map: impl IntoIterator<Item = (D, D)>, | ||
620 | ) -> Self { | ||
621 | let mut rewriter = SyntaxRewriter::default(); | ||
622 | for (from, to) in replacement_map { | ||
623 | rewriter.replace(from.syntax(), to.syntax()) | ||
624 | } | ||
625 | rewriter.rewrite_ast(self) | ||
626 | } | ||
627 | fn indent_level(&self) -> IndentLevel { | ||
628 | IndentLevel::from_node(self.syntax()) | ||
629 | } | ||
630 | #[must_use] | ||
631 | fn indent(&self, level: IndentLevel) -> Self { | ||
632 | Self::cast(level.increase_indent(self.syntax().clone())).unwrap() | ||
633 | } | ||
634 | #[must_use] | ||
635 | fn dedent(&self, level: IndentLevel) -> Self { | ||
636 | Self::cast(level.decrease_indent(self.syntax().clone())).unwrap() | ||
637 | } | ||
638 | #[must_use] | ||
639 | fn reset_indent(&self) -> Self { | ||
640 | let level = IndentLevel::from_node(self.syntax()); | ||
641 | self.dedent(level) | ||
642 | } | ||
643 | } | ||
644 | |||
645 | impl<N: AstNode + Clone> AstNodeEdit for N {} | ||
646 | |||
647 | fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElement> { | ||
648 | let element = element.into(); | ||
649 | element.clone()..=element | ||
650 | } | ||
651 | |||
652 | #[test] | ||
653 | fn test_increase_indent() { | ||
654 | let arm_list = { | ||
655 | let arm = make::match_arm(iter::once(make::wildcard_pat().into()), make::expr_unit()); | ||
656 | make::match_arm_list(vec![arm.clone(), arm]) | ||
657 | }; | ||
658 | assert_eq!( | ||
659 | arm_list.syntax().to_string(), | ||
660 | "{ | ||
661 | _ => (), | ||
662 | _ => (), | ||
663 | }" | ||
664 | ); | ||
665 | let indented = arm_list.indent(IndentLevel(2)); | ||
666 | assert_eq!( | ||
667 | indented.syntax().to_string(), | ||
668 | "{ | ||
669 | _ => (), | ||
670 | _ => (), | ||
671 | }" | ||
672 | ); | ||
673 | } | ||