textwrap/
indentation.rs
1pub fn indent(s: &str, prefix: &str) -> String {
51 let mut result = String::new();
52 for line in s.lines() {
53 if line.chars().any(|c| !c.is_whitespace()) {
54 result.push_str(prefix);
55 result.push_str(line);
56 }
57 result.push('\n');
58 }
59 result
60}
61
62pub fn dedent(s: &str) -> String {
81 let mut prefix = "";
82 let mut lines = s.lines();
83
84 for line in &mut lines {
86 let mut whitespace_idx = line.len();
87 for (idx, ch) in line.char_indices() {
88 if !ch.is_whitespace() {
89 whitespace_idx = idx;
90 break;
91 }
92 }
93
94 if whitespace_idx < line.len() {
96 prefix = &line[..whitespace_idx];
97 break;
98 }
99 }
100
101 for line in &mut lines {
104 let mut whitespace_idx = line.len();
105 for ((idx, a), b) in line.char_indices().zip(prefix.chars()) {
106 if a != b {
107 whitespace_idx = idx;
108 break;
109 }
110 }
111
112 if whitespace_idx < line.len() && whitespace_idx < prefix.len() {
115 prefix = &line[..whitespace_idx];
116 }
117 }
118
119 let mut result = String::new();
121 for line in s.lines() {
122 if line.starts_with(&prefix) && line.chars().any(|c| !c.is_whitespace()) {
123 let (_, tail) = line.split_at(prefix.len());
124 result.push_str(tail);
125 }
126 result.push('\n');
127 }
128
129 if result.ends_with('\n') && !s.ends_with('\n') {
130 let new_len = result.len() - 1;
131 result.truncate(new_len);
132 }
133
134 result
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 fn add_nl(lines: &[&str]) -> String {
144 lines.join("\n") + "\n"
145 }
146
147 #[test]
148 fn indent_empty() {
149 assert_eq!(indent("\n", " "), "\n");
150 }
151
152 #[test]
153 #[cfg_attr(rustfmt, rustfmt_skip)]
154 fn indent_nonempty() {
155 let x = vec![" foo",
156 "bar",
157 " baz"];
158 let y = vec!["// foo",
159 "//bar",
160 "// baz"];
161 assert_eq!(indent(&add_nl(&x), "//"), add_nl(&y));
162 }
163
164 #[test]
165 #[cfg_attr(rustfmt, rustfmt_skip)]
166 fn indent_empty_line() {
167 let x = vec![" foo",
168 "bar",
169 "",
170 " baz"];
171 let y = vec!["// foo",
172 "//bar",
173 "",
174 "// baz"];
175 assert_eq!(indent(&add_nl(&x), "//"), add_nl(&y));
176 }
177
178 #[test]
179 fn dedent_empty() {
180 assert_eq!(dedent(""), "");
181 }
182
183 #[test]
184 #[cfg_attr(rustfmt, rustfmt_skip)]
185 fn dedent_multi_line() {
186 let x = vec![" foo",
187 " bar",
188 " baz"];
189 let y = vec![" foo",
190 "bar",
191 " baz"];
192 assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
193 }
194
195 #[test]
196 #[cfg_attr(rustfmt, rustfmt_skip)]
197 fn dedent_empty_line() {
198 let x = vec![" foo",
199 " bar",
200 " ",
201 " baz"];
202 let y = vec![" foo",
203 "bar",
204 "",
205 " baz"];
206 assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
207 }
208
209 #[test]
210 #[cfg_attr(rustfmt, rustfmt_skip)]
211 fn dedent_blank_line() {
212 let x = vec![" foo",
213 "",
214 " bar",
215 " foo",
216 " bar",
217 " baz"];
218 let y = vec!["foo",
219 "",
220 " bar",
221 " foo",
222 " bar",
223 " baz"];
224 assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
225 }
226
227 #[test]
228 #[cfg_attr(rustfmt, rustfmt_skip)]
229 fn dedent_whitespace_line() {
230 let x = vec![" foo",
231 " ",
232 " bar",
233 " foo",
234 " bar",
235 " baz"];
236 let y = vec!["foo",
237 "",
238 " bar",
239 " foo",
240 " bar",
241 " baz"];
242 assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
243 }
244
245 #[test]
246 #[cfg_attr(rustfmt, rustfmt_skip)]
247 fn dedent_mixed_whitespace() {
248 let x = vec!["\tfoo",
249 " bar"];
250 let y = vec!["\tfoo",
251 " bar"];
252 assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
253 }
254
255 #[test]
256 #[cfg_attr(rustfmt, rustfmt_skip)]
257 fn dedent_tabbed_whitespace() {
258 let x = vec!["\t\tfoo",
259 "\t\t\tbar"];
260 let y = vec!["foo",
261 "\tbar"];
262 assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
263 }
264
265 #[test]
266 #[cfg_attr(rustfmt, rustfmt_skip)]
267 fn dedent_mixed_tabbed_whitespace() {
268 let x = vec!["\t \tfoo",
269 "\t \t\tbar"];
270 let y = vec!["foo",
271 "\tbar"];
272 assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
273 }
274
275 #[test]
276 #[cfg_attr(rustfmt, rustfmt_skip)]
277 fn dedent_mixed_tabbed_whitespace2() {
278 let x = vec!["\t \tfoo",
279 "\t \tbar"];
280 let y = vec!["\tfoo",
281 " \tbar"];
282 assert_eq!(dedent(&add_nl(&x)), add_nl(&y));
283 }
284
285 #[test]
286 #[cfg_attr(rustfmt, rustfmt_skip)]
287 fn dedent_preserve_no_terminating_newline() {
288 let x = vec![" foo",
289 " bar"].join("\n");
290 let y = vec!["foo",
291 " bar"].join("\n");
292 assert_eq!(dedent(&x), y);
293 }
294}