• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
from(level: u8) -> IndentLevel15     fn from(level: u8) -> IndentLevel {
16         IndentLevel(level)
17     }
18 }
19 
20 impl fmt::Display for IndentLevel {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result21     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;
add(self, rhs: u8) -> IndentLevel37     fn add(self, rhs: u8) -> IndentLevel {
38         IndentLevel(self.0 + rhs)
39     }
40 }
41 
42 impl IndentLevel {
single() -> IndentLevel43     pub fn single() -> IndentLevel {
44         IndentLevel(0)
45     }
is_zero(&self) -> bool46     pub fn is_zero(&self) -> bool {
47         self.0 == 0
48     }
from_element(element: &SyntaxElement) -> IndentLevel49     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 
from_node(node: &SyntaxNode) -> IndentLevel56     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 
from_token(token: &SyntaxToken) -> IndentLevel63     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.
increase_indent(self, node: &SyntaxNode)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!("{}{self}", ws.syntax()));
91                     ted::replace(ws.syntax(), &new_ws);
92                 }
93             }
94         }
95     }
96 
decrease_indent(self, node: &SyntaxNode)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 
prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken>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 {
indent_level(&self) -> IndentLevel121     fn indent_level(&self) -> IndentLevel {
122         IndentLevel::from_node(self.syntax())
123     }
124     #[must_use]
indent(&self, level: IndentLevel) -> Self125     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]
dedent(&self, level: IndentLevel) -> Self135     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]
reset_indent(&self) -> Self145     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]
test_increase_indent()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 }
175