Skip to main content

ra_ap_syntax/ast/
edit.rs

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::{fmt, iter, ops};
5
6use crate::{
7    AstToken, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
8    ast::{self, AstNode, make},
9    syntax_editor::{SyntaxEditor, SyntaxMappingBuilder},
10    ted,
11};
12
13use super::syntax_factory::SyntaxFactory;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct IndentLevel(pub u8);
17
18impl From<u8> for IndentLevel {
19    fn from(level: u8) -> IndentLevel {
20        IndentLevel(level)
21    }
22}
23
24impl fmt::Display for IndentLevel {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        let spaces = "                                        ";
27        let buf;
28        let len = self.0 as usize * 4;
29        let indent = if len <= spaces.len() {
30            &spaces[..len]
31        } else {
32            buf = " ".repeat(len);
33            &buf
34        };
35        fmt::Display::fmt(indent, f)
36    }
37}
38
39impl ops::Add<u8> for IndentLevel {
40    type Output = IndentLevel;
41    fn add(self, rhs: u8) -> IndentLevel {
42        IndentLevel(self.0 + rhs)
43    }
44}
45
46impl ops::AddAssign<u8> for IndentLevel {
47    fn add_assign(&mut self, rhs: u8) {
48        self.0 += rhs;
49    }
50}
51
52impl IndentLevel {
53    pub fn zero() -> IndentLevel {
54        IndentLevel(0)
55    }
56    pub fn is_zero(&self) -> bool {
57        self.0 == 0
58    }
59    pub fn from_element(element: &SyntaxElement) -> IndentLevel {
60        match element {
61            rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),
62            rowan::NodeOrToken::Token(it) => IndentLevel::from_token(it),
63        }
64    }
65
66    pub fn from_node(node: &SyntaxNode) -> IndentLevel {
67        match node.first_token() {
68            Some(it) => Self::from_token(&it),
69            None => IndentLevel(0),
70        }
71    }
72
73    pub fn from_token(token: &SyntaxToken) -> IndentLevel {
74        for ws in prev_tokens(token.clone()).filter_map(ast::Whitespace::cast) {
75            let text = ws.syntax().text();
76            if let Some(pos) = text.rfind('\n') {
77                let level = text[pos + 1..].chars().count() / 4;
78                return IndentLevel(level as u8);
79            }
80        }
81        IndentLevel(0)
82    }
83
84    /// XXX: this intentionally doesn't change the indent of the very first token.
85    /// For example, in something like:
86    /// ```
87    /// fn foo() -> i32 {
88    ///    92
89    /// }
90    /// ```
91    /// if you indent the block, the `{` token would stay put.
92    pub(super) fn increase_indent(self, node: &SyntaxNode) {
93        let tokens = node.preorder_with_tokens().filter_map(|event| match event {
94            rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
95            _ => None,
96        });
97        for token in tokens {
98            if let Some(ws) = ast::Whitespace::cast(token)
99                && ws.text().contains('\n')
100            {
101                let new_ws = make::tokens::whitespace(&format!("{}{self}", ws.syntax()));
102                ted::replace(ws.syntax(), &new_ws);
103            }
104        }
105    }
106
107    pub(super) fn clone_increase_indent(self, node: &SyntaxNode) -> SyntaxNode {
108        let node = node.clone_subtree();
109        let mut editor = SyntaxEditor::new(node.clone());
110        let tokens = node
111            .preorder_with_tokens()
112            .filter_map(|event| match event {
113                rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
114                _ => None,
115            })
116            .filter_map(ast::Whitespace::cast)
117            .filter(|ws| ws.text().contains('\n'));
118        for ws in tokens {
119            let new_ws = make::tokens::whitespace(&format!("{}{self}", ws.syntax()));
120            editor.replace(ws.syntax(), &new_ws);
121        }
122        editor.finish().new_root().clone()
123    }
124
125    pub(super) fn decrease_indent(self, node: &SyntaxNode) {
126        let tokens = node.preorder_with_tokens().filter_map(|event| match event {
127            rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
128            _ => None,
129        });
130        for token in tokens {
131            if let Some(ws) = ast::Whitespace::cast(token)
132                && ws.text().contains('\n')
133            {
134                let new_ws = make::tokens::whitespace(
135                    &ws.syntax().text().replace(&format!("\n{self}"), "\n"),
136                );
137                ted::replace(ws.syntax(), &new_ws);
138            }
139        }
140    }
141
142    pub(super) fn clone_decrease_indent(self, node: &SyntaxNode) -> SyntaxNode {
143        let node = node.clone_subtree();
144        let mut editor = SyntaxEditor::new(node.clone());
145        let tokens = node
146            .preorder_with_tokens()
147            .filter_map(|event| match event {
148                rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
149                _ => None,
150            })
151            .filter_map(ast::Whitespace::cast)
152            .filter(|ws| ws.text().contains('\n'));
153        for ws in tokens {
154            let new_ws =
155                make::tokens::whitespace(&ws.syntax().text().replace(&format!("\n{self}"), "\n"));
156            editor.replace(ws.syntax(), &new_ws);
157        }
158        editor.finish().new_root().clone()
159    }
160}
161
162fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
163    iter::successors(Some(token), |token| token.prev_token())
164}
165
166pub trait AstNodeEdit: AstNode + Clone + Sized {
167    fn indent_level(&self) -> IndentLevel {
168        IndentLevel::from_node(self.syntax())
169    }
170    #[must_use]
171    fn indent(&self, level: IndentLevel) -> Self {
172        Self::cast(level.clone_increase_indent(self.syntax())).unwrap()
173    }
174    #[must_use]
175    fn indent_with_mapping(&self, level: IndentLevel, make: &SyntaxFactory) -> Self {
176        let new_node = self.indent(level);
177        if let Some(mut mapping) = make.mappings() {
178            let mut builder = SyntaxMappingBuilder::new(new_node.syntax().clone());
179            for (old, new) in self.syntax().children().zip(new_node.syntax().children()) {
180                builder.map_node(old, new);
181            }
182            builder.finish(&mut mapping);
183        }
184        new_node
185    }
186    #[must_use]
187    fn dedent(&self, level: IndentLevel) -> Self {
188        Self::cast(level.clone_decrease_indent(self.syntax())).unwrap()
189    }
190    #[must_use]
191    fn reset_indent(&self) -> Self {
192        let level = IndentLevel::from_node(self.syntax());
193        self.dedent(level)
194    }
195}
196
197impl<N: AstNode + Clone> AstNodeEdit for N {}
198
199#[test]
200fn test_increase_indent() {
201    let arm_list = {
202        let arm = make::match_arm(make::wildcard_pat().into(), None, make::ext::expr_unit());
203        make::match_arm_list([arm.clone(), arm])
204    };
205    assert_eq!(
206        arm_list.syntax().to_string(),
207        "{
208    _ => (),
209    _ => (),
210}"
211    );
212    let indented = arm_list.indent(IndentLevel(2));
213    assert_eq!(
214        indented.syntax().to_string(),
215        "{
216            _ => (),
217            _ => (),
218        }"
219    );
220}