#![doc(html_root_url = "https://docs.rs/textwrap/0.16.0")]
#![forbid(unsafe_code)] #![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![allow(clippy::redundant_field_names)]
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
mod readme_doctest {}
use std::borrow::Cow;
mod indentation;
pub use crate::indentation::{dedent, indent};
mod word_separators;
pub use word_separators::WordSeparator;
pub mod word_splitters;
pub use word_splitters::WordSplitter;
pub mod wrap_algorithms;
pub use wrap_algorithms::WrapAlgorithm;
mod line_ending;
pub use line_ending::LineEnding;
pub mod core;
#[cfg(fuzzing)]
pub mod fuzzing;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct Options<'a> {
pub width: usize,
pub line_ending: LineEnding,
pub initial_indent: &'a str,
pub subsequent_indent: &'a str,
pub break_words: bool,
pub wrap_algorithm: WrapAlgorithm,
pub word_separator: WordSeparator,
pub word_splitter: WordSplitter,
}
impl<'a> From<&'a Options<'a>> for Options<'a> {
fn from(options: &'a Options<'a>) -> Self {
Self {
width: options.width,
line_ending: options.line_ending,
initial_indent: options.initial_indent,
subsequent_indent: options.subsequent_indent,
break_words: options.break_words,
word_separator: options.word_separator,
wrap_algorithm: options.wrap_algorithm,
word_splitter: options.word_splitter.clone(),
}
}
}
impl<'a> From<usize> for Options<'a> {
fn from(width: usize) -> Self {
Options::new(width)
}
}
impl<'a> Options<'a> {
pub const fn new(width: usize) -> Self {
Options {
width,
line_ending: LineEnding::LF,
initial_indent: "",
subsequent_indent: "",
break_words: true,
word_separator: WordSeparator::new(),
wrap_algorithm: WrapAlgorithm::new(),
word_splitter: WordSplitter::HyphenSplitter,
}
}
#[cfg(feature = "terminal_size")]
pub fn with_termwidth() -> Self {
Self::new(termwidth())
}
pub fn line_ending(self, line_ending: LineEnding) -> Self {
Options {
line_ending,
..self
}
}
pub fn initial_indent(self, indent: &'a str) -> Self {
Options {
initial_indent: indent,
..self
}
}
pub fn subsequent_indent(self, indent: &'a str) -> Self {
Options {
subsequent_indent: indent,
..self
}
}
pub fn break_words(self, setting: bool) -> Self {
Options {
break_words: setting,
..self
}
}
pub fn word_separator(self, word_separator: WordSeparator) -> Options<'a> {
Options {
width: self.width,
line_ending: self.line_ending,
initial_indent: self.initial_indent,
subsequent_indent: self.subsequent_indent,
break_words: self.break_words,
word_separator: word_separator,
wrap_algorithm: self.wrap_algorithm,
word_splitter: self.word_splitter,
}
}
pub fn wrap_algorithm(self, wrap_algorithm: WrapAlgorithm) -> Options<'a> {
Options {
width: self.width,
line_ending: self.line_ending,
initial_indent: self.initial_indent,
subsequent_indent: self.subsequent_indent,
break_words: self.break_words,
word_separator: self.word_separator,
wrap_algorithm: wrap_algorithm,
word_splitter: self.word_splitter,
}
}
pub fn word_splitter(self, word_splitter: WordSplitter) -> Options<'a> {
Options {
width: self.width,
line_ending: self.line_ending,
initial_indent: self.initial_indent,
subsequent_indent: self.subsequent_indent,
break_words: self.break_words,
word_separator: self.word_separator,
wrap_algorithm: self.wrap_algorithm,
word_splitter,
}
}
}
#[cfg(feature = "terminal_size")]
pub fn termwidth() -> usize {
terminal_size::terminal_size().map_or(80, |(terminal_size::Width(w), _)| w.into())
}
pub fn fill<'a, Opt>(text: &str, width_or_options: Opt) -> String
where
Opt: Into<Options<'a>>,
{
let options = width_or_options.into();
if text.len() < options.width && !text.contains('\n') && options.initial_indent.is_empty() {
String::from(text.trim_end_matches(' '))
} else {
fill_slow_path(text, options)
}
}
fn fill_slow_path(text: &str, options: Options<'_>) -> String {
let mut result = String::with_capacity(text.len());
let line_ending_str = options.line_ending.as_str();
for (i, line) in wrap(text, options).iter().enumerate() {
if i > 0 {
result.push_str(line_ending_str);
}
result.push_str(line);
}
result
}
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, core::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 line_ending::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
}
pub fn wrap<'a, Opt>(text: &str, width_or_options: Opt) -> Vec<Cow<'_, str>>
where
Opt: Into<Options<'a>>,
{
let options: Options = width_or_options.into();
let line_ending_str = options.line_ending.as_str();
let mut lines = Vec::new();
for line in text.split(line_ending_str) {
wrap_single_line(line, &options, &mut lines);
}
lines
}
fn wrap_single_line<'a>(line: &'a str, options: &Options<'_>, lines: &mut Vec<Cow<'a, str>>) {
let indent = if lines.is_empty() {
options.initial_indent
} else {
options.subsequent_indent
};
if line.len() < options.width && indent.is_empty() {
lines.push(Cow::from(line.trim_end_matches(' ')));
} else {
wrap_single_line_slow_path(line, options, lines)
}
}
fn wrap_single_line_slow_path<'a>(
line: &'a str,
options: &Options<'_>,
lines: &mut Vec<Cow<'a, str>>,
) {
let initial_width = options
.width
.saturating_sub(core::display_width(options.initial_indent));
let subsequent_width = options
.width
.saturating_sub(core::display_width(options.subsequent_indent));
let line_widths = [initial_width, subsequent_width];
let words = options.word_separator.find_words(line);
let split_words = word_splitters::split_words(words, &options.word_splitter);
let broken_words = if options.break_words {
let mut broken_words = core::break_words(split_words, line_widths[1]);
if !options.initial_indent.is_empty() {
broken_words.insert(0, core::Word::from(""));
}
broken_words
} else {
split_words.collect::<Vec<_>>()
};
let wrapped_words = options.wrap_algorithm.wrap(&broken_words, &line_widths);
let mut idx = 0;
for words in wrapped_words {
let last_word = match words.last() {
None => {
lines.push(Cow::from(""));
continue;
}
Some(word) => word,
};
let len = words
.iter()
.map(|word| word.len() + word.whitespace.len())
.sum::<usize>()
- last_word.whitespace.len();
let mut result = if lines.is_empty() && !options.initial_indent.is_empty() {
Cow::Owned(options.initial_indent.to_owned())
} else if !lines.is_empty() && !options.subsequent_indent.is_empty() {
Cow::Owned(options.subsequent_indent.to_owned())
} else {
Cow::from("")
};
result += &line[idx..idx + len];
if !last_word.penalty.is_empty() {
result.to_mut().push_str(last_word.penalty);
}
lines.push(result);
idx += len + last_word.whitespace.len();
}
}
pub fn wrap_columns<'a, Opt>(
text: &str,
columns: usize,
total_width_or_options: Opt,
left_gap: &str,
middle_gap: &str,
right_gap: &str,
) -> Vec<String>
where
Opt: Into<Options<'a>>,
{
assert!(columns > 0);
let mut options: Options = total_width_or_options.into();
let inner_width = options
.width
.saturating_sub(core::display_width(left_gap))
.saturating_sub(core::display_width(right_gap))
.saturating_sub(core::display_width(middle_gap) * (columns - 1));
let column_width = std::cmp::max(inner_width / columns, 1);
options.width = column_width;
let last_column_padding = " ".repeat(inner_width % column_width);
let wrapped_lines = wrap(text, options);
let lines_per_column =
wrapped_lines.len() / columns + usize::from(wrapped_lines.len() % columns > 0);
let mut lines = Vec::new();
for line_no in 0..lines_per_column {
let mut line = String::from(left_gap);
for column_no in 0..columns {
match wrapped_lines.get(line_no + column_no * lines_per_column) {
Some(column_line) => {
line.push_str(column_line);
line.push_str(&" ".repeat(column_width - core::display_width(column_line)));
}
None => {
line.push_str(&" ".repeat(column_width));
}
}
if column_no == columns - 1 {
line.push_str(&last_column_padding);
} else {
line.push_str(middle_gap);
}
}
line.push_str(right_gap);
lines.push(line);
}
lines
}
pub fn fill_inplace(text: &mut String, width: usize) {
let mut indices = Vec::new();
let mut offset = 0;
for line in text.split('\n') {
let words = WordSeparator::AsciiSpace
.find_words(line)
.collect::<Vec<_>>();
let wrapped_words = wrap_algorithms::wrap_first_fit(&words, &[width as f64]);
let mut line_offset = offset;
for words in &wrapped_words[..wrapped_words.len() - 1] {
let line_len = words
.iter()
.map(|word| word.len() + word.whitespace.len())
.sum::<usize>();
line_offset += line_len;
indices.push(line_offset - 1);
}
offset += line.len() + 1;
}
let mut bytes = std::mem::take(text).into_bytes();
for idx in indices {
bytes[idx] = b'\n';
}
*text = String::from_utf8(bytes).unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "hyphenation")]
use hyphenation::{Language, Load, Standard};
#[test]
fn options_agree_with_usize() {
let opt_usize = Options::from(42_usize);
let opt_options = Options::new(42);
assert_eq!(opt_usize.width, opt_options.width);
assert_eq!(opt_usize.initial_indent, opt_options.initial_indent);
assert_eq!(opt_usize.subsequent_indent, opt_options.subsequent_indent);
assert_eq!(opt_usize.break_words, opt_options.break_words);
assert_eq!(
opt_usize.word_splitter.split_points("hello-world"),
opt_options.word_splitter.split_points("hello-world")
);
}
#[test]
fn no_wrap() {
assert_eq!(wrap("foo", 10), vec!["foo"]);
}
#[test]
fn wrap_simple() {
assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]);
}
#[test]
fn to_be_or_not() {
assert_eq!(
wrap(
"To be, or not to be, that is the question.",
Options::new(10).wrap_algorithm(WrapAlgorithm::FirstFit)
),
vec!["To be, or", "not to be,", "that is", "the", "question."]
);
}
#[test]
fn multiple_words_on_first_line() {
assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]);
}
#[test]
fn long_word() {
assert_eq!(wrap("foo", 0), vec!["f", "o", "o"]);
}
#[test]
fn long_words() {
assert_eq!(wrap("foo bar", 0), vec!["f", "o", "o", "b", "a", "r"]);
}
#[test]
fn max_width() {
assert_eq!(wrap("foo bar", usize::MAX), vec!["foo bar"]);
let text = "Hello there! This is some English text. \
It should not be wrapped given the extents below.";
assert_eq!(wrap(text, usize::MAX), vec![text]);
}
#[test]
fn leading_whitespace() {
assert_eq!(wrap(" foo bar", 6), vec![" foo", "bar"]);
}
#[test]
fn leading_whitespace_empty_first_line() {
assert_eq!(wrap(" foobar baz", 6), vec!["", "foobar", "baz"]);
}
#[test]
fn trailing_whitespace() {
assert_eq!(wrap("foo bar baz ", 5), vec!["foo", "bar", "baz"]);
}
#[test]
fn issue_99() {
assert_eq!(
wrap("aaabbbccc x yyyzzzwww", 9),
vec!["aaabbbccc", "x", "yyyzzzwww"]
);
}
#[test]
fn issue_129() {
let options = Options::new(1).word_separator(WordSeparator::AsciiSpace);
assert_eq!(wrap("x – x", options), vec!["x", "–", "x"]);
}
#[test]
fn wide_character_handling() {
assert_eq!(wrap("Hello, World!", 15), vec!["Hello, World!"]);
assert_eq!(
wrap(
"Hello, World!",
Options::new(15).word_separator(WordSeparator::AsciiSpace)
),
vec!["Hello,", "World!"]
);
#[cfg(feature = "unicode-linebreak")]
assert_eq!(
wrap(
"Hello, World!",
Options::new(15).word_separator(WordSeparator::UnicodeBreakProperties),
),
vec!["Hello, W", "orld!"]
);
}
#[test]
fn empty_line_is_indented() {
let options = Options::new(10).initial_indent("!!!");
assert_eq!(fill("", &options), "!!!");
}
#[test]
fn indent_single_line() {
let options = Options::new(10).initial_indent(">>>"); assert_eq!(fill("foo", &options), ">>>foo");
}
#[test]
fn indent_first_emoji() {
let options = Options::new(10).initial_indent("👉👉");
assert_eq!(
wrap("x x x x x x x x x x x x x", &options),
vec!["👉👉x x x", "x x x x x", "x x x x x"]
);
}
#[test]
fn indent_multiple_lines() {
let options = Options::new(6).initial_indent("* ").subsequent_indent(" ");
assert_eq!(
wrap("foo bar baz", &options),
vec!["* foo", " bar", " baz"]
);
}
#[test]
fn only_initial_indent_multiple_lines() {
let options = Options::new(10).initial_indent(" ");
assert_eq!(wrap("foo\nbar\nbaz", &options), vec![" foo", "bar", "baz"]);
}
#[test]
fn only_subsequent_indent_multiple_lines() {
let options = Options::new(10).subsequent_indent(" ");
assert_eq!(
wrap("foo\nbar\nbaz", &options),
vec!["foo", " bar", " baz"]
);
}
#[test]
fn indent_break_words() {
let options = Options::new(5).initial_indent("* ").subsequent_indent(" ");
assert_eq!(wrap("foobarbaz", &options), vec!["* foo", " bar", " baz"]);
}
#[test]
fn initial_indent_break_words() {
let options = Options::new(5).initial_indent("-->");
assert_eq!(wrap("foobarbaz", &options), vec!["-->", "fooba", "rbaz"]);
}
#[test]
fn hyphens() {
assert_eq!(wrap("foo-bar", 5), vec!["foo-", "bar"]);
}
#[test]
fn trailing_hyphen() {
let options = Options::new(5).break_words(false);
assert_eq!(wrap("foobar-", &options), vec!["foobar-"]);
}
#[test]
fn multiple_hyphens() {
assert_eq!(wrap("foo-bar-baz", 5), vec!["foo-", "bar-", "baz"]);
}
#[test]
fn hyphens_flag() {
let options = Options::new(5).break_words(false);
assert_eq!(
wrap("The --foo-bar flag.", &options),
vec!["The", "--foo-", "bar", "flag."]
);
}
#[test]
fn repeated_hyphens() {
let options = Options::new(4).break_words(false);
assert_eq!(wrap("foo--bar", &options), vec!["foo--bar"]);
}
#[test]
fn hyphens_alphanumeric() {
assert_eq!(wrap("Na2-CH4", 5), vec!["Na2-", "CH4"]);
}
#[test]
fn hyphens_non_alphanumeric() {
let options = Options::new(5).break_words(false);
assert_eq!(wrap("foo(-)bar", &options), vec!["foo(-)bar"]);
}
#[test]
fn multiple_splits() {
assert_eq!(wrap("foo-bar-baz", 9), vec!["foo-bar-", "baz"]);
}
#[test]
fn forced_split() {
let options = Options::new(5).break_words(false);
assert_eq!(wrap("foobar-baz", &options), vec!["foobar-", "baz"]);
}
#[test]
fn multiple_unbroken_words_issue_193() {
let options = Options::new(3).break_words(false);
assert_eq!(
wrap("small large tiny", &options),
vec!["small", "large", "tiny"]
);
assert_eq!(
wrap("small large tiny", &options),
vec!["small", "large", "tiny"]
);
}
#[test]
fn very_narrow_lines_issue_193() {
let options = Options::new(1).break_words(false);
assert_eq!(wrap("fooo x y", &options), vec!["fooo", "x", "y"]);
assert_eq!(wrap("fooo x y", &options), vec!["fooo", "x", "y"]);
}
#[test]
fn simple_hyphens() {
let options = Options::new(8).word_splitter(WordSplitter::HyphenSplitter);
assert_eq!(wrap("foo bar-baz", &options), vec!["foo bar-", "baz"]);
}
#[test]
fn no_hyphenation() {
let options = Options::new(8).word_splitter(WordSplitter::NoHyphenation);
assert_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]);
}
#[test]
#[cfg(feature = "hyphenation")]
fn auto_hyphenation_double_hyphenation() {
let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
let options = Options::new(10);
assert_eq!(
wrap("Internationalization", &options),
vec!["Internatio", "nalization"]
);
let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary));
assert_eq!(
wrap("Internationalization", &options),
vec!["Interna-", "tionaliza-", "tion"]
);
}
#[test]
#[cfg(feature = "hyphenation")]
fn auto_hyphenation_issue_158() {
let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
let options = Options::new(10);
assert_eq!(
wrap("participation is the key to success", &options),
vec!["participat", "ion is", "the key to", "success"]
);
let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary));
assert_eq!(
wrap("participation is the key to success", &options),
vec!["partici-", "pation is", "the key to", "success"]
);
}
#[test]
#[cfg(feature = "hyphenation")]
fn split_len_hyphenation() {
let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
let options = Options::new(15).word_splitter(WordSplitter::Hyphenation(dictionary));
assert_eq!(
wrap("garbage collection", &options),
vec!["garbage col-", "lection"]
);
}
#[test]
#[cfg(feature = "hyphenation")]
fn borrowed_lines() {
use std::borrow::Cow::{Borrowed, Owned};
let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary));
let lines = wrap("Internationalization", &options);
assert_eq!(lines, vec!["Interna-", "tionaliza-", "tion"]);
if let Borrowed(s) = lines[0] {
assert!(false, "should not have been borrowed: {:?}", s);
}
if let Borrowed(s) = lines[1] {
assert!(false, "should not have been borrowed: {:?}", s);
}
if let Owned(ref s) = lines[2] {
assert!(false, "should not have been owned: {:?}", s);
}
}
#[test]
#[cfg(feature = "hyphenation")]
fn auto_hyphenation_with_hyphen() {
let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
let options = Options::new(8).break_words(false);
assert_eq!(
wrap("over-caffinated", &options),
vec!["over-", "caffinated"]
);
let options = options.word_splitter(WordSplitter::Hyphenation(dictionary));
assert_eq!(
wrap("over-caffinated", &options),
vec!["over-", "caffi-", "nated"]
);
}
#[test]
fn break_words() {
assert_eq!(wrap("foobarbaz", 3), vec!["foo", "bar", "baz"]);
}
#[test]
fn break_words_wide_characters() {
let options = Options::new(5).word_separator(WordSeparator::AsciiSpace);
assert_eq!(wrap("Hello", options), vec!["He", "ll", "o"]);
}
#[test]
fn break_words_zero_width() {
assert_eq!(wrap("foobar", 0), vec!["f", "o", "o", "b", "a", "r"]);
}
#[test]
fn break_long_first_word() {
assert_eq!(wrap("testx y", 4), vec!["test", "x y"]);
}
#[test]
fn break_words_line_breaks() {
assert_eq!(fill("ab\ncdefghijkl", 5), "ab\ncdefg\nhijkl");
assert_eq!(fill("abcdefgh\nijkl", 5), "abcde\nfgh\nijkl");
}
#[test]
fn break_words_empty_lines() {
assert_eq!(
fill("foo\nbar", &Options::new(2).break_words(false)),
"foo\nbar"
);
}
#[test]
fn preserve_line_breaks() {
assert_eq!(fill("", 80), "");
assert_eq!(fill("\n", 80), "\n");
assert_eq!(fill("\n\n\n", 80), "\n\n\n");
assert_eq!(fill("test\n", 80), "test\n");
assert_eq!(fill("test\n\na\n\n", 80), "test\n\na\n\n");
assert_eq!(
fill(
"1 3 5 7\n1 3 5 7",
Options::new(7).wrap_algorithm(WrapAlgorithm::FirstFit)
),
"1 3 5 7\n1 3 5 7"
);
assert_eq!(
fill(
"1 3 5 7\n1 3 5 7",
Options::new(5).wrap_algorithm(WrapAlgorithm::FirstFit)
),
"1 3 5\n7\n1 3 5\n7"
);
}
#[test]
fn preserve_line_breaks_with_whitespace() {
assert_eq!(fill(" ", 80), "");
assert_eq!(fill(" \n ", 80), "\n");
assert_eq!(fill(" \n \n \n ", 80), "\n\n\n");
}
#[test]
fn non_breaking_space() {
let options = Options::new(5).break_words(false);
assert_eq!(fill("foo bar baz", &options), "foo bar baz");
}
#[test]
fn non_breaking_hyphen() {
let options = Options::new(5).break_words(false);
assert_eq!(fill("foo‑bar‑baz", &options), "foo‑bar‑baz");
}
#[test]
fn fill_simple() {
assert_eq!(fill("foo bar baz", 10), "foo bar\nbaz");
}
#[test]
fn fill_colored_text() {
let green_hello = "\u{1b}[0m\u{1b}[32mHello\u{1b}[0m";
let blue_world = "\u{1b}[0m\u{1b}[34mWorld!\u{1b}[0m";
assert_eq!(
fill(&(String::from(green_hello) + " " + blue_world), 6),
String::from(green_hello) + "\n" + blue_world
);
}
#[test]
fn fill_unicode_boundary() {
fill("\u{1b}!Ͽ", 10);
}
#[test]
fn fill_inplace_empty() {
let mut text = String::from("");
fill_inplace(&mut text, 80);
assert_eq!(text, "");
}
#[test]
fn fill_inplace_simple() {
let mut text = String::from("foo bar baz");
fill_inplace(&mut text, 10);
assert_eq!(text, "foo bar\nbaz");
}
#[test]
fn fill_inplace_multiple_lines() {
let mut text = String::from("Some text to wrap over multiple lines");
fill_inplace(&mut text, 12);
assert_eq!(text, "Some text to\nwrap over\nmultiple\nlines");
}
#[test]
fn fill_inplace_long_word() {
let mut text = String::from("Internationalization is hard");
fill_inplace(&mut text, 10);
assert_eq!(text, "Internationalization\nis hard");
}
#[test]
fn fill_inplace_no_hyphen_splitting() {
let mut text = String::from("A well-chosen example");
fill_inplace(&mut text, 10);
assert_eq!(text, "A\nwell-chosen\nexample");
}
#[test]
fn fill_inplace_newlines() {
let mut text = String::from("foo bar\n\nbaz\n\n\n");
fill_inplace(&mut text, 10);
assert_eq!(text, "foo bar\n\nbaz\n\n\n");
}
#[test]
fn fill_inplace_newlines_reset_line_width() {
let mut text = String::from("1 3 5\n1 3 5 7 9\n1 3 5 7 9 1 3");
fill_inplace(&mut text, 10);
assert_eq!(text, "1 3 5\n1 3 5 7 9\n1 3 5 7 9\n1 3");
}
#[test]
fn fill_inplace_leading_whitespace() {
let mut text = String::from(" foo bar baz");
fill_inplace(&mut text, 10);
assert_eq!(text, " foo bar\nbaz");
}
#[test]
fn fill_inplace_trailing_whitespace() {
let mut text = String::from("foo bar baz ");
fill_inplace(&mut text, 10);
assert_eq!(text, "foo bar\nbaz ");
}
#[test]
fn fill_inplace_interior_whitespace() {
let mut text = String::from("foo bar baz");
fill_inplace(&mut text, 10);
assert_eq!(text, "foo bar \nbaz");
}
#[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 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");
}
#[test]
fn wrap_columns_empty_text() {
assert_eq!(wrap_columns("", 1, 10, "| ", "", " |"), vec!["| |"]);
}
#[test]
fn wrap_columns_single_column() {
assert_eq!(
wrap_columns("Foo", 3, 30, "| ", " | ", " |"),
vec!["| Foo | | |"]
);
}
#[test]
fn wrap_columns_uneven_columns() {
assert_eq!(
wrap_columns("Foo Bar Baz Quux", 4, 21, "|", "|", "|"),
vec!["|Foo |Bar |Baz |Quux|"]
);
assert_eq!(
wrap_columns("Foo Bar Baz Quux", 4, 24, "|", "|", "|"),
vec!["|Foo |Bar |Baz |Quux |"]
);
assert_eq!(
wrap_columns("Foo Bar Baz Quux", 4, 25, "|", "|", "|"),
vec!["|Foo |Bar |Baz |Quux |"]
);
}
#[test]
#[cfg(feature = "unicode-width")]
fn wrap_columns_with_emojis() {
assert_eq!(
wrap_columns(
"Words and a few emojis 😍 wrapped in ⓶ columns",
2,
30,
"✨ ",
" ⚽ ",
" 👀"
),
vec![
"✨ Words ⚽ wrapped in 👀",
"✨ and a few ⚽ ⓶ columns 👀",
"✨ emojis 😍 ⚽ 👀"
]
);
}
#[test]
fn wrap_columns_big_gaps() {
assert_eq!(
wrap_columns("xyz", 2, 10, "----> ", " !!! ", " <----"),
vec![
"----> x !!! z <----", "----> y !!! <----"
]
);
}
#[test]
#[should_panic]
fn wrap_columns_panic_with_zero_columns() {
wrap_columns("", 0, 10, "", "", "");
}
}