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    ted,
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct IndentLevel(pub u8);
14
15impl From<u8> for IndentLevel {
16    fn from(level: u8) -> IndentLevel {
17        IndentLevel(level)
18    }
19}
20
21impl fmt::Display for IndentLevel {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        let spaces = "                                        ";
24        let buf;
25        let len = self.0 as usize * 4;
26        let indent = if len <= spaces.len() {
27            &spaces[..len]
28        } else {
29            buf = " ".repeat(len);
30            &buf
31        };
32        fmt::Display::fmt(indent, f)
33    }
34}
35
36impl ops::Add<u8> for IndentLevel {
37    type Output = IndentLevel;
38    fn add(self, rhs: u8) -> IndentLevel {
39        IndentLevel(self.0 + rhs)
40    }
41}
42
43impl IndentLevel {
44    pub fn single() -> IndentLevel {
45        IndentLevel(0)
46    }
47    pub fn is_zero(&self) -> bool {
48        self.0 == 0
49    }
50    pub fn from_element(element: &SyntaxElement) -> IndentLevel {
51        match element {
52            rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),
53            rowan::NodeOrToken::Token(it) => IndentLevel::from_token(it),
54        }
55    }
56
57    pub fn from_node(node: &SyntaxNode) -> IndentLevel {
58        match node.first_token() {
59            Some(it) => Self::from_token(&it),
60            None => IndentLevel(0),
61        }
62    }
63
64    pub fn from_token(token: &SyntaxToken) -> IndentLevel {
65        for ws in prev_tokens(token.clone()).filter_map(ast::Whitespace::cast) {
66            let text = ws.syntax().text();
67            if let Some(pos) = text.rfind('\n') {
68                let level = text[pos + 1..].chars().count() / 4;
69                return IndentLevel(level as u8);
70            }
71        }
72        IndentLevel(0)
73    }
74
75    /// XXX: this intentionally doesn't change the indent of the very first token.
76    /// For example, in something like:
77    /// ```
78    /// fn foo() -> i32 {
79    ///    92
80    /// }
81    /// ```
82    /// if you indent the block, the `{` token would stay put.
83    pub(super) fn increase_indent(self, node: &SyntaxNode) {
84        let tokens = node.preorder_with_tokens().filter_map(|event| match event {
85            rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
86            _ => None,
87        });
88        for token in tokens {
89            if let Some(ws) = ast::Whitespace::cast(token) {
90                if ws.text().contains('\n') {
91                    let new_ws = make::tokens::whitespace(&format!("{}{self}", ws.syntax()));
92                    ted::replace(ws.syntax(), &new_ws);
93                }
94            }
95        }
96    }
97
98    pub(super) fn decrease_indent(self, node: &SyntaxNode) {
99        let tokens = node.preorder_with_tokens().filter_map(|event| match event {
100            rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
101            _ => None,
102        });
103        for token in tokens {
104            if let Some(ws) = ast::Whitespace::cast(token) {
105                if ws.text().contains('\n') {
106                    let new_ws = make::tokens::whitespace(
107                        &ws.syntax().text().replace(&format!("\n{self}"), "\n"),
108                    );
109                    ted::replace(ws.syntax(), &new_ws);
110                }
111            }
112        }
113    }
114}
115
116fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
117    iter::successors(Some(token), |token| token.prev_token())
118}
119
120/// Soft-deprecated in favor of mutable tree editing API `edit_in_place::Ident`.
121pub trait AstNodeEdit: AstNode + Clone + Sized {
122    fn indent_level(&self) -> IndentLevel {
123        IndentLevel::from_node(self.syntax())
124    }
125    #[must_use]
126    fn indent(&self, level: IndentLevel) -> Self {
127        fn indent_inner(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode {
128            let res = node.clone_subtree().clone_for_update();
129            level.increase_indent(&res);
130            res.clone_subtree()
131        }
132
133        Self::cast(indent_inner(self.syntax(), level)).unwrap()
134    }
135    #[must_use]
136    fn dedent(&self, level: IndentLevel) -> Self {
137        fn dedent_inner(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode {
138            let res = node.clone_subtree().clone_for_update();
139            level.decrease_indent(&res);
140            res.clone_subtree()
141        }
142
143        Self::cast(dedent_inner(self.syntax(), level)).unwrap()
144    }
145    #[must_use]
146    fn reset_indent(&self) -> Self {
147        let level = IndentLevel::from_node(self.syntax());
148        self.dedent(level)
149    }
150}
151
152impl<N: AstNode + Clone> AstNodeEdit for N {}
153
154#[test]
155fn test_increase_indent() {
156    let arm_list = {
157        let arm = make::match_arm(make::wildcard_pat().into(), None, make::ext::expr_unit());
158        make::match_arm_list([arm.clone(), arm])
159    };
160    assert_eq!(
161        arm_list.syntax().to_string(),
162        "{
163    _ => (),
164    _ => (),
165}"
166    );
167    let indented = arm_list.indent(IndentLevel(2));
168    assert_eq!(
169        indented.syntax().to_string(),
170        "{
171            _ => (),
172            _ => (),
173        }"
174    );
175}