cargo_cargofmt/formatting/
indent.rs1use std::borrow::Cow;
2
3use crate::toml::TokenKind;
4use crate::toml::TomlToken;
5
6#[tracing::instrument]
7pub fn normalize_indent(
8 tokens: &mut crate::toml::TomlTokens<'_>,
9 hard_tabs: bool,
10 tab_spaces: usize,
11) {
12 let mut depth = 0;
13 let mut indices = crate::toml::TokenIndices::new();
14 let mut buffer = PaddingBuffer::new(hard_tabs, tab_spaces);
15 while let Some(i) = indices.next_index(tokens) {
16 match tokens.tokens[i].kind {
17 TokenKind::StdTableOpen | TokenKind::ArrayTableOpen => {}
18 TokenKind::StdTableClose | TokenKind::ArrayTableClose => {}
19 TokenKind::ArrayOpen | TokenKind::InlineTableOpen => {
20 depth += 1;
21 }
22 TokenKind::ArrayClose | TokenKind::InlineTableClose => {
23 depth -= 1;
24 }
25 TokenKind::SimpleKey => {}
26 TokenKind::KeySep => {}
27 TokenKind::KeyValSep => {}
28 TokenKind::Scalar => {}
29 TokenKind::ValueSep => {}
30 TokenKind::Whitespace => {}
31 TokenKind::Comment => {}
32 TokenKind::Newline => {
33 let next_i = i + 1;
34 if let Some(next) = tokens.tokens.get_mut(next_i) {
35 match (next.kind, depth) {
36 (TokenKind::Newline, _) => {}
37 (TokenKind::Whitespace, 0) => {
38 *next = TomlToken::EMPTY;
39 }
40 (TokenKind::Whitespace, _) => {
41 let close_count = close_count(tokens, next_i);
42 let ws = buffer.whitespace(depth - close_count);
43 let mut token = TomlToken::EMPTY;
44 token.raw = Cow::Owned(ws.to_owned());
45 tokens.tokens[next_i] = token;
46 }
47 (_, 0) => {}
48 (_, _) => {
49 let close_count = close_count(tokens, next_i);
50 let ws = buffer.whitespace(depth - close_count);
51 let mut token = TomlToken::EMPTY;
52 token.raw = Cow::Owned(ws.to_owned());
53 tokens.tokens.insert(next_i, token);
54 }
55 }
56 }
57 }
58 TokenKind::Error => {}
59 }
60 }
61 tokens.trim_empty_whitespace();
62}
63
64struct PaddingBuffer {
65 buffer: String,
66 c: &'static str,
67 count_per_indent: usize,
68}
69
70impl PaddingBuffer {
71 fn new(hard_tabs: bool, tab_spaces: usize) -> Self {
72 let (count_per_indent, c) = if hard_tabs {
73 (1, "\t")
74 } else {
75 (tab_spaces, " ")
76 };
77 Self {
78 buffer: Default::default(),
79 c,
80 count_per_indent,
81 }
82 }
83
84 fn whitespace(&mut self, depth: usize) -> &str {
85 let count = depth * self.count_per_indent;
86
87 self.buffer.truncate(count);
88 if let Some(add) = count.checked_sub(self.buffer.len()) {
89 self.buffer.reserve(add);
90 for _ in 0..add {
91 self.buffer.push_str(self.c);
92 }
93 }
94
95 &self.buffer
96 }
97}
98
99fn close_count(tokens: &crate::toml::TomlTokens<'_>, i: usize) -> usize {
100 let token_line_count = tokens.tokens[i..]
101 .iter()
102 .take_while(|t| {
103 !matches!(
104 t.kind,
105 TokenKind::Newline
106 | TokenKind::Comment
107 | TokenKind::ArrayOpen
108 | TokenKind::InlineTableOpen
109 )
110 })
111 .count();
112 let end = i + token_line_count + 1;
113 tokens.tokens[i..end]
114 .iter()
115 .filter(|t| matches!(t.kind, TokenKind::ArrayClose | TokenKind::InlineTableClose))
116 .count()
117}
118
119#[cfg(test)]
120mod test {
121 use snapbox::assert_data_eq;
122 use snapbox::str;
123 use snapbox::IntoData;
124
125 #[track_caller]
126 fn valid(input: &str, hard_tabs: bool, tab_spaces: usize, expected: impl IntoData) {
127 let mut tokens = crate::toml::TomlTokens::parse(input);
128 super::normalize_indent(&mut tokens, hard_tabs, tab_spaces);
129 let actual = tokens.to_string();
130
131 assert_data_eq!(&actual, expected);
132
133 let (_, errors) = toml::de::DeTable::parse_recoverable(&actual);
134 if !errors.is_empty() {
135 use std::fmt::Write as _;
136 let mut result = String::new();
137 writeln!(&mut result, "---").unwrap();
138 for error in errors {
139 writeln!(&mut result, "{error}").unwrap();
140 writeln!(&mut result, "---").unwrap();
141 }
142 panic!("failed to parse\n---\n{actual}\n{result}");
143 }
144 }
145
146 #[test]
147 fn empty_tabs() {
148 valid("", true, 10, str![]);
149 }
150
151 #[test]
152 fn empty_spaces() {
153 valid("", false, 10, str![]);
154 }
155
156 #[test]
157 fn cleanup_tabs() {
158 valid(
159 "
160 a = 5
161
162 # Hello
163
164 [b]
165 a = 10
166 b = [
167 1,
168 2,
169 3,
170 ]
171 c = [
172 [
173 1,
174 2,
175 3,
176 ]
177 ]
178 d = [[
179 1,
180 2,
181 3,
182 ]]
183
184 [e]
185 f = 10
186
187g = 11
188",
189 true,
190 10,
191 str![[r#"
192
193a = 5
194
195# Hello
196
197[b]
198a = 10
199b = [
200 1,
201 2,
202 3,
203]
204c = [
205 [
206 1,
207 2,
208 3,
209 ]
210]
211d = [[
212 1,
213 2,
214 3,
215]]
216
217[e]
218f = 10
219
220g = 11
221
222"#]],
223 );
224 }
225
226 #[test]
227 fn cleanup_spaces() {
228 valid(
229 "
230 a = 5
231
232 # Hello
233
234 [b]
235 a = 10
236 b = [
237 1,
238 2,
239 3,
240 ]
241 c = [
242 [
243 1,
244 2,
245 3,
246 ]
247 ]
248 d = [[
249 1,
250 2,
251 3,
252 ]]
253
254 [e]
255 f = 10
256
257g = 11
258",
259 false,
260 10,
261 str![[r#"
262
263a = 5
264
265# Hello
266
267[b]
268a = 10
269b = [
270 1,
271 2,
272 3,
273]
274c = [
275 [
276 1,
277 2,
278 3,
279 ]
280]
281d = [[
282 1,
283 2,
284 3,
285]]
286
287[e]
288f = 10
289
290g = 11
291
292"#]],
293 );
294 }
295}