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