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