1use 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 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
120pub 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}