1use alloc::string::String;
8
9pub fn indent(s: &str, prefix: &str) -> String {
55 let mut result = String::with_capacity(2 * s.len());
60 let trimmed_prefix = prefix.trim_end();
61 for (idx, line) in s.split_terminator('\n').enumerate() {
62 if idx > 0 {
63 result.push('\n');
64 }
65 if line.trim().is_empty() {
66 result.push_str(trimmed_prefix);
67 } else {
68 result.push_str(prefix);
69 }
70 result.push_str(line);
71 }
72 if s.ends_with('\n') {
73 result.push('\n');
75 }
76 result
77}
78
79pub fn dedent(s: &str) -> String {
98 let mut prefix = "";
99 let mut lines = s.lines();
100
101 for line in &mut lines {
103 let mut whitespace_idx = line.len();
104 for (idx, ch) in line.char_indices() {
105 if !ch.is_whitespace() {
106 whitespace_idx = idx;
107 break;
108 }
109 }
110
111 if whitespace_idx < line.len() {
113 prefix = &line[..whitespace_idx];
114 break;
115 }
116 }
117
118 for line in &mut lines {
121 let mut whitespace_idx = line.len();
122 for ((idx, a), b) in line.char_indices().zip(prefix.chars()) {
123 if a != b {
124 whitespace_idx = idx;
125 break;
126 }
127 }
128
129 if whitespace_idx < line.len() && whitespace_idx < prefix.len() {
132 prefix = &line[..whitespace_idx];
133 }
134 }
135
136 let mut result = String::new();
138 for line in s.lines() {
139 if line.starts_with(prefix) && line.chars().any(|c| !c.is_whitespace()) {
140 let (_, tail) = line.split_at(prefix.len());
141 result.push_str(tail);
142 }
143 result.push('\n');
144 }
145
146 if result.ends_with('\n') && !s.ends_with('\n') {
147 let new_len = result.len() - 1;
148 result.truncate(new_len);
149 }
150
151 result
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn indent_empty() {
160 assert_eq!(indent("\n", " "), "\n");
161 }
162
163 #[test]
164 #[rustfmt::skip]
165 fn indent_nonempty() {
166 let text = [
167 " foo\n",
168 "bar\n",
169 " baz\n",
170 ].join("");
171 let expected = [
172 "// foo\n",
173 "// bar\n",
174 "// baz\n",
175 ].join("");
176 assert_eq!(indent(&text, "// "), expected);
177 }
178
179 #[test]
180 #[rustfmt::skip]
181 fn indent_empty_line() {
182 let text = [
183 " foo",
184 "bar",
185 "",
186 " baz",
187 ].join("\n");
188 let expected = [
189 "// foo",
190 "// bar",
191 "//",
192 "// baz",
193 ].join("\n");
194 assert_eq!(indent(&text, "// "), expected);
195 }
196
197 #[test]
198 fn dedent_empty() {
199 assert_eq!(dedent(""), "");
200 }
201
202 #[test]
203 #[rustfmt::skip]
204 fn dedent_multi_line() {
205 let x = [
206 " foo",
207 " bar",
208 " baz",
209 ].join("\n");
210 let y = [
211 " foo",
212 "bar",
213 " baz"
214 ].join("\n");
215 assert_eq!(dedent(&x), y);
216 }
217
218 #[test]
219 #[rustfmt::skip]
220 fn dedent_empty_line() {
221 let x = [
222 " foo",
223 " bar",
224 " ",
225 " baz"
226 ].join("\n");
227 let y = [
228 " foo",
229 "bar",
230 "",
231 " baz"
232 ].join("\n");
233 assert_eq!(dedent(&x), y);
234 }
235
236 #[test]
237 #[rustfmt::skip]
238 fn dedent_blank_line() {
239 let x = [
240 " foo",
241 "",
242 " bar",
243 " foo",
244 " bar",
245 " baz",
246 ].join("\n");
247 let y = [
248 "foo",
249 "",
250 " bar",
251 " foo",
252 " bar",
253 " baz",
254 ].join("\n");
255 assert_eq!(dedent(&x), y);
256 }
257
258 #[test]
259 #[rustfmt::skip]
260 fn dedent_whitespace_line() {
261 let x = [
262 " foo",
263 " ",
264 " bar",
265 " foo",
266 " bar",
267 " baz",
268 ].join("\n");
269 let y = [
270 "foo",
271 "",
272 " bar",
273 " foo",
274 " bar",
275 " baz",
276 ].join("\n");
277 assert_eq!(dedent(&x), y);
278 }
279
280 #[test]
281 #[rustfmt::skip]
282 fn dedent_mixed_whitespace() {
283 let x = [
284 "\tfoo",
285 " bar",
286 ].join("\n");
287 let y = [
288 "\tfoo",
289 " bar",
290 ].join("\n");
291 assert_eq!(dedent(&x), y);
292 }
293
294 #[test]
295 #[rustfmt::skip]
296 fn dedent_tabbed_whitespace() {
297 let x = [
298 "\t\tfoo",
299 "\t\t\tbar",
300 ].join("\n");
301 let y = [
302 "foo",
303 "\tbar",
304 ].join("\n");
305 assert_eq!(dedent(&x), y);
306 }
307
308 #[test]
309 #[rustfmt::skip]
310 fn dedent_mixed_tabbed_whitespace() {
311 let x = [
312 "\t \tfoo",
313 "\t \t\tbar",
314 ].join("\n");
315 let y = [
316 "foo",
317 "\tbar",
318 ].join("\n");
319 assert_eq!(dedent(&x), y);
320 }
321
322 #[test]
323 #[rustfmt::skip]
324 fn dedent_mixed_tabbed_whitespace2() {
325 let x = [
326 "\t \tfoo",
327 "\t \tbar",
328 ].join("\n");
329 let y = [
330 "\tfoo",
331 " \tbar",
332 ].join("\n");
333 assert_eq!(dedent(&x), y);
334 }
335
336 #[test]
337 #[rustfmt::skip]
338 fn dedent_preserve_no_terminating_newline() {
339 let x = [
340 " foo",
341 " bar",
342 ].join("\n");
343 let y = [
344 "foo",
345 " bar",
346 ].join("\n");
347 assert_eq!(dedent(&x), y);
348 }
349}