1use crate::core::display_width;
4use crate::line_ending::NonEmptyLines;
5use crate::{fill, LineEnding, Options};
6
7pub fn unfill(text: &str) -> (String, Options<'_>) {
63 let prefix_chars: &[_] = &[' ', '-', '+', '*', '>', '#', '/'];
64
65 let mut options = Options::new(0);
66 for (idx, line) in text.lines().enumerate() {
67 options.width = std::cmp::max(options.width, display_width(line));
68 let without_prefix = line.trim_start_matches(prefix_chars);
69 let prefix = &line[..line.len() - without_prefix.len()];
70
71 if idx == 0 {
72 options.initial_indent = prefix;
73 } else if idx == 1 {
74 options.subsequent_indent = prefix;
75 } else if idx > 1 {
76 for ((idx, x), y) in prefix.char_indices().zip(options.subsequent_indent.chars()) {
77 if x != y {
78 options.subsequent_indent = &prefix[..idx];
79 break;
80 }
81 }
82 if prefix.len() < options.subsequent_indent.len() {
83 options.subsequent_indent = prefix;
84 }
85 }
86 }
87
88 let mut unfilled = String::with_capacity(text.len());
89 let mut detected_line_ending = None;
90
91 for (idx, (line, ending)) in NonEmptyLines(text).enumerate() {
92 if idx == 0 {
93 unfilled.push_str(&line[options.initial_indent.len()..]);
94 } else {
95 unfilled.push(' ');
96 unfilled.push_str(&line[options.subsequent_indent.len()..]);
97 }
98 match (detected_line_ending, ending) {
99 (None, Some(_)) => detected_line_ending = ending,
100 (Some(LineEnding::CRLF), Some(LineEnding::LF)) => detected_line_ending = ending,
101 _ => (),
102 }
103 }
104
105 if let Some(line_ending) = detected_line_ending {
107 if text.ends_with(line_ending.as_str()) {
108 unfilled.push_str(line_ending.as_str());
109 }
110 }
111
112 options.line_ending = detected_line_ending.unwrap_or(LineEnding::LF);
113 (unfilled, options)
114}
115
116pub fn refill<'a, Opt>(filled_text: &str, new_width_or_options: Opt) -> String
170where
171 Opt: Into<Options<'a>>,
172{
173 let mut new_options = new_width_or_options.into();
174 let (text, options) = unfill(filled_text);
175 let stripped = text.strip_suffix(options.line_ending.as_str());
177 let new_line_ending = new_options.line_ending.as_str();
178
179 new_options.initial_indent = options.initial_indent;
180 new_options.subsequent_indent = options.subsequent_indent;
181 let mut refilled = fill(stripped.unwrap_or(&text), new_options);
182
183 if stripped.is_some() {
185 refilled.push_str(new_line_ending);
186 }
187 refilled
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn unfill_simple() {
196 let (text, options) = unfill("foo\nbar");
197 assert_eq!(text, "foo bar");
198 assert_eq!(options.width, 3);
199 assert_eq!(options.line_ending, LineEnding::LF);
200 }
201
202 #[test]
203 fn unfill_no_new_line() {
204 let (text, options) = unfill("foo bar");
205 assert_eq!(text, "foo bar");
206 assert_eq!(options.width, 7);
207 assert_eq!(options.line_ending, LineEnding::LF);
208 }
209
210 #[test]
211 fn unfill_simple_crlf() {
212 let (text, options) = unfill("foo\r\nbar");
213 assert_eq!(text, "foo bar");
214 assert_eq!(options.width, 3);
215 assert_eq!(options.line_ending, LineEnding::CRLF);
216 }
217
218 #[test]
219 fn unfill_mixed_new_lines() {
220 let (text, options) = unfill("foo\r\nbar\nbaz");
221 assert_eq!(text, "foo bar baz");
222 assert_eq!(options.width, 3);
223 assert_eq!(options.line_ending, LineEnding::LF);
224 }
225
226 #[test]
227 fn test_unfill_consecutive_different_prefix() {
228 let (text, options) = unfill("foo\n*\n/");
229 assert_eq!(text, "foo * /");
230 assert_eq!(options.width, 3);
231 assert_eq!(options.line_ending, LineEnding::LF);
232 }
233
234 #[test]
235 fn unfill_trailing_newlines() {
236 let (text, options) = unfill("foo\nbar\n\n\n");
237 assert_eq!(text, "foo bar\n");
238 assert_eq!(options.width, 3);
239 }
240
241 #[test]
242 fn unfill_mixed_trailing_newlines() {
243 let (text, options) = unfill("foo\r\nbar\n\r\n\n");
244 assert_eq!(text, "foo bar\n");
245 assert_eq!(options.width, 3);
246 assert_eq!(options.line_ending, LineEnding::LF);
247 }
248
249 #[test]
250 fn unfill_trailing_crlf() {
251 let (text, options) = unfill("foo bar\r\n");
252 assert_eq!(text, "foo bar\r\n");
253 assert_eq!(options.width, 7);
254 assert_eq!(options.line_ending, LineEnding::CRLF);
255 }
256
257 #[test]
258 fn unfill_initial_indent() {
259 let (text, options) = unfill(" foo\nbar\nbaz");
260 assert_eq!(text, "foo bar baz");
261 assert_eq!(options.width, 5);
262 assert_eq!(options.initial_indent, " ");
263 }
264
265 #[test]
266 fn unfill_differing_indents() {
267 let (text, options) = unfill(" foo\n bar\n baz");
268 assert_eq!(text, "foo bar baz");
269 assert_eq!(options.width, 7);
270 assert_eq!(options.initial_indent, " ");
271 assert_eq!(options.subsequent_indent, " ");
272 }
273
274 #[test]
275 fn unfill_list_item() {
276 let (text, options) = unfill("* foo\n bar\n baz");
277 assert_eq!(text, "foo bar baz");
278 assert_eq!(options.width, 5);
279 assert_eq!(options.initial_indent, "* ");
280 assert_eq!(options.subsequent_indent, " ");
281 }
282
283 #[test]
284 fn unfill_multiple_char_prefix() {
285 let (text, options) = unfill(" // foo bar\n // baz\n // quux");
286 assert_eq!(text, "foo bar baz quux");
287 assert_eq!(options.width, 14);
288 assert_eq!(options.initial_indent, " // ");
289 assert_eq!(options.subsequent_indent, " // ");
290 }
291
292 #[test]
293 fn unfill_block_quote() {
294 let (text, options) = unfill("> foo\n> bar\n> baz");
295 assert_eq!(text, "foo bar baz");
296 assert_eq!(options.width, 5);
297 assert_eq!(options.initial_indent, "> ");
298 assert_eq!(options.subsequent_indent, "> ");
299 }
300
301 #[test]
302 fn unfill_only_prefixes_issue_466() {
303 let (text, options) = unfill("######\nfoo");
306 assert_eq!(text, " foo");
307 assert_eq!(options.width, 6);
308 assert_eq!(options.initial_indent, "######");
309 assert_eq!(options.subsequent_indent, "");
310 }
311
312 #[test]
313 fn unfill_trailing_newlines_issue_466() {
314 let (text, options) = unfill("foo\n##\n\n\r");
318 assert_eq!(text, "foo ## \r");
320 assert_eq!(options.width, 3);
321 assert_eq!(options.initial_indent, "");
322 assert_eq!(options.subsequent_indent, "");
323 }
324
325 #[test]
326 fn unfill_whitespace() {
327 assert_eq!(unfill("foo bar").0, "foo bar");
328 }
329
330 #[test]
331 fn refill_convert_lf_to_crlf() {
332 let options = Options::new(5).line_ending(LineEnding::CRLF);
333 assert_eq!(refill("foo\nbar\n", options), "foo\r\nbar\r\n",);
334 }
335
336 #[test]
337 fn refill_convert_crlf_to_lf() {
338 let options = Options::new(5).line_ending(LineEnding::LF);
339 assert_eq!(refill("foo\r\nbar\r\n", options), "foo\nbar\n",);
340 }
341
342 #[test]
343 fn refill_convert_mixed_newlines() {
344 let options = Options::new(5).line_ending(LineEnding::CRLF);
345 assert_eq!(refill("foo\r\nbar\n", options), "foo\r\nbar\r\n",);
346 }
347
348 #[test]
349 fn refill_defaults_to_lf() {
350 assert_eq!(refill("foo bar baz", 5), "foo\nbar\nbaz");
351 }
352}