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
7 pub(crate) mod core;
8 #[cfg(feature = "wrap_help")]
9 pub(crate) mod word_separators;
10 #[cfg(feature = "wrap_help")]
11 pub(crate) mod wrap_algorithms;
12
13 #[cfg(feature = "wrap_help")]
wrap(content: &str, hard_width: usize) -> String14 pub(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"))]
wrap(content: &str, _hard_width: usize) -> String26 pub(crate) fn wrap(content: &str, _hard_width: usize) -> String {
27 content.to_owned()
28 }
29
30 #[cfg(test)]
31 #[cfg(feature = "wrap_help")]
32 mod test {
33 /// Compatibility shim to keep textwrap's tests
wrap(content: &str, hard_width: usize) -> Vec<String>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]
no_wrap()43 fn no_wrap() {
44 assert_eq!(wrap("foo", 10), vec!["foo"]);
45 }
46
47 #[test]
wrap_simple()48 fn wrap_simple() {
49 assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]);
50 }
51
52 #[test]
to_be_or_not()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]
multiple_words_on_first_line()61 fn multiple_words_on_first_line() {
62 assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]);
63 }
64
65 #[test]
long_word()66 fn long_word() {
67 assert_eq!(wrap("foo", 0), vec!["foo"]);
68 }
69
70 #[test]
long_words()71 fn long_words() {
72 assert_eq!(wrap("foo bar", 0), vec!["foo", "bar"]);
73 }
74
75 #[test]
max_width()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]
leading_whitespace()85 fn leading_whitespace() {
86 assert_eq!(wrap(" foo bar", 6), vec![" foo", "bar"]);
87 }
88
89 #[test]
leading_whitespace_empty_first_line()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]
trailing_whitespace()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]
issue_99()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]
issue_129()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