use crate::core::display_width;
use crate::line_ending::NonEmptyLines;
use crate::{fill, LineEnding, Options};
pub fn unfill(text: &str) -> (String, Options<'_>) {
let prefix_chars: &[_] = &[' ', '-', '+', '*', '>', '#', '/'];
let mut options = Options::new(0);
for (idx, line) in text.lines().enumerate() {
options.width = std::cmp::max(options.width, display_width(line));
let without_prefix = line.trim_start_matches(prefix_chars);
let prefix = &line[..line.len() - without_prefix.len()];
if idx == 0 {
options.initial_indent = prefix;
} else if idx == 1 {
options.subsequent_indent = prefix;
} else if idx > 1 {
for ((idx, x), y) in prefix.char_indices().zip(options.subsequent_indent.chars()) {
if x != y {
options.subsequent_indent = &prefix[..idx];
break;
}
}
if prefix.len() < options.subsequent_indent.len() {
options.subsequent_indent = prefix;
}
}
}
let mut unfilled = String::with_capacity(text.len());
let mut detected_line_ending = None;
for (idx, (line, ending)) in NonEmptyLines(text).enumerate() {
if idx == 0 {
unfilled.push_str(&line[options.initial_indent.len()..]);
} else {
unfilled.push(' ');
unfilled.push_str(&line[options.subsequent_indent.len()..]);
}
match (detected_line_ending, ending) {
(None, Some(_)) => detected_line_ending = ending,
(Some(LineEnding::CRLF), Some(LineEnding::LF)) => detected_line_ending = ending,
_ => (),
}
}
if let Some(line_ending) = detected_line_ending {
if text.ends_with(line_ending.as_str()) {
unfilled.push_str(line_ending.as_str());
}
}
options.line_ending = detected_line_ending.unwrap_or(LineEnding::LF);
(unfilled, options)
}
pub fn refill<'a, Opt>(filled_text: &str, new_width_or_options: Opt) -> String
where
Opt: Into<Options<'a>>,
{
let mut new_options = new_width_or_options.into();
let (text, options) = unfill(filled_text);
let stripped = text.strip_suffix(options.line_ending.as_str());
let new_line_ending = new_options.line_ending.as_str();
new_options.initial_indent = options.initial_indent;
new_options.subsequent_indent = options.subsequent_indent;
let mut refilled = fill(stripped.unwrap_or(&text), new_options);
if stripped.is_some() {
refilled.push_str(new_line_ending);
}
refilled
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unfill_simple() {
let (text, options) = unfill("foo\nbar");
assert_eq!(text, "foo bar");
assert_eq!(options.width, 3);
assert_eq!(options.line_ending, LineEnding::LF);
}
#[test]
fn unfill_no_new_line() {
let (text, options) = unfill("foo bar");
assert_eq!(text, "foo bar");
assert_eq!(options.width, 7);
assert_eq!(options.line_ending, LineEnding::LF);
}
#[test]
fn unfill_simple_crlf() {
let (text, options) = unfill("foo\r\nbar");
assert_eq!(text, "foo bar");
assert_eq!(options.width, 3);
assert_eq!(options.line_ending, LineEnding::CRLF);
}
#[test]
fn unfill_mixed_new_lines() {
let (text, options) = unfill("foo\r\nbar\nbaz");
assert_eq!(text, "foo bar baz");
assert_eq!(options.width, 3);
assert_eq!(options.line_ending, LineEnding::LF);
}
#[test]
fn test_unfill_consecutive_different_prefix() {
let (text, options) = unfill("foo\n*\n/");
assert_eq!(text, "foo * /");
assert_eq!(options.width, 3);
assert_eq!(options.line_ending, LineEnding::LF);
}
#[test]
fn unfill_trailing_newlines() {
let (text, options) = unfill("foo\nbar\n\n\n");
assert_eq!(text, "foo bar\n");
assert_eq!(options.width, 3);
}
#[test]
fn unfill_mixed_trailing_newlines() {
let (text, options) = unfill("foo\r\nbar\n\r\n\n");
assert_eq!(text, "foo bar\n");
assert_eq!(options.width, 3);
assert_eq!(options.line_ending, LineEnding::LF);
}
#[test]
fn unfill_trailing_crlf() {
let (text, options) = unfill("foo bar\r\n");
assert_eq!(text, "foo bar\r\n");
assert_eq!(options.width, 7);
assert_eq!(options.line_ending, LineEnding::CRLF);
}
#[test]
fn unfill_initial_indent() {
let (text, options) = unfill(" foo\nbar\nbaz");
assert_eq!(text, "foo bar baz");
assert_eq!(options.width, 5);
assert_eq!(options.initial_indent, " ");
}
#[test]
fn unfill_differing_indents() {
let (text, options) = unfill(" foo\n bar\n baz");
assert_eq!(text, "foo bar baz");
assert_eq!(options.width, 7);
assert_eq!(options.initial_indent, " ");
assert_eq!(options.subsequent_indent, " ");
}
#[test]
fn unfill_list_item() {
let (text, options) = unfill("* foo\n bar\n baz");
assert_eq!(text, "foo bar baz");
assert_eq!(options.width, 5);
assert_eq!(options.initial_indent, "* ");
assert_eq!(options.subsequent_indent, " ");
}
#[test]
fn unfill_multiple_char_prefix() {
let (text, options) = unfill(" // foo bar\n // baz\n // quux");
assert_eq!(text, "foo bar baz quux");
assert_eq!(options.width, 14);
assert_eq!(options.initial_indent, " // ");
assert_eq!(options.subsequent_indent, " // ");
}
#[test]
fn unfill_block_quote() {
let (text, options) = unfill("> foo\n> bar\n> baz");
assert_eq!(text, "foo bar baz");
assert_eq!(options.width, 5);
assert_eq!(options.initial_indent, "> ");
assert_eq!(options.subsequent_indent, "> ");
}
#[test]
fn unfill_only_prefixes_issue_466() {
let (text, options) = unfill("######\nfoo");
assert_eq!(text, " foo");
assert_eq!(options.width, 6);
assert_eq!(options.initial_indent, "######");
assert_eq!(options.subsequent_indent, "");
}
#[test]
fn unfill_trailing_newlines_issue_466() {
let (text, options) = unfill("foo\n##\n\n\r");
assert_eq!(text, "foo ## \r");
assert_eq!(options.width, 3);
assert_eq!(options.initial_indent, "");
assert_eq!(options.subsequent_indent, "");
}
#[test]
fn unfill_whitespace() {
assert_eq!(unfill("foo bar").0, "foo bar");
}
#[test]
fn refill_convert_lf_to_crlf() {
let options = Options::new(5).line_ending(LineEnding::CRLF);
assert_eq!(refill("foo\nbar\n", options), "foo\r\nbar\r\n",);
}
#[test]
fn refill_convert_crlf_to_lf() {
let options = Options::new(5).line_ending(LineEnding::LF);
assert_eq!(refill("foo\r\nbar\r\n", options), "foo\nbar\n",);
}
#[test]
fn refill_convert_mixed_newlines() {
let options = Options::new(5).line_ending(LineEnding::CRLF);
assert_eq!(refill("foo\r\nbar\n", options), "foo\r\nbar\r\n",);
}
#[test]
fn refill_defaults_to_lf() {
assert_eq!(refill("foo bar baz", 5), "foo\nbar\nbaz");
}
}