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