Skip to main content

textwrap/
wrap.rs

1//! Functions for wrapping text.
2
3use alloc::borrow::{Cow, ToOwned};
4use alloc::vec::Vec;
5
6use crate::Options;
7use crate::core::{Word, break_words, display_width};
8use crate::word_splitters::split_words;
9
10/// Wrap a line of text at a given width.
11///
12/// The result is a vector of lines. Each line is of type [`Cow<'_,
13/// str>`](Cow), which means that the line will borrow from the input
14/// `&str` if possible. The lines do not have trailing whitespace,
15/// including a final `'\n'`. Please use [`fill()`](crate::fill()) if
16/// you need a [`String`] instead.
17///
18/// The easiest way to use this function is to pass an integer for
19/// `width_or_options`:
20///
21/// ```
22/// use textwrap::wrap;
23///
24/// let lines = wrap("Memory safety without garbage collection.", 15);
25/// assert_eq!(lines, &[
26///     "Memory safety",
27///     "without garbage",
28///     "collection.",
29/// ]);
30/// ```
31///
32/// If you need to customize the wrapping, you can pass an [`Options`]
33/// instead of an `usize`:
34///
35/// ```
36/// use textwrap::{wrap, Options};
37///
38/// let options = Options::new(15)
39///     .initial_indent("- ")
40///     .subsequent_indent("  ");
41/// let lines = wrap("Memory safety without garbage collection.", &options);
42/// assert_eq!(lines, &[
43///     "- Memory safety",
44///     "  without",
45///     "  garbage",
46///     "  collection.",
47/// ]);
48/// ```
49///
50/// # Optimal-Fit Wrapping
51///
52/// By default, `wrap` will try to ensure an even right margin by
53/// finding breaks that avoid short lines. We call this an
54/// “optimal-fit algorithm” since the line breaks are computed by
55/// considering all possible line breaks. The alternative is a
56/// “first-fit algorithm” which simply accumulates words until they no
57/// longer fit on the line.
58///
59/// As an example, using the first-fit algorithm to wrap the famous
60/// Hamlet quote “To be, or not to be: that is the question” in a
61/// narrow column with room for only 10 characters looks like this:
62///
63/// ```
64/// # use textwrap::{WrapAlgorithm::FirstFit, Options, wrap};
65/// #
66/// # let lines = wrap("To be, or not to be: that is the question",
67/// #                  Options::new(10).wrap_algorithm(FirstFit));
68/// # assert_eq!(lines.join("\n") + "\n", "\
69/// To be, or
70/// not to be:
71/// that is
72/// the
73/// question
74/// # ");
75/// ```
76///
77/// Notice how the second to last line is quite narrow because
78/// “question” was too large to fit? The greedy first-fit algorithm
79/// doesn’t look ahead, so it has no other option than to put
80/// “question” onto its own line.
81///
82/// With the optimal-fit wrapping algorithm, the previous lines are
83/// shortened slightly in order to make the word “is” move onto the
84/// second to last line:
85///
86/// ```
87/// # #[cfg(feature = "smawk")] {
88/// # use textwrap::{Options, WrapAlgorithm, wrap};
89/// #
90/// # let lines = wrap(
91/// #     "To be, or not to be: that is the question",
92/// #     Options::new(10).wrap_algorithm(WrapAlgorithm::new_optimal_fit())
93/// # );
94/// # assert_eq!(lines.join("\n") + "\n", "\
95/// To be,
96/// or not to
97/// be: that
98/// is the
99/// question
100/// # "); }
101/// ```
102///
103/// Please see [`WrapAlgorithm`](crate::WrapAlgorithm) for details on
104/// the choices.
105///
106/// # Examples
107///
108/// The returned iterator yields lines of type `Cow<'_, str>`. If
109/// possible, the wrapped lines will borrow from the input string. For
110/// example, with hanging indentation, the first line can borrow from
111/// the input, but the subsequent lines become owned strings:
112///
113/// ```
114/// use std::borrow::Cow::{Borrowed, Owned};
115/// use textwrap::{wrap, Options};
116///
117/// let options = Options::new(15).subsequent_indent("....");
118/// let lines = wrap("Wrapping text all day long.", &options);
119/// let annotated = lines
120///     .iter()
121///     .map(|line| match line {
122///         Borrowed(text) => format!("[Borrowed] {}", text),
123///         Owned(text) => format!("[Owned]    {}", text),
124///     })
125///     .collect::<Vec<_>>();
126/// assert_eq!(
127///     annotated,
128///     &[
129///         "[Borrowed] Wrapping text",
130///         "[Owned]    ....all day",
131///         "[Owned]    ....long.",
132///     ]
133/// );
134/// ```
135///
136/// ## Leading and Trailing Whitespace
137///
138/// As a rule, leading whitespace (indentation) is preserved and
139/// trailing whitespace is discarded.
140///
141/// In more detail, when wrapping words into lines, words are found
142/// by splitting the input text on space characters. One or more
143/// spaces (shown here as “␣”) are attached to the end of each word:
144///
145/// ```text
146/// "Foo␣␣␣bar␣baz" -> ["Foo␣␣␣", "bar␣", "baz"]
147/// ```
148///
149/// These words are then put into lines. The interword whitespace is
150/// preserved, unless the lines are wrapped so that the `"Foo␣␣␣"`
151/// word falls at the end of a line:
152///
153/// ```
154/// use textwrap::wrap;
155///
156/// assert_eq!(wrap("Foo   bar baz", 10), vec!["Foo   bar", "baz"]);
157/// assert_eq!(wrap("Foo   bar baz", 8), vec!["Foo", "bar baz"]);
158/// ```
159///
160/// Notice how the trailing whitespace is removed in both cases: in the
161/// first example, `"bar␣"` becomes `"bar"` and in the second case
162/// `"Foo␣␣␣"` becomes `"Foo"`.
163///
164/// Leading whitespace is preserved when the following word fits on
165/// the first line. To understand this, consider how words are found
166/// in a text with leading spaces:
167///
168/// ```text
169/// "␣␣foo␣bar" -> ["␣␣", "foo␣", "bar"]
170/// ```
171///
172/// When put into lines, the indentation is preserved if `"foo"` fits
173/// on the first line, otherwise you end up with an empty line:
174///
175/// ```
176/// use textwrap::wrap;
177///
178/// assert_eq!(wrap("  foo bar", 8), vec!["  foo", "bar"]);
179/// assert_eq!(wrap("  foo bar", 4), vec!["", "foo", "bar"]);
180/// ```
181pub fn wrap<'a, Opt>(text: &str, width_or_options: Opt) -> Vec<Cow<'_, str>>
182where
183    Opt: Into<Options<'a>>,
184{
185    let options: Options = width_or_options.into();
186    let line_ending_str = options.line_ending.as_str();
187
188    let mut lines = Vec::new();
189    for line in text.split(line_ending_str) {
190        wrap_single_line(line, &options, &mut lines);
191    }
192
193    lines
194}
195
196pub(crate) fn wrap_single_line<'a>(
197    line: &'a str,
198    options: &Options<'_>,
199    lines: &mut Vec<Cow<'a, str>>,
200) {
201    let indent = if lines.is_empty() {
202        options.initial_indent
203    } else {
204        options.subsequent_indent
205    };
206    if line.len() < options.width && indent.is_empty() {
207        if options.preserve_trailing_space {
208            lines.push(Cow::from(line));
209        } else {
210            lines.push(Cow::from(line.trim_end_matches(' ')));
211        }
212    } else {
213        wrap_single_line_slow_path(line, options, lines)
214    }
215}
216
217/// Wrap a single line of text.
218///
219/// This is taken when `line` is longer than `options.width`.
220pub(crate) fn wrap_single_line_slow_path<'a>(
221    line: &'a str,
222    options: &Options<'_>,
223    lines: &mut Vec<Cow<'a, str>>,
224) {
225    let initial_width = options
226        .width
227        .saturating_sub(display_width(options.initial_indent));
228    let subsequent_width = options
229        .width
230        .saturating_sub(display_width(options.subsequent_indent));
231    let line_widths = [initial_width, subsequent_width];
232
233    let words = options.word_separator.find_words(line);
234    let split_words = split_words(words, &options.word_splitter);
235    let broken_words = if options.break_words {
236        let mut broken_words = break_words(split_words, line_widths[1]);
237        if !options.initial_indent.is_empty() {
238            // Without this, the first word will always go into the
239            // first line. However, since we break words based on the
240            // _second_ line width, it can be wrong to unconditionally
241            // put the first word onto the first line. An empty
242            // zero-width word fixed this.
243            broken_words.insert(0, Word::from(""));
244        }
245        broken_words
246    } else {
247        split_words.collect::<Vec<_>>()
248    };
249
250    let wrapped_words = options.wrap_algorithm.wrap(&broken_words, &line_widths);
251
252    let mut idx = 0;
253    for words in wrapped_words {
254        let last_word = match words.last() {
255            None => {
256                lines.push(Cow::from(""));
257                continue;
258            }
259            Some(word) => word,
260        };
261
262        // We assume here that all words are contiguous in `line`.
263        // That is, the sum of their lengths should add up to the
264        // length of `line`.
265        let len = words
266            .iter()
267            .map(|word| word.len() + word.whitespace.len())
268            .sum::<usize>();
269
270        // The result is owned if we have indentation, otherwise we
271        // can simply borrow an empty string.
272        let mut result = if lines.is_empty() && !options.initial_indent.is_empty() {
273            Cow::Owned(options.initial_indent.to_owned())
274        } else if !lines.is_empty() && !options.subsequent_indent.is_empty() {
275            Cow::Owned(options.subsequent_indent.to_owned())
276        } else {
277            // We can use an empty string here since string
278            // concatenation for `Cow` preserves a borrowed value when
279            // either side is empty.
280            Cow::from("")
281        };
282
283        if last_word.penalty.is_empty() && options.preserve_trailing_space {
284            result += &line[idx..idx + len];
285        } else {
286            result += &line[idx..idx + len - last_word.whitespace.len()];
287        }
288
289        if !last_word.penalty.is_empty() {
290            result.to_mut().push_str(last_word.penalty);
291        }
292
293        lines.push(result);
294
295        // Advance by the length of `result`, plus the length of
296        // `last_word.whitespace` -- even if we had a penalty, we need
297        // to skip over the whitespace.
298        idx += len;
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305    use crate::{WordSeparator, WordSplitter, WrapAlgorithm};
306    #[cfg(not(feature = "std"))]
307    use alloc::{format, vec};
308
309    #[cfg(feature = "hyphenation")]
310    use hyphenation::{Language, Load, Standard};
311
312    #[test]
313    fn no_wrap() {
314        assert_eq!(wrap("foo", 10), vec!["foo"]);
315    }
316
317    #[test]
318    fn wrap_simple() {
319        assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]);
320    }
321
322    #[test]
323    fn to_be_or_not() {
324        assert_eq!(
325            wrap(
326                "To be, or not to be, that is the question.",
327                Options::new(10).wrap_algorithm(WrapAlgorithm::FirstFit)
328            ),
329            vec!["To be, or", "not to be,", "that is", "the", "question."]
330        );
331    }
332
333    #[test]
334    fn multiple_words_on_first_line() {
335        assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]);
336    }
337
338    #[test]
339    fn long_word() {
340        assert_eq!(wrap("foo", 0), vec!["f", "o", "o"]);
341    }
342
343    #[test]
344    fn long_words() {
345        assert_eq!(wrap("foo bar", 0), vec!["f", "o", "o", "b", "a", "r"]);
346    }
347
348    #[test]
349    fn max_width() {
350        assert_eq!(wrap("foo bar", usize::MAX), vec!["foo bar"]);
351
352        let text = "Hello there! This is some English text. \
353                    It should not be wrapped given the extents below.";
354        assert_eq!(wrap(text, usize::MAX), vec![text]);
355    }
356
357    #[test]
358    fn leading_whitespace() {
359        assert_eq!(wrap("  foo bar", 6), vec!["  foo", "bar"]);
360    }
361
362    #[test]
363    fn leading_whitespace_empty_first_line() {
364        // If there is no space for the first word, the first line
365        // will be empty. This is because the string is split into
366        // words like [" ", "foobar ", "baz"], which puts "foobar " on
367        // the second line. We never output trailing whitespace
368        assert_eq!(wrap(" foobar baz", 6), vec!["", "foobar", "baz"]);
369    }
370
371    #[test]
372    fn trailing_whitespace() {
373        // Whitespace is only significant inside a line. After a line
374        // gets too long and is broken, the first word starts in
375        // column zero and is not indented.
376        assert_eq!(wrap("foo     bar     baz  ", 5), vec!["foo", "bar", "baz"]);
377    }
378
379    #[test]
380    fn preserve_trailing_whitespace() {
381        let options = Options::new(5).preserve_trailing_space(true);
382        // Preserve the whitespace at the end of the line, allowing it
383        // to overflow if it is just whitespaces.
384        assert_eq!(
385            wrap("foo     bar     baz  ", &options),
386            vec!["foo     ", "bar     ", "baz  "]
387        );
388    }
389
390    #[test]
391    fn issue_99() {
392        // We did not reset the in_whitespace flag correctly and did
393        // not handle single-character words after a line break.
394        assert_eq!(
395            wrap("aaabbbccc x yyyzzzwww", 9),
396            vec!["aaabbbccc", "x", "yyyzzzwww"]
397        );
398    }
399
400    #[test]
401    fn issue_129() {
402        // The dash is an em-dash which takes up four bytes. We used
403        // to panic since we tried to index into the character.
404        let options = Options::new(1).word_separator(WordSeparator::AsciiSpace);
405        assert_eq!(wrap("x – x", options), vec!["x", "–", "x"]);
406    }
407
408    #[test]
409    fn wide_character_handling() {
410        assert_eq!(wrap("Hello, World!", 15), vec!["Hello, World!"]);
411        assert_eq!(
412            wrap(
413                "Hello, World!",
414                Options::new(15).word_separator(WordSeparator::AsciiSpace)
415            ),
416            vec!["Hello,", "World!"]
417        );
418
419        // Wide characters are allowed to break if the
420        // unicode-linebreak feature is enabled.
421        #[cfg(feature = "unicode-linebreak")]
422        assert_eq!(
423            wrap(
424                "Hello, World!",
425                Options::new(15).word_separator(WordSeparator::UnicodeBreakProperties),
426            ),
427            vec!["Hello, W", "orld!"]
428        );
429    }
430
431    #[test]
432    #[cfg(feature = "unicode-width")]
433    fn wrap_wide_characters_unicode_width() {
434        // Wide characters occupy 2 columns each. This works in both std
435        // and no_std environments with the unicode-width feature.
436        let options = Options::new(8);
437        assert_eq!(wrap("안녕하세요", &options), vec!["안녕하세", "요"]);
438    }
439
440    #[test]
441    fn indent_empty_line() {
442        // Previously, indentation was not applied to empty lines.
443        // However, this is somewhat inconsistent and undesirable if
444        // the indentation is something like a border ("| ") which you
445        // want to apply to all lines, empty or not.
446        let options = Options::new(10).initial_indent("!!!");
447        assert_eq!(wrap("", &options), vec!["!!!"]);
448    }
449
450    #[test]
451    fn indent_single_line() {
452        let options = Options::new(10).initial_indent(">>>"); // No trailing space
453        assert_eq!(wrap("foo", &options), vec![">>>foo"]);
454    }
455
456    #[test]
457    fn indent_first_emoji() {
458        let options = Options::new(10).initial_indent("👉👉");
459        assert_eq!(
460            wrap("x x x x x x x x x x x x x", &options),
461            vec!["👉👉x x x", "x x x x x", "x x x x x"]
462        );
463    }
464
465    #[test]
466    fn indent_multiple_lines() {
467        let options = Options::new(6).initial_indent("* ").subsequent_indent("  ");
468        assert_eq!(
469            wrap("foo bar baz", &options),
470            vec!["* foo", "  bar", "  baz"]
471        );
472    }
473
474    #[test]
475    fn only_initial_indent_multiple_lines() {
476        let options = Options::new(10).initial_indent("  ");
477        assert_eq!(wrap("foo\nbar\nbaz", &options), vec!["  foo", "bar", "baz"]);
478    }
479
480    #[test]
481    fn only_subsequent_indent_multiple_lines() {
482        let options = Options::new(10).subsequent_indent("  ");
483        assert_eq!(
484            wrap("foo\nbar\nbaz", &options),
485            vec!["foo", "  bar", "  baz"]
486        );
487    }
488
489    #[test]
490    fn indent_break_words() {
491        let options = Options::new(5).initial_indent("* ").subsequent_indent("  ");
492        assert_eq!(wrap("foobarbaz", &options), vec!["* foo", "  bar", "  baz"]);
493    }
494
495    #[test]
496    fn initial_indent_break_words() {
497        // This is a corner-case showing how the long word is broken
498        // according to the width of the subsequent lines. The first
499        // fragment of the word no longer fits on the first line,
500        // which ends up being pure indentation.
501        let options = Options::new(5).initial_indent("-->");
502        assert_eq!(wrap("foobarbaz", &options), vec!["-->", "fooba", "rbaz"]);
503    }
504
505    #[test]
506    fn hyphens() {
507        assert_eq!(wrap("foo-bar", 5), vec!["foo-", "bar"]);
508    }
509
510    #[test]
511    fn trailing_hyphen() {
512        let options = Options::new(5).break_words(false);
513        assert_eq!(wrap("foobar-", &options), vec!["foobar-"]);
514    }
515
516    #[test]
517    fn multiple_hyphens() {
518        assert_eq!(wrap("foo-bar-baz", 5), vec!["foo-", "bar-", "baz"]);
519    }
520
521    #[test]
522    fn hyphens_flag() {
523        let options = Options::new(5).break_words(false);
524        assert_eq!(
525            wrap("The --foo-bar flag.", &options),
526            vec!["The", "--foo-", "bar", "flag."]
527        );
528    }
529
530    #[test]
531    fn repeated_hyphens() {
532        let options = Options::new(4).break_words(false);
533        assert_eq!(wrap("foo--bar", &options), vec!["foo--bar"]);
534    }
535
536    #[test]
537    fn hyphens_alphanumeric() {
538        assert_eq!(wrap("Na2-CH4", 5), vec!["Na2-", "CH4"]);
539    }
540
541    #[test]
542    fn hyphens_non_alphanumeric() {
543        let options = Options::new(5).break_words(false);
544        assert_eq!(wrap("foo(-)bar", &options), vec!["foo(-)bar"]);
545    }
546
547    #[test]
548    fn multiple_splits() {
549        assert_eq!(wrap("foo-bar-baz", 9), vec!["foo-bar-", "baz"]);
550    }
551
552    #[test]
553    fn forced_split() {
554        let options = Options::new(5).break_words(false);
555        assert_eq!(wrap("foobar-baz", &options), vec!["foobar-", "baz"]);
556    }
557
558    #[test]
559    fn multiple_unbroken_words_issue_193() {
560        let options = Options::new(3).break_words(false);
561        assert_eq!(
562            wrap("small large tiny", &options),
563            vec!["small", "large", "tiny"]
564        );
565        assert_eq!(
566            wrap("small  large   tiny", &options),
567            vec!["small", "large", "tiny"]
568        );
569    }
570
571    #[test]
572    fn very_narrow_lines_issue_193() {
573        let options = Options::new(1).break_words(false);
574        assert_eq!(wrap("fooo x y", &options), vec!["fooo", "x", "y"]);
575        assert_eq!(wrap("fooo   x     y", &options), vec!["fooo", "x", "y"]);
576    }
577
578    #[test]
579    fn simple_hyphens() {
580        let options = Options::new(8).word_splitter(WordSplitter::HyphenSplitter);
581        assert_eq!(wrap("foo bar-baz", &options), vec!["foo bar-", "baz"]);
582    }
583
584    #[test]
585    fn no_hyphenation() {
586        let options = Options::new(8).word_splitter(WordSplitter::NoHyphenation);
587        assert_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]);
588    }
589
590    #[test]
591    #[cfg(feature = "hyphenation")]
592    fn auto_hyphenation_double_hyphenation() {
593        let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
594        let options = Options::new(10);
595        assert_eq!(
596            wrap("Internationalization", &options),
597            vec!["Internatio", "nalization"]
598        );
599
600        let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary));
601        assert_eq!(
602            wrap("Internationalization", &options),
603            vec!["Interna-", "tionaliza-", "tion"]
604        );
605    }
606
607    #[test]
608    #[cfg(feature = "hyphenation")]
609    fn auto_hyphenation_issue_158() {
610        let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
611        let options = Options::new(10);
612        assert_eq!(
613            wrap("participation is the key to success", &options),
614            vec!["participat", "ion is", "the key to", "success"]
615        );
616
617        let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary));
618        assert_eq!(
619            wrap("participation is the key to success", &options),
620            vec!["partici-", "pation is", "the key to", "success"]
621        );
622    }
623
624    #[test]
625    #[cfg(feature = "hyphenation")]
626    fn split_len_hyphenation() {
627        // Test that hyphenation takes the width of the whitespace
628        // into account.
629        let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
630        let options = Options::new(15).word_splitter(WordSplitter::Hyphenation(dictionary));
631        assert_eq!(
632            wrap("garbage   collection", &options),
633            vec!["garbage   col-", "lection"]
634        );
635    }
636
637    #[test]
638    #[cfg(feature = "hyphenation")]
639    fn borrowed_lines() {
640        // Lines that end with an extra hyphen are owned, the final
641        // line is borrowed.
642        use alloc::borrow::Cow::{Borrowed, Owned};
643        let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
644        let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary));
645        let lines = wrap("Internationalization", &options);
646        assert_eq!(lines, vec!["Interna-", "tionaliza-", "tion"]);
647        if let Borrowed(s) = lines[0] {
648            assert!(false, "should not have been borrowed: {:?}", s);
649        }
650        if let Borrowed(s) = lines[1] {
651            assert!(false, "should not have been borrowed: {:?}", s);
652        }
653        if let Owned(ref s) = lines[2] {
654            assert!(false, "should not have been owned: {:?}", s);
655        }
656    }
657
658    #[test]
659    #[cfg(feature = "hyphenation")]
660    fn auto_hyphenation_with_hyphen() {
661        let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
662        let options = Options::new(8).break_words(false);
663        assert_eq!(
664            wrap("over-caffinated", &options),
665            vec!["over-", "caffinated"]
666        );
667
668        let options = options.word_splitter(WordSplitter::Hyphenation(dictionary));
669        assert_eq!(
670            wrap("over-caffinated", &options),
671            vec!["over-", "caffi-", "nated"]
672        );
673    }
674
675    #[test]
676    fn break_words() {
677        assert_eq!(wrap("foobarbaz", 3), vec!["foo", "bar", "baz"]);
678    }
679
680    #[test]
681    fn break_words_wide_characters() {
682        // Even the poor man's version of `ch_width` counts these
683        // characters as wide.
684        let options = Options::new(5).word_separator(WordSeparator::AsciiSpace);
685        assert_eq!(wrap("Hello", options), vec!["He", "ll", "o"]);
686    }
687
688    #[test]
689    fn break_words_zero_width() {
690        assert_eq!(wrap("foobar", 0), vec!["f", "o", "o", "b", "a", "r"]);
691    }
692
693    #[test]
694    fn break_long_first_word() {
695        assert_eq!(wrap("testx y", 4), vec!["test", "x y"]);
696    }
697
698    #[test]
699    fn wrap_preserves_line_breaks_trims_whitespace() {
700        assert_eq!(wrap("  ", 80), vec![""]);
701        assert_eq!(wrap("  \n  ", 80), vec!["", ""]);
702        assert_eq!(wrap("  \n \n  \n ", 80), vec!["", "", "", ""]);
703    }
704
705    #[test]
706    fn wrap_preserves_line_breaks_and_trailing_whitespace() {
707        let options = Options::new(80).preserve_trailing_space(true);
708        assert_eq!(wrap("  ", &options), vec!["  "]);
709        assert_eq!(wrap("  \n  ", &options), vec!["  ", "  "]);
710        assert_eq!(wrap("  \n \n  \n ", &options), vec!["  ", " ", "  ", " "]);
711    }
712
713    #[test]
714    fn preserve_trailing_space_borrows_spaces() {
715        use alloc::borrow::Cow::Owned;
716        let lines = wrap(
717            "foo bar baz",
718            Options::new(10).preserve_trailing_space(true),
719        );
720        assert_eq!(lines, vec!["foo bar ", "baz"]);
721        if let Owned(ref s) = lines[0] {
722            assert!(false, "should not have been owned: {:?}", s);
723        }
724    }
725
726    #[test]
727    fn wrap_colored_text() {
728        // The words are much longer than 6 bytes, but they remain
729        // intact after filling the text.
730        let green_hello = "\u{1b}[0m\u{1b}[32mHello\u{1b}[0m";
731        let blue_world = "\u{1b}[0m\u{1b}[34mWorld!\u{1b}[0m";
732        assert_eq!(
733            wrap(&format!("{} {}", green_hello, blue_world), 6),
734            vec![green_hello, blue_world],
735        );
736    }
737}