1//! Fork of `textwrap` crate
2//!
3//! Benefits of forking:
4//! - Pull in only what we need rather than relying on the compiler to remove what we don't need
5//! - `LineWrapper` is able to incrementally wrap which will help with `StyledStr
6
7pub(crate) mod core;
8#[cfg(feature = "wrap_help")]
9pub(crate) mod word_separators;
10#[cfg(feature = "wrap_help")]
11pub(crate) mod wrap_algorithms;
12
13#[cfg(feature = "wrap_help")]
14pub(crate) fn wrap(content: &str, hard_width: usize) -> String {
15 let mut wrapper = wrap_algorithms::LineWrapper::new(hard_width);
16 let mut total = Vec::new();
17 for line in content.split_inclusive('\n') {
18 wrapper.reset();
19 let line = word_separators::find_words_ascii_space(line).collect::<Vec<_>>();
20 total.extend(wrapper.wrap(line));
21 }
22 total.join("")
23}
24
25#[cfg(not(feature = "wrap_help"))]
26pub(crate) fn wrap(content: &str, _hard_width: usize) -> String {
27 content.to_owned()
28}
29
30#[cfg(test)]
31#[cfg(feature = "wrap_help")]
32mod test {
33 /// Compatibility shim to keep textwrap's tests
34 fn wrap(content: &str, hard_width: usize) -> Vec<String> {
35 super::wrap(content, hard_width)
36 .trim_end()
37 .split('\n')
38 .map(|s| s.to_owned())
39 .collect::<Vec<_>>()
40 }
41
42 #[test]
43 fn no_wrap() {
44 assert_eq!(wrap("foo", 10), vec!["foo"]);
45 }
46
47 #[test]
48 fn wrap_simple() {
49 assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]);
50 }
51
52 #[test]
53 fn to_be_or_not() {
54 assert_eq!(
55 wrap("To be, or not to be, that is the question.", 10),
56 vec!["To be, or", "not to be,", "that is", "the", "question."]
57 );
58 }
59
60 #[test]
61 fn multiple_words_on_first_line() {
62 assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]);
63 }
64
65 #[test]
66 fn long_word() {
67 assert_eq!(wrap("foo", 0), vec!["foo"]);
68 }
69
70 #[test]
71 fn long_words() {
72 assert_eq!(wrap("foo bar", 0), vec!["foo", "bar"]);
73 }
74
75 #[test]
76 fn max_width() {
77 assert_eq!(wrap("foo bar", usize::MAX), vec!["foo bar"]);
78
79 let text = "Hello there! This is some English text. \
80 It should not be wrapped given the extents below.";
81 assert_eq!(wrap(text, usize::MAX), vec![text]);
82 }
83
84 #[test]
85 fn leading_whitespace() {
86 assert_eq!(wrap(" foo bar", 6), vec![" foo", " bar"]);
87 }
88
89 #[test]
90 fn leading_whitespace_empty_first_line() {
91 // If there is no space for the first word, the first line
92 // will be empty. This is because the string is split into
93 // words like [" ", "foobar ", "baz"], which puts "foobar " on
94 // the second line. We never output trailing whitespace
95 assert_eq!(wrap(" foobar baz", 6), vec!["", " foobar", " baz"]);
96 }
97
98 #[test]
99 fn trailing_whitespace() {
100 // Whitespace is only significant inside a line. After a line
101 // gets too long and is broken, the first word starts in
102 // column zero and is not indented.
103 assert_eq!(wrap("foo bar baz ", 5), vec!["foo", "bar", "baz"]);
104 }
105
106 #[test]
107 fn issue_99() {
108 // We did not reset the in_whitespace flag correctly and did
109 // not handle single-character words after a line break.
110 assert_eq!(
111 wrap("aaabbbccc x yyyzzzwww", 9),
112 vec!["aaabbbccc", "x", "yyyzzzwww"]
113 );
114 }
115
116 #[test]
117 fn issue_129() {
118 // The dash is an em-dash which takes up four bytes. We used
119 // to panic since we tried to index into the character.
120 assert_eq!(wrap("x – x", 1), vec!["x", "–", "x"]);
121 }
122}
123