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