• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! The textwrap library provides functions for word wrapping and
2 //! indenting text.
3 //!
4 //! # Wrapping Text
5 //!
6 //! Wrapping text can be very useful in command-line programs where
7 //! you want to format dynamic output nicely so it looks good in a
8 //! terminal. A quick example:
9 //!
10 //! ```no_run
11 //! fn main() {
12 //!     let text = "textwrap: a small library for wrapping text.";
13 //!     println!("{}", textwrap::fill(text, 18));
14 //! }
15 //! ```
16 //!
17 //! When you run this program, it will display the following output:
18 //!
19 //! ```text
20 //! textwrap: a small
21 //! library for
22 //! wrapping text.
23 //! ```
24 //!
25 //! If you enable the `hyphenation` Cargo feature, you can get
26 //! automatic hyphenation for a number of languages:
27 //!
28 //! ```no_run
29 //! # #[cfg(feature = "hyphenation")]
30 //! use hyphenation::{Language, Load, Standard};
31 //! use textwrap::{fill, Options};
32 //!
33 //! # #[cfg(feature = "hyphenation")]
34 //! fn main() {
35 //!     let text = "textwrap: a small library for wrapping text.";
36 //!     let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
37 //!     let options = Options::new(18).splitter(dictionary);
38 //!     println!("{}", fill(text, &options));
39 //! }
40 //!
41 //! # #[cfg(not(feature = "hyphenation"))]
42 //! # fn main() { }
43 //! ```
44 //!
45 //! The program will now output:
46 //!
47 //! ```text
48 //! textwrap: a small
49 //! library for wrap-
50 //! ping text.
51 //! ```
52 //!
53 //! See also the [`unfill`] and [`refill`] functions which allow you to
54 //! manipulate already wrapped text.
55 //!
56 //! ## Wrapping Strings at Compile Time
57 //!
58 //! If your strings are known at compile time, please take a look at
59 //! the procedural macros from the [textwrap-macros] crate.
60 //!
61 //! ## Displayed Width vs Byte Size
62 //!
63 //! To word wrap text, one must know the width of each word so one can
64 //! know when to break lines. This library will by default measure the
65 //! width of text using the _displayed width_, not the size in bytes.
66 //! The `unicode-width` Cargo feature controls this.
67 //!
68 //! This is important for non-ASCII text. ASCII characters such as `a`
69 //! and `!` are simple and take up one column each. This means that
70 //! the displayed width is equal to the string length in bytes.
71 //! However, non-ASCII characters and symbols take up more than one
72 //! byte when UTF-8 encoded: `é` is `0xc3 0xa9` (two bytes) and `⚙` is
73 //! `0xe2 0x9a 0x99` (three bytes) in UTF-8, respectively.
74 //!
75 //! This is why we take care to use the displayed width instead of the
76 //! byte count when computing line lengths. All functions in this
77 //! library handle Unicode characters like this when the
78 //! `unicode-width` Cargo feature is enabled (it is enabled by
79 //! default).
80 //!
81 //! # Indentation and Dedentation
82 //!
83 //! The textwrap library also offers functions for adding a prefix to
84 //! every line of a string and to remove leading whitespace. As an
85 //! example, the [`indent`] function allows you to turn lines of text
86 //! into a bullet list:
87 //!
88 //! ```
89 //! let before = "
90 //! foo
91 //! bar
92 //! baz
93 //! ";
94 //! let after = "
95 //! * foo
96 //! * bar
97 //! * baz
98 //! ";
99 //! assert_eq!(textwrap::indent(before, "* "), after);
100 //! ```
101 //!
102 //! Removing leading whitespace is done with [`dedent`]:
103 //!
104 //! ```
105 //! let before = "
106 //!     Some
107 //!       indented
108 //!         text
109 //! ";
110 //! let after = "
111 //! Some
112 //!   indented
113 //!     text
114 //! ";
115 //! assert_eq!(textwrap::dedent(before), after);
116 //! ```
117 //!
118 //! # Cargo Features
119 //!
120 //! The textwrap library can be slimmed down as needed via a number of
121 //! Cargo features. This means you only pay for the features you
122 //! actually use.
123 //!
124 //! The full dependency graph, where dashed lines indicate optional
125 //! dependencies, is shown below:
126 //!
127 //! <img src="https://raw.githubusercontent.com/mgeisler/textwrap/master/images/textwrap-0.13.4.svg">
128 //!
129 //! ## Default Features
130 //!
131 //! These features are enabled by default:
132 //!
133 //! * `unicode-width`: enables correct width computation of non-ASCII
134 //!   characters via the [unicode-width] crate. Without this feature,
135 //!   every [`char`] is 1 column wide, except for emojis which are 2
136 //!   columns wide. See the [`core::display_width`] function for
137 //!   details.
138 //!
139 //!   This feature can be disabled if you only need to wrap ASCII
140 //!   text, or if the functions in [`core`] are used directly with
141 //!   [`core::Fragment`]s for which the widths have been computed in
142 //!   other ways.
143 //!
144 //! * `smawk`: enables linear-time wrapping of the whole paragraph via
145 //!   the [smawk] crate. See the [`core::wrap_optimal_fit`] function
146 //!   for details on the optimal-fit algorithm.
147 //!
148 //!   This feature can be disabled if you only ever intend to use
149 //!   [`core::wrap_first_fit`].
150 //!
151 //! ## Optional Features
152 //!
153 //! These Cargo features enable new functionality:
154 //!
155 //! * `terminal_size`: enables automatic detection of the terminal
156 //!   width via the [terminal_size] crate. See the
157 //!   [`Options::with_termwidth`] constructor for details.
158 //!
159 //! * `hyphenation`: enables language-sensitive hyphenation via the
160 //!   [hyphenation] crate. See the [`WordSplitter`] trait for details.
161 //!
162 //! [unicode-width]: https://docs.rs/unicode-width/
163 //! [smawk]: https://docs.rs/smawk/
164 //! [textwrap-macros]: https://docs.rs/textwrap-macros/
165 //! [terminal_size]: https://docs.rs/terminal_size/
166 //! [hyphenation]: https://docs.rs/hyphenation/
167 
168 #![doc(html_root_url = "https://docs.rs/textwrap/0.13.4")]
169 #![forbid(unsafe_code)] // See https://github.com/mgeisler/textwrap/issues/210
170 #![deny(missing_docs)]
171 #![deny(missing_debug_implementations)]
172 #![allow(clippy::redundant_field_names)]
173 
174 use std::borrow::Cow;
175 
176 mod indentation;
177 pub use crate::indentation::dedent;
178 pub use crate::indentation::indent;
179 
180 mod splitting;
181 pub use crate::splitting::{HyphenSplitter, NoHyphenation, WordSplitter};
182 
183 pub mod core;
184 
185 /// Holds settings for wrapping and filling text.
186 #[derive(Debug, Clone)]
187 pub struct Options<'a, S: ?Sized = Box<dyn WordSplitter>> {
188     /// The width in columns at which the text will be wrapped.
189     pub width: usize,
190     /// Indentation used for the first line of output. See the
191     /// [`Options::initial_indent`] method.
192     pub initial_indent: &'a str,
193     /// Indentation used for subsequent lines of output. See the
194     /// [`Options::subsequent_indent`] method.
195     pub subsequent_indent: &'a str,
196     /// Allow long words to be broken if they cannot fit on a line.
197     /// When set to `false`, some lines may be longer than
198     /// `self.width`. See the [`Options::break_words`] method.
199     pub break_words: bool,
200     /// Wraping algorithm to use, see [`core::WrapAlgorithm`] for
201     /// details.
202     pub wrap_algorithm: core::WrapAlgorithm,
203     /// The method for splitting words. This can be used to prohibit
204     /// splitting words on hyphens, or it can be used to implement
205     /// language-aware machine hyphenation. Please see the
206     /// [`WordSplitter`] trait for details.
207     pub splitter: S,
208 }
209 
210 impl<'a, S: ?Sized> From<&'a Options<'a, S>> for Options<'a, &'a S> {
from(options: &'a Options<'a, S>) -> Self211     fn from(options: &'a Options<'a, S>) -> Self {
212         Self {
213             width: options.width,
214             initial_indent: options.initial_indent,
215             subsequent_indent: options.subsequent_indent,
216             break_words: options.break_words,
217             wrap_algorithm: options.wrap_algorithm,
218             splitter: &options.splitter,
219         }
220     }
221 }
222 
223 impl<'a> From<usize> for Options<'a, HyphenSplitter> {
from(width: usize) -> Self224     fn from(width: usize) -> Self {
225         Options::new(width)
226     }
227 }
228 
229 /// Constructors for boxed Options, specifically.
230 impl<'a> Options<'a, HyphenSplitter> {
231     /// Creates a new [`Options`] with the specified width and static
232     /// dispatch using the [`HyphenSplitter`]. Equivalent to
233     ///
234     /// ```
235     /// # use textwrap::{Options, HyphenSplitter, WordSplitter};
236     /// # let width = 80;
237     /// # let actual = Options::new(width);
238     /// # let expected =
239     /// Options {
240     ///     width: width,
241     ///     initial_indent: "",
242     ///     subsequent_indent: "",
243     ///     break_words: true,
244     ///     #[cfg(feature = "smawk")]
245     ///     wrap_algorithm: textwrap::core::WrapAlgorithm::OptimalFit,
246     ///     #[cfg(not(feature = "smawk"))]
247     ///     wrap_algorithm: textwrap::core::WrapAlgorithm::FirstFit,
248     ///     splitter: HyphenSplitter,
249     /// }
250     /// # ;
251     /// # assert_eq!(actual.width, expected.width);
252     /// # assert_eq!(actual.initial_indent, expected.initial_indent);
253     /// # assert_eq!(actual.subsequent_indent, expected.subsequent_indent);
254     /// # assert_eq!(actual.break_words, expected.break_words);
255     /// # assert_eq!(actual.wrap_algorithm, expected.wrap_algorithm);
256     /// # let expected_coerced: Options<'static, HyphenSplitter> = expected;
257     /// ```
258     ///
259     /// Note that the default wrap algorithm changes based on the
260     /// `smawk` Cargo feature. The best available algorithm is used by
261     /// default.
262     ///
263     /// Static dispatch mean here, that the splitter is stored as-is
264     /// and the type is known at compile-time. Thus the returned value
265     /// is actually a `Options<HyphenSplitter>`.
266     ///
267     /// Dynamic dispatch on the other hand, mean that the splitter is
268     /// stored as a trait object for instance in a `Box<dyn
269     /// WordSplitter>`. This way the splitter's inner type can be
270     /// changed without changing the type of this struct, which then
271     /// would be just `Options` as a short cut for `Options<Box<dyn
272     /// WordSplitter>>`.
273     ///
274     /// The value and type of the splitter can be choose from the
275     /// start using the [`Options::with_splitter`] constructor or
276     /// changed afterwards using the [`Options::splitter`] method.
277     /// Whether static or dynamic dispatch is used, depends on whether
278     /// these functions are given a boxed [`WordSplitter`] or not.
279     /// Take for example:
280     ///
281     /// ```
282     /// use textwrap::{HyphenSplitter, NoHyphenation, Options};
283     /// # use textwrap::{WordSplitter};
284     /// # let width = 80;
285     ///
286     /// // uses HyphenSplitter with static dispatch
287     /// // the actual type: Options<HyphenSplitter>
288     /// let opt = Options::new(width);
289     /// # let opt_coerce: Options<HyphenSplitter> = opt;
290     ///
291     /// // uses NoHyphenation with static dispatch
292     /// // the actual type: Options<NoHyphenation>
293     /// let opt = Options::new(width).splitter(NoHyphenation);
294     /// # let opt_coerce: Options<NoHyphenation> = opt;
295     ///
296     /// // uses HyphenSplitter with dynamic dispatch
297     /// // the actual type: Options<Box<dyn WordSplitter>>
298     /// let opt: Options = Options::new(width).splitter(Box::new(HyphenSplitter));
299     /// # let opt_coerce: Options<Box<dyn WordSplitter>> = opt;
300     ///
301     /// // uses NoHyphenation with dynamic dispatch
302     /// // the actual type: Options<Box<dyn WordSplitter>>
303     /// let opt: Options = Options::new(width).splitter(Box::new(NoHyphenation));
304     /// # let opt_coerce: Options<Box<dyn WordSplitter>> = opt;
305     /// ```
306     ///
307     /// Notice that the last two variables have the same type, despite
308     /// the different `WordSplitter` in use. Thus dynamic dispatch
309     /// allows to change the splitter at run-time without changing the
310     /// variables type.
new(width: usize) -> Self311     pub const fn new(width: usize) -> Self {
312         Options::with_splitter(width, HyphenSplitter)
313     }
314 
315     /// Creates a new [`Options`] with `width` set to the current
316     /// terminal width. If the terminal width cannot be determined
317     /// (typically because the standard input and output is not
318     /// connected to a terminal), a width of 80 characters will be
319     /// used. Other settings use the same defaults as
320     /// [`Options::new`].
321     ///
322     /// Equivalent to:
323     ///
324     /// ```no_run
325     /// use textwrap::{termwidth, Options};
326     ///
327     /// let options = Options::new(termwidth());
328     /// ```
329     ///
330     /// **Note:** Only available when the `terminal_size` feature is
331     /// enabled.
332     #[cfg(feature = "terminal_size")]
with_termwidth() -> Self333     pub fn with_termwidth() -> Self {
334         Self::new(termwidth())
335     }
336 }
337 
338 impl<'a, S> Options<'a, S> {
339     /// Creates a new [`Options`] with the specified width and
340     /// splitter. Equivalent to
341     ///
342     /// ```
343     /// # use textwrap::{Options, NoHyphenation, HyphenSplitter};
344     /// # const splitter: NoHyphenation = NoHyphenation;
345     /// # const width: usize = 80;
346     /// # const actual: Options<'static, NoHyphenation> = Options::with_splitter(width, splitter);
347     /// # let expected =
348     /// Options {
349     ///     width: width,
350     ///     initial_indent: "",
351     ///     subsequent_indent: "",
352     ///     break_words: true,
353     ///     #[cfg(feature = "smawk")]
354     ///     wrap_algorithm: textwrap::core::WrapAlgorithm::OptimalFit,
355     ///     #[cfg(not(feature = "smawk"))]
356     ///     wrap_algorithm: textwrap::core::WrapAlgorithm::FirstFit,
357     ///     splitter: splitter,
358     /// }
359     /// # ;
360     /// # assert_eq!(actual.width, expected.width);
361     /// # assert_eq!(actual.initial_indent, expected.initial_indent);
362     /// # assert_eq!(actual.subsequent_indent, expected.subsequent_indent);
363     /// # assert_eq!(actual.break_words, expected.break_words);
364     /// # assert_eq!(actual.wrap_algorithm, expected.wrap_algorithm);
365     /// # let expected_coerced: Options<'static, NoHyphenation> = expected;
366     /// ```
367     ///
368     /// This constructor allows to specify the splitter to be used. It
369     /// is like a short-cut for `Options::new(w).splitter(s)`, but
370     /// this function is a `const fn`. The given splitter may be in a
371     /// [`Box`], which then can be coerced into a trait object for
372     /// dynamic dispatch:
373     ///
374     /// ```
375     /// use textwrap::{HyphenSplitter, NoHyphenation, Options};
376     /// # use textwrap::{WordSplitter};
377     /// # const width: usize = 80;
378     ///
379     /// // This opt contains a boxed trait object as splitter.
380     /// // The type annotation is important, otherwise it will be not a trait object
381     /// let mut opt: Options = Options::with_splitter(width, Box::new(NoHyphenation));
382     /// // Its type is actually: `Options<Box<dyn WordSplitter>>`:
383     /// let opt_coerced: Options<Box<dyn WordSplitter>> = opt;
384     ///
385     /// // Thus, it can be overridden with a different splitter.
386     /// opt = Options::with_splitter(width, Box::new(HyphenSplitter));
387     /// // Now, containing a `HyphenSplitter` instead.
388     /// ```
389     ///
390     /// Since the splitter is given by value, which determines the
391     /// generic type parameter, it can be used to produce both an
392     /// [`Options`] with static and dynamic dispatch, respectively.
393     /// While dynamic dispatch allows to change the type of the inner
394     /// splitter at run time as seen above, static dispatch especially
395     /// can store the splitter directly, without the need for a box.
396     /// This in turn allows it to be used in constant and static
397     /// context:
398     ///
399     /// ```
400     /// use textwrap::{HyphenSplitter, Options};
401     /// # const width: usize = 80;
402     ///
403     /// const FOO: Options<HyphenSplitter> = Options::with_splitter(width, HyphenSplitter);
404     /// static BAR: Options<HyphenSplitter> = FOO;
405     /// ```
with_splitter(width: usize, splitter: S) -> Self406     pub const fn with_splitter(width: usize, splitter: S) -> Self {
407         Options {
408             width,
409             initial_indent: "",
410             subsequent_indent: "",
411             break_words: true,
412             #[cfg(feature = "smawk")]
413             wrap_algorithm: core::WrapAlgorithm::OptimalFit,
414             #[cfg(not(feature = "smawk"))]
415             wrap_algorithm: core::WrapAlgorithm::FirstFit,
416             splitter: splitter,
417         }
418     }
419 }
420 
421 impl<'a, S: WordSplitter> Options<'a, S> {
422     /// Change [`self.initial_indent`]. The initial indentation is
423     /// used on the very first line of output.
424     ///
425     /// # Examples
426     ///
427     /// Classic paragraph indentation can be achieved by specifying an
428     /// initial indentation and wrapping each paragraph by itself:
429     ///
430     /// ```
431     /// use textwrap::{Options, wrap};
432     ///
433     /// let options = Options::new(16).initial_indent("    ");
434     /// assert_eq!(wrap("This is a little example.", options),
435     ///            vec!["    This is a",
436     ///                 "little example."]);
437     /// ```
438     ///
439     /// [`self.initial_indent`]: #structfield.initial_indent
initial_indent(self, indent: &'a str) -> Self440     pub fn initial_indent(self, indent: &'a str) -> Self {
441         Options {
442             initial_indent: indent,
443             ..self
444         }
445     }
446 
447     /// Change [`self.subsequent_indent`]. The subsequent indentation
448     /// is used on lines following the first line of output.
449     ///
450     /// # Examples
451     ///
452     /// Combining initial and subsequent indentation lets you format a
453     /// single paragraph as a bullet list:
454     ///
455     /// ```
456     /// use textwrap::{Options, wrap};
457     ///
458     /// let options = Options::new(12)
459     ///     .initial_indent("* ")
460     ///     .subsequent_indent("  ");
461     /// #[cfg(feature = "smawk")]
462     /// assert_eq!(wrap("This is a little example.", options),
463     ///            vec!["* This is",
464     ///                 "  a little",
465     ///                 "  example."]);
466     ///
467     /// // Without the `smawk` feature, the wrapping is a little different:
468     /// #[cfg(not(feature = "smawk"))]
469     /// assert_eq!(wrap("This is a little example.", options),
470     ///            vec!["* This is a",
471     ///                 "  little",
472     ///                 "  example."]);
473     /// ```
474     ///
475     /// [`self.subsequent_indent`]: #structfield.subsequent_indent
subsequent_indent(self, indent: &'a str) -> Self476     pub fn subsequent_indent(self, indent: &'a str) -> Self {
477         Options {
478             subsequent_indent: indent,
479             ..self
480         }
481     }
482 
483     /// Change [`self.break_words`]. This controls if words longer
484     /// than `self.width` can be broken, or if they will be left
485     /// sticking out into the right margin.
486     ///
487     /// # Examples
488     ///
489     /// ```
490     /// use textwrap::{wrap, Options};
491     ///
492     /// let options = Options::new(4).break_words(true);
493     /// assert_eq!(wrap("This is a little example.", options),
494     ///            vec!["This",
495     ///                 "is a",
496     ///                 "litt",
497     ///                 "le",
498     ///                 "exam",
499     ///                 "ple."]);
500     /// ```
501     ///
502     /// [`self.break_words`]: #structfield.break_words
break_words(self, setting: bool) -> Self503     pub fn break_words(self, setting: bool) -> Self {
504         Options {
505             break_words: setting,
506             ..self
507         }
508     }
509 
510     /// Change [`self.wrap_algorithm`].
511     ///
512     /// See [`core::WrapAlgorithm`] for details on the choices.
513     ///
514     /// [`self.wrap_algorithm`]: #structfield.wrap_algorithm
wrap_algorithm(self, wrap_algorithm: core::WrapAlgorithm) -> Self515     pub fn wrap_algorithm(self, wrap_algorithm: core::WrapAlgorithm) -> Self {
516         Options {
517             wrap_algorithm,
518             ..self
519         }
520     }
521 
522     /// Change [`self.splitter`]. The [`WordSplitter`] is used to fit
523     /// part of a word into the current line when wrapping text.
524     ///
525     /// This function may return a different type than `Self`. That is
526     /// the case when the given `splitter` is of a different type the
527     /// the currently stored one in the `splitter` field. Take for
528     /// example:
529     ///
530     /// ```
531     /// use textwrap::{HyphenSplitter, NoHyphenation, Options};
532     /// // The default type returned by `new` is `Options<HyphenSplitter>`
533     /// let opt: Options<HyphenSplitter> = Options::new(80);
534     /// // Setting a different splitter changes the type
535     /// let opt: Options<NoHyphenation> = opt.splitter(NoHyphenation);
536     /// ```
537     ///
538     /// [`self.splitter`]: #structfield.splitter
splitter<T>(self, splitter: T) -> Options<'a, T>539     pub fn splitter<T>(self, splitter: T) -> Options<'a, T> {
540         Options {
541             width: self.width,
542             initial_indent: self.initial_indent,
543             subsequent_indent: self.subsequent_indent,
544             break_words: self.break_words,
545             wrap_algorithm: self.wrap_algorithm,
546             splitter: splitter,
547         }
548     }
549 }
550 
551 /// Return the current terminal width. If the terminal width cannot be
552 /// determined (typically because the standard output is not connected
553 /// to a terminal), a default width of 80 characters will be used.
554 ///
555 /// # Examples
556 ///
557 /// Create an [`Options`] for wrapping at the current terminal width
558 /// with a two column margin to the left and the right:
559 ///
560 /// ```no_run
561 /// use textwrap::{termwidth, NoHyphenation, Options};
562 ///
563 /// let width = termwidth() - 4; // Two columns on each side.
564 /// let options = Options::new(width)
565 ///     .splitter(NoHyphenation)
566 ///     .initial_indent("  ")
567 ///     .subsequent_indent("  ");
568 /// ```
569 ///
570 /// **Note:** Only available when the `terminal_size` Cargo feature is
571 /// enabled.
572 #[cfg(feature = "terminal_size")]
termwidth() -> usize573 pub fn termwidth() -> usize {
574     terminal_size::terminal_size().map_or(80, |(terminal_size::Width(w), _)| w.into())
575 }
576 
577 /// Fill a line of text at a given width.
578 ///
579 /// The result is a [`String`], complete with newlines between each
580 /// line. Use the [`wrap`] function if you need access to the
581 /// individual lines.
582 ///
583 /// The easiest way to use this function is to pass an integer for
584 /// `width_or_options`:
585 ///
586 /// ```
587 /// use textwrap::fill;
588 ///
589 /// assert_eq!(
590 ///     fill("Memory safety without garbage collection.", 15),
591 ///     "Memory safety\nwithout garbage\ncollection."
592 /// );
593 /// ```
594 ///
595 /// If you need to customize the wrapping, you can pass an [`Options`]
596 /// instead of an `usize`:
597 ///
598 /// ```
599 /// use textwrap::{fill, Options};
600 ///
601 /// let options = Options::new(15)
602 ///     .initial_indent("- ")
603 ///     .subsequent_indent("  ");
604 /// assert_eq!(
605 ///     fill("Memory safety without garbage collection.", &options),
606 ///     "- Memory safety\n  without\n  garbage\n  collection."
607 /// );
608 /// ```
fill<'a, S, Opt>(text: &str, width_or_options: Opt) -> String where S: WordSplitter, Opt: Into<Options<'a, S>>,609 pub fn fill<'a, S, Opt>(text: &str, width_or_options: Opt) -> String
610 where
611     S: WordSplitter,
612     Opt: Into<Options<'a, S>>,
613 {
614     // This will avoid reallocation in simple cases (no
615     // indentation, no hyphenation).
616     let mut result = String::with_capacity(text.len());
617 
618     for (i, line) in wrap(text, width_or_options).iter().enumerate() {
619         if i > 0 {
620             result.push('\n');
621         }
622         result.push_str(&line);
623     }
624 
625     result
626 }
627 
628 /// Unpack a paragraph of already-wrapped text.
629 ///
630 /// This function attempts to recover the original text from a single
631 /// paragraph of text produced by the [`fill`] function. This means
632 /// that it turns
633 ///
634 /// ```text
635 /// textwrap: a small
636 /// library for
637 /// wrapping text.
638 /// ```
639 ///
640 /// back into
641 ///
642 /// ```text
643 /// textwrap: a small library for wrapping text.
644 /// ```
645 ///
646 /// In addition, it will recognize a common prefix among the lines.
647 /// The prefix of the first line is returned in
648 /// [`Options::initial_indent`] and the prefix (if any) of the the
649 /// other lines is returned in [`Options::subsequent_indent`].
650 ///
651 /// In addition to `' '`, the prefixes can consist of characters used
652 /// for unordered lists (`'-'`, `'+'`, and `'*'`) and block quotes
653 /// (`'>'`) in Markdown as well as characters often used for inline
654 /// comments (`'#'` and `'/'`).
655 ///
656 /// The text must come from a single wrapped paragraph. This means
657 /// that there can be no `"\n\n"` within the text.
658 ///
659 /// # Examples
660 ///
661 /// ```
662 /// use textwrap::unfill;
663 ///
664 /// let (text, options) = unfill("\
665 /// * This is an
666 ///   example of
667 ///   a list item.
668 /// ");
669 ///
670 /// assert_eq!(text, "This is an example of a list item.\n");
671 /// assert_eq!(options.initial_indent, "* ");
672 /// assert_eq!(options.subsequent_indent, "  ");
673 /// ```
unfill<'a>(text: &'a str) -> (String, Options<'a, HyphenSplitter>)674 pub fn unfill<'a>(text: &'a str) -> (String, Options<'a, HyphenSplitter>) {
675     let trimmed = text.trim_end_matches('\n');
676     let prefix_chars: &[_] = &[' ', '-', '+', '*', '>', '#', '/'];
677 
678     let mut options = Options::new(0);
679     for (idx, line) in trimmed.split('\n').enumerate() {
680         options.width = std::cmp::max(options.width, core::display_width(line));
681         let without_prefix = line.trim_start_matches(prefix_chars);
682         let prefix = &line[..line.len() - without_prefix.len()];
683 
684         if idx == 0 {
685             options.initial_indent = prefix;
686         } else if idx == 1 {
687             options.subsequent_indent = prefix;
688         } else if idx > 1 {
689             for ((idx, x), y) in prefix.char_indices().zip(options.subsequent_indent.chars()) {
690                 if x != y {
691                     options.subsequent_indent = &prefix[..idx];
692                     break;
693                 }
694             }
695             if prefix.len() < options.subsequent_indent.len() {
696                 options.subsequent_indent = prefix;
697             }
698         }
699     }
700 
701     let mut unfilled = String::with_capacity(text.len());
702     for (idx, line) in trimmed.split('\n').enumerate() {
703         if idx == 0 {
704             unfilled.push_str(&line[options.initial_indent.len()..]);
705         } else {
706             unfilled.push(' ');
707             unfilled.push_str(&line[options.subsequent_indent.len()..]);
708         }
709     }
710 
711     unfilled.push_str(&text[trimmed.len()..]);
712     (unfilled, options)
713 }
714 
715 /// Refill a paragraph of wrapped text with a new width.
716 ///
717 /// This function will first use the [`unfill`] function to remove
718 /// newlines from the text. Afterwards the text is filled again using
719 /// the [`fill`] function.
720 ///
721 /// The `new_width_or_options` argument specify the new width and can
722 /// specify other options as well — except for
723 /// [`Options::initial_indent`] and [`Options::subsequent_indent`],
724 /// which are deduced from `filled_text`.
725 ///
726 /// # Examples
727 ///
728 /// ```
729 /// use textwrap::refill;
730 ///
731 /// let text = "\
732 /// > Memory safety without
733 /// > garbage collection.
734 /// ";
735 /// assert_eq!(refill(text, 15), "\
736 /// > Memory safety
737 /// > without
738 /// > garbage
739 /// > collection.
740 /// ");
refill<'a, S, Opt>(filled_text: &str, new_width_or_options: Opt) -> String where S: WordSplitter, Opt: Into<Options<'a, S>>,741 pub fn refill<'a, S, Opt>(filled_text: &str, new_width_or_options: Opt) -> String
742 where
743     S: WordSplitter,
744     Opt: Into<Options<'a, S>>,
745 {
746     let trimmed = filled_text.trim_end_matches('\n');
747     let (text, options) = unfill(trimmed);
748     let mut new_options = new_width_or_options.into();
749     new_options.initial_indent = options.initial_indent;
750     new_options.subsequent_indent = options.subsequent_indent;
751     let mut refilled = fill(&text, new_options);
752     refilled.push_str(&filled_text[trimmed.len()..]);
753     refilled
754 }
755 
756 /// Wrap a line of text at a given width.
757 ///
758 /// The result is a vector of lines, each line is of type [`Cow<'_,
759 /// str>`](Cow), which means that the line will borrow from the input
760 /// `&str` if possible. The lines do not have a trailing `'\n'`. Use
761 /// the [`fill`] function if you need a [`String`] instead.
762 ///
763 /// The easiest way to use this function is to pass an integer for
764 /// `width_or_options`:
765 ///
766 /// ```
767 /// use textwrap::wrap;
768 ///
769 /// let lines = wrap("Memory safety without garbage collection.", 15);
770 /// assert_eq!(lines, &[
771 ///     "Memory safety",
772 ///     "without garbage",
773 ///     "collection.",
774 /// ]);
775 /// ```
776 ///
777 /// If you need to customize the wrapping, you can pass an [`Options`]
778 /// instead of an `usize`:
779 ///
780 /// ```
781 /// use textwrap::{wrap, Options};
782 ///
783 /// let options = Options::new(15)
784 ///     .initial_indent("- ")
785 ///     .subsequent_indent("  ");
786 /// let lines = wrap("Memory safety without garbage collection.", &options);
787 /// assert_eq!(lines, &[
788 ///     "- Memory safety",
789 ///     "  without",
790 ///     "  garbage",
791 ///     "  collection.",
792 /// ]);
793 /// ```
794 ///
795 /// # Optimal-Fit Wrapping
796 ///
797 /// By default, `wrap` will try to ensure an even right margin by
798 /// finding breaks which avoid short lines. We call this an
799 /// “optimal-fit algorithm” since the line breaks are computed by
800 /// considering all possible line breaks. The alternative is a
801 /// “first-fit algorithm” which simply accumulates words until they no
802 /// longer fit on the line.
803 ///
804 /// As an example, using the first-fit algorithm to wrap the famous
805 /// Hamlet quote “To be, or not to be: that is the question” in a
806 /// narrow column with room for only 10 characters looks like this:
807 ///
808 /// ```
809 /// # use textwrap::{Options, wrap};
810 /// # use textwrap::core::WrapAlgorithm::FirstFit;
811 /// #
812 /// # let lines = wrap("To be, or not to be: that is the question",
813 /// #                  Options::new(10).wrap_algorithm(FirstFit));
814 /// # assert_eq!(lines.join("\n") + "\n", "\
815 /// To be, or
816 /// not to be:
817 /// that is
818 /// the
819 /// question
820 /// # ");
821 /// ```
822 ///
823 /// Notice how the second to last line is quite narrow because
824 /// “question” was too large to fit? The greedy first-fit algorithm
825 /// doesn’t look ahead, so it has no other option than to put
826 /// “question” onto its own line.
827 ///
828 /// With the optimal-fit wrapping algorithm, the previous lines are
829 /// shortened slightly in order to make the word “is” go into the
830 /// second last line:
831 ///
832 /// ```
833 /// # #[cfg(feature = "smawk")] {
834 /// # use textwrap::{Options, wrap};
835 /// # use textwrap::core::WrapAlgorithm::OptimalFit;
836 /// #
837 /// # let lines = wrap("To be, or not to be: that is the question",
838 /// #                  Options::new(10).wrap_algorithm(OptimalFit));
839 /// # assert_eq!(lines.join("\n") + "\n", "\
840 /// To be,
841 /// or not to
842 /// be: that
843 /// is the
844 /// question
845 /// # "); }
846 /// ```
847 ///
848 /// Please see [`core::WrapAlgorithm`] for details.
849 ///
850 /// # Examples
851 ///
852 /// The returned iterator yields lines of type `Cow<'_, str>`. If
853 /// possible, the wrapped lines will borrow from the input string. As
854 /// an example, a hanging indentation, the first line can borrow from
855 /// the input, but the subsequent lines become owned strings:
856 ///
857 /// ```
858 /// use std::borrow::Cow::{Borrowed, Owned};
859 /// use textwrap::{wrap, Options};
860 ///
861 /// let options = Options::new(15).subsequent_indent("....");
862 /// let lines = wrap("Wrapping text all day long.", &options);
863 /// let annotated = lines
864 ///     .iter()
865 ///     .map(|line| match line {
866 ///         Borrowed(text) => format!("[Borrowed] {}", text),
867 ///         Owned(text) => format!("[Owned]    {}", text),
868 ///     })
869 ///     .collect::<Vec<_>>();
870 /// assert_eq!(
871 ///     annotated,
872 ///     &[
873 ///         "[Borrowed] Wrapping text",
874 ///         "[Owned]    ....all day",
875 ///         "[Owned]    ....long.",
876 ///     ]
877 /// );
878 /// ```
wrap<'a, S, Opt>(text: &str, width_or_options: Opt) -> Vec<Cow<'_, str>> where S: WordSplitter, Opt: Into<Options<'a, S>>,879 pub fn wrap<'a, S, Opt>(text: &str, width_or_options: Opt) -> Vec<Cow<'_, str>>
880 where
881     S: WordSplitter,
882     Opt: Into<Options<'a, S>>,
883 {
884     let options = width_or_options.into();
885 
886     let initial_width = options
887         .width
888         .saturating_sub(core::display_width(options.initial_indent));
889     let subsequent_width = options
890         .width
891         .saturating_sub(core::display_width(options.subsequent_indent));
892 
893     let mut lines = Vec::new();
894     for line in text.split('\n') {
895         let words = core::find_words(line);
896         let split_words = core::split_words(words, &options);
897         let broken_words = if options.break_words {
898             let mut broken_words = core::break_words(split_words, subsequent_width);
899             if !options.initial_indent.is_empty() {
900                 // Without this, the first word will always go into
901                 // the first line. However, since we break words based
902                 // on the _second_ line width, it can be wrong to
903                 // unconditionally put the first word onto the first
904                 // line. An empty zero-width word fixed this.
905                 broken_words.insert(0, core::Word::from(""));
906             }
907             broken_words
908         } else {
909             split_words.collect::<Vec<_>>()
910         };
911 
912         #[rustfmt::skip]
913         let line_lengths = |i| if i == 0 { initial_width } else { subsequent_width };
914         let wrapped_words = match options.wrap_algorithm {
915             #[cfg(feature = "smawk")]
916             core::WrapAlgorithm::OptimalFit => core::wrap_optimal_fit(&broken_words, line_lengths),
917             core::WrapAlgorithm::FirstFit => core::wrap_first_fit(&broken_words, line_lengths),
918         };
919 
920         let mut idx = 0;
921         for words in wrapped_words {
922             let last_word = match words.last() {
923                 None => {
924                     lines.push(Cow::from(""));
925                     continue;
926                 }
927                 Some(word) => word,
928             };
929 
930             // We assume here that all words are contiguous in `line`.
931             // That is, the sum of their lengths should add up to the
932             // length of `line`.
933             let len = words
934                 .iter()
935                 .map(|word| word.len() + word.whitespace.len())
936                 .sum::<usize>()
937                 - last_word.whitespace.len();
938 
939             // The result is owned if we have indentation, otherwise
940             // we can simply borrow an empty string.
941             let mut result = if lines.is_empty() && !options.initial_indent.is_empty() {
942                 Cow::Owned(options.initial_indent.to_owned())
943             } else if !lines.is_empty() && !options.subsequent_indent.is_empty() {
944                 Cow::Owned(options.subsequent_indent.to_owned())
945             } else {
946                 // We can use an empty string here since string
947                 // concatenation for `Cow` preserves a borrowed value
948                 // when either side is empty.
949                 Cow::from("")
950             };
951 
952             result += &line[idx..idx + len];
953 
954             if !last_word.penalty.is_empty() {
955                 result.to_mut().push_str(&last_word.penalty);
956             }
957 
958             lines.push(result);
959 
960             // Advance by the length of `result`, plus the length of
961             // `last_word.whitespace` -- even if we had a penalty, we
962             // need to skip over the whitespace.
963             idx += len + last_word.whitespace.len();
964         }
965     }
966 
967     lines
968 }
969 
970 /// Wrap text into columns with a given total width.
971 ///
972 /// The `left_gap`, `mid_gap` and `right_gap` arguments specify the
973 /// strings to insert before, between, and after the columns. The
974 /// total width of all columns and all gaps is specified using the
975 /// `total_width_or_options` argument. This argument can simply be an
976 /// integer if you want to use default settings when wrapping, or it
977 /// can be a [`Options`] value if you want to customize the wrapping.
978 ///
979 /// If the columns are narrow, it is recommended to set
980 /// [`Options::break_words`] to `true` to prevent words from
981 /// protruding into the margins.
982 ///
983 /// The per-column width is computed like this:
984 ///
985 /// ```
986 /// # let (left_gap, middle_gap, right_gap) = ("", "", "");
987 /// # let columns = 2;
988 /// # let options = textwrap::Options::new(80);
989 /// let inner_width = options.width
990 ///     - textwrap::core::display_width(left_gap)
991 ///     - textwrap::core::display_width(right_gap)
992 ///     - textwrap::core::display_width(middle_gap) * (columns - 1);
993 /// let column_width = inner_width / columns;
994 /// ```
995 ///
996 /// The `text` is wrapped using [`wrap`] and the given `options`
997 /// argument, but the width is overwritten to the computed
998 /// `column_width`.
999 ///
1000 /// # Panics
1001 ///
1002 /// Panics if `columns` is zero.
1003 ///
1004 /// # Examples
1005 ///
1006 /// ```
1007 /// use textwrap::wrap_columns;
1008 ///
1009 /// let text = "\
1010 /// This is an example text, which is wrapped into three columns. \
1011 /// Notice how the final column can be shorter than the others.";
1012 ///
1013 /// #[cfg(feature = "smawk")]
1014 /// assert_eq!(wrap_columns(text, 3, 50, "| ", " | ", " |"),
1015 ///            vec!["| This is       | into three    | column can be  |",
1016 ///                 "| an example    | columns.      | shorter than   |",
1017 ///                 "| text, which   | Notice how    | the others.    |",
1018 ///                 "| is wrapped    | the final     |                |"]);
1019 ///
1020 /// // Without the `smawk` feature, the middle column is a little more uneven:
1021 /// #[cfg(not(feature = "smawk"))]
1022 /// assert_eq!(wrap_columns(text, 3, 50, "| ", " | ", " |"),
1023 ///            vec!["| This is an    | three         | column can be  |",
1024 ///                 "| example text, | columns.      | shorter than   |",
1025 ///                 "| which is      | Notice how    | the others.    |",
1026 ///                 "| wrapped into  | the final     |                |"]);
wrap_columns<'a, S, Opt>( text: &str, columns: usize, total_width_or_options: Opt, left_gap: &str, mid_gap: &str, right_gap: &str, ) -> Vec<String> where S: WordSplitter, Opt: Into<Options<'a, S>>,1027 pub fn wrap_columns<'a, S, Opt>(
1028     text: &str,
1029     columns: usize,
1030     total_width_or_options: Opt,
1031     left_gap: &str,
1032     mid_gap: &str,
1033     right_gap: &str,
1034 ) -> Vec<String>
1035 where
1036     S: WordSplitter,
1037     Opt: Into<Options<'a, S>>,
1038 {
1039     assert!(columns > 0);
1040 
1041     let mut options = total_width_or_options.into();
1042 
1043     let inner_width = options
1044         .width
1045         .saturating_sub(core::display_width(left_gap))
1046         .saturating_sub(core::display_width(right_gap))
1047         .saturating_sub(core::display_width(mid_gap) * (columns - 1));
1048 
1049     let column_width = std::cmp::max(inner_width / columns, 1);
1050     options.width = column_width;
1051     let last_column_padding = " ".repeat(inner_width % column_width);
1052     let wrapped_lines = wrap(text, options);
1053     let lines_per_column =
1054         wrapped_lines.len() / columns + usize::from(wrapped_lines.len() % columns > 0);
1055     let mut lines = Vec::new();
1056     for line_no in 0..lines_per_column {
1057         let mut line = String::from(left_gap);
1058         for column_no in 0..columns {
1059             match wrapped_lines.get(line_no + column_no * lines_per_column) {
1060                 Some(column_line) => {
1061                     line.push_str(&column_line);
1062                     line.push_str(&" ".repeat(column_width - core::display_width(&column_line)));
1063                 }
1064                 None => {
1065                     line.push_str(&" ".repeat(column_width));
1066                 }
1067             }
1068             if column_no == columns - 1 {
1069                 line.push_str(&last_column_padding);
1070             } else {
1071                 line.push_str(mid_gap);
1072             }
1073         }
1074         line.push_str(right_gap);
1075         lines.push(line);
1076     }
1077 
1078     lines
1079 }
1080 
1081 /// Fill `text` in-place without reallocating the input string.
1082 ///
1083 /// This function works by modifying the input string: some `' '`
1084 /// characters will be replaced by `'\n'` characters. The rest of the
1085 /// text remains untouched.
1086 ///
1087 /// Since we can only replace existing whitespace in the input with
1088 /// `'\n'`, we cannot do hyphenation nor can we split words longer
1089 /// than the line width. Indentation is also ruled out. In other
1090 /// words, `fill_inplace(width)` behaves as if you had called [`fill`]
1091 /// with these options:
1092 ///
1093 /// ```
1094 /// # use textwrap::{Options, NoHyphenation};
1095 /// # let width = 80;
1096 /// Options {
1097 ///     width: width,
1098 ///     initial_indent: "",
1099 ///     subsequent_indent: "",
1100 ///     break_words: false,
1101 ///     wrap_algorithm: textwrap::core::WrapAlgorithm::FirstFit,
1102 ///     splitter: NoHyphenation,
1103 /// };
1104 /// ```
1105 ///
1106 /// The wrap algorithm is [`core::WrapAlgorithm::FirstFit`] since this
1107 /// is the fastest algorithm — and the main reason to use
1108 /// `fill_inplace` is to get the string broken into newlines as fast
1109 /// as possible.
1110 ///
1111 /// A last difference is that (unlike [`fill`]) `fill_inplace` can
1112 /// leave trailing whitespace on lines. This is because we wrap by
1113 /// inserting a `'\n'` at the final whitespace in the input string:
1114 ///
1115 /// ```
1116 /// let mut text = String::from("Hello   World!");
1117 /// textwrap::fill_inplace(&mut text, 10);
1118 /// assert_eq!(text, "Hello  \nWorld!");
1119 /// ```
1120 ///
1121 /// If we didn't do this, the word `World!` would end up being
1122 /// indented. You can avoid this if you make sure that your input text
1123 /// has no double spaces.
1124 ///
1125 /// # Performance
1126 ///
1127 /// In benchmarks, `fill_inplace` is about twice as fast as [`fill`].
1128 /// Please see the [`linear`
1129 /// benchmark](https://github.com/mgeisler/textwrap/blob/master/benches/linear.rs)
1130 /// for details.
fill_inplace(text: &mut String, width: usize)1131 pub fn fill_inplace(text: &mut String, width: usize) {
1132     let mut indices = Vec::new();
1133 
1134     let mut offset = 0;
1135     for line in text.split('\n') {
1136         let words = core::find_words(line).collect::<Vec<_>>();
1137         let wrapped_words = core::wrap_first_fit(&words, |_| width);
1138 
1139         let mut line_offset = offset;
1140         for words in &wrapped_words[..wrapped_words.len() - 1] {
1141             let line_len = words
1142                 .iter()
1143                 .map(|word| word.len() + word.whitespace.len())
1144                 .sum::<usize>();
1145 
1146             line_offset += line_len;
1147             // We've advanced past all ' ' characters -- want to move
1148             // one ' ' backwards and insert our '\n' there.
1149             indices.push(line_offset - 1);
1150         }
1151 
1152         // Advance past entire line, plus the '\n' which was removed
1153         // by the split call above.
1154         offset += line.len() + 1;
1155     }
1156 
1157     let mut bytes = std::mem::take(text).into_bytes();
1158     for idx in indices {
1159         bytes[idx] = b'\n';
1160     }
1161     *text = String::from_utf8(bytes).unwrap();
1162 }
1163 
1164 #[cfg(test)]
1165 mod tests {
1166     use super::*;
1167     #[cfg(feature = "hyphenation")]
1168     use hyphenation::{Language, Load, Standard};
1169 
1170     #[test]
options_agree_with_usize()1171     fn options_agree_with_usize() {
1172         let opt_usize = Options::from(42_usize);
1173         let opt_options = Options::new(42);
1174 
1175         assert_eq!(opt_usize.width, opt_options.width);
1176         assert_eq!(opt_usize.initial_indent, opt_options.initial_indent);
1177         assert_eq!(opt_usize.subsequent_indent, opt_options.subsequent_indent);
1178         assert_eq!(opt_usize.break_words, opt_options.break_words);
1179         assert_eq!(
1180             opt_usize.splitter.split_points("hello-world"),
1181             opt_options.splitter.split_points("hello-world")
1182         );
1183     }
1184 
1185     #[test]
no_wrap()1186     fn no_wrap() {
1187         assert_eq!(wrap("foo", 10), vec!["foo"]);
1188     }
1189 
1190     #[test]
wrap_simple()1191     fn wrap_simple() {
1192         assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]);
1193     }
1194 
1195     #[test]
to_be_or_not()1196     fn to_be_or_not() {
1197         assert_eq!(
1198             wrap(
1199                 "To be, or not to be, that is the question.",
1200                 Options::new(10).wrap_algorithm(core::WrapAlgorithm::FirstFit)
1201             ),
1202             vec!["To be, or", "not to be,", "that is", "the", "question."]
1203         );
1204     }
1205 
1206     #[test]
multiple_words_on_first_line()1207     fn multiple_words_on_first_line() {
1208         assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]);
1209     }
1210 
1211     #[test]
long_word()1212     fn long_word() {
1213         assert_eq!(wrap("foo", 0), vec!["f", "o", "o"]);
1214     }
1215 
1216     #[test]
long_words()1217     fn long_words() {
1218         assert_eq!(wrap("foo bar", 0), vec!["f", "o", "o", "b", "a", "r"]);
1219     }
1220 
1221     #[test]
max_width()1222     fn max_width() {
1223         assert_eq!(wrap("foo bar", usize::max_value()), vec!["foo bar"]);
1224     }
1225 
1226     #[test]
leading_whitespace()1227     fn leading_whitespace() {
1228         assert_eq!(wrap("  foo bar", 6), vec!["  foo", "bar"]);
1229     }
1230 
1231     #[test]
trailing_whitespace()1232     fn trailing_whitespace() {
1233         // Whitespace is only significant inside a line. After a line
1234         // gets too long and is broken, the first word starts in
1235         // column zero and is not indented.
1236         assert_eq!(wrap("foo     bar     baz  ", 5), vec!["foo", "bar", "baz"]);
1237     }
1238 
1239     #[test]
issue_99()1240     fn issue_99() {
1241         // We did not reset the in_whitespace flag correctly and did
1242         // not handle single-character words after a line break.
1243         assert_eq!(
1244             wrap("aaabbbccc x yyyzzzwww", 9),
1245             vec!["aaabbbccc", "x", "yyyzzzwww"]
1246         );
1247     }
1248 
1249     #[test]
issue_129()1250     fn issue_129() {
1251         // The dash is an em-dash which takes up four bytes. We used
1252         // to panic since we tried to index into the character.
1253         assert_eq!(wrap("x – x", 1), vec!["x", "–", "x"]);
1254     }
1255 
1256     #[test]
1257     #[cfg(feature = "unicode-width")]
wide_character_handling()1258     fn wide_character_handling() {
1259         assert_eq!(wrap("Hello, World!", 15), vec!["Hello, World!"]);
1260         assert_eq!(
1261             wrap("Hello, World!", 15),
1262             vec!["Hello,", "World!"]
1263         );
1264     }
1265 
1266     #[test]
empty_line_is_indented()1267     fn empty_line_is_indented() {
1268         // Previously, indentation was not applied to empty lines.
1269         // However, this is somewhat inconsistent and undesirable if
1270         // the indentation is something like a border ("| ") which you
1271         // want to apply to all lines, empty or not.
1272         let options = Options::new(10).initial_indent("!!!");
1273         assert_eq!(fill("", &options), "!!!");
1274     }
1275 
1276     #[test]
indent_single_line()1277     fn indent_single_line() {
1278         let options = Options::new(10).initial_indent(">>>"); // No trailing space
1279         assert_eq!(fill("foo", &options), ">>>foo");
1280     }
1281 
1282     #[test]
1283     #[cfg(feature = "unicode-width")]
indent_first_emoji()1284     fn indent_first_emoji() {
1285         let options = Options::new(10).initial_indent("����");
1286         assert_eq!(
1287             wrap("x x x x x x x x x x x x x", &options),
1288             vec!["����x x x", "x x x x x", "x x x x x"]
1289         );
1290     }
1291 
1292     #[test]
indent_multiple_lines()1293     fn indent_multiple_lines() {
1294         let options = Options::new(6).initial_indent("* ").subsequent_indent("  ");
1295         assert_eq!(
1296             wrap("foo bar baz", &options),
1297             vec!["* foo", "  bar", "  baz"]
1298         );
1299     }
1300 
1301     #[test]
indent_break_words()1302     fn indent_break_words() {
1303         let options = Options::new(5).initial_indent("* ").subsequent_indent("  ");
1304         assert_eq!(wrap("foobarbaz", &options), vec!["* foo", "  bar", "  baz"]);
1305     }
1306 
1307     #[test]
initial_indent_break_words()1308     fn initial_indent_break_words() {
1309         // This is a corner-case showing how the long word is broken
1310         // according to the width of the subsequent lines. The first
1311         // fragment of the word no longer fits on the first line,
1312         // which ends up being pure indentation.
1313         let options = Options::new(5).initial_indent("-->");
1314         assert_eq!(wrap("foobarbaz", &options), vec!["-->", "fooba", "rbaz"]);
1315     }
1316 
1317     #[test]
hyphens()1318     fn hyphens() {
1319         assert_eq!(wrap("foo-bar", 5), vec!["foo-", "bar"]);
1320     }
1321 
1322     #[test]
trailing_hyphen()1323     fn trailing_hyphen() {
1324         let options = Options::new(5).break_words(false);
1325         assert_eq!(wrap("foobar-", &options), vec!["foobar-"]);
1326     }
1327 
1328     #[test]
multiple_hyphens()1329     fn multiple_hyphens() {
1330         assert_eq!(wrap("foo-bar-baz", 5), vec!["foo-", "bar-", "baz"]);
1331     }
1332 
1333     #[test]
hyphens_flag()1334     fn hyphens_flag() {
1335         let options = Options::new(5).break_words(false);
1336         assert_eq!(
1337             wrap("The --foo-bar flag.", &options),
1338             vec!["The", "--foo-", "bar", "flag."]
1339         );
1340     }
1341 
1342     #[test]
repeated_hyphens()1343     fn repeated_hyphens() {
1344         let options = Options::new(4).break_words(false);
1345         assert_eq!(wrap("foo--bar", &options), vec!["foo--bar"]);
1346     }
1347 
1348     #[test]
hyphens_alphanumeric()1349     fn hyphens_alphanumeric() {
1350         assert_eq!(wrap("Na2-CH4", 5), vec!["Na2-", "CH4"]);
1351     }
1352 
1353     #[test]
hyphens_non_alphanumeric()1354     fn hyphens_non_alphanumeric() {
1355         let options = Options::new(5).break_words(false);
1356         assert_eq!(wrap("foo(-)bar", &options), vec!["foo(-)bar"]);
1357     }
1358 
1359     #[test]
multiple_splits()1360     fn multiple_splits() {
1361         assert_eq!(wrap("foo-bar-baz", 9), vec!["foo-bar-", "baz"]);
1362     }
1363 
1364     #[test]
forced_split()1365     fn forced_split() {
1366         let options = Options::new(5).break_words(false);
1367         assert_eq!(wrap("foobar-baz", &options), vec!["foobar-", "baz"]);
1368     }
1369 
1370     #[test]
multiple_unbroken_words_issue_193()1371     fn multiple_unbroken_words_issue_193() {
1372         let options = Options::new(3).break_words(false);
1373         assert_eq!(
1374             wrap("small large tiny", &options),
1375             vec!["small", "large", "tiny"]
1376         );
1377         assert_eq!(
1378             wrap("small  large   tiny", &options),
1379             vec!["small", "large", "tiny"]
1380         );
1381     }
1382 
1383     #[test]
very_narrow_lines_issue_193()1384     fn very_narrow_lines_issue_193() {
1385         let options = Options::new(1).break_words(false);
1386         assert_eq!(wrap("fooo x y", &options), vec!["fooo", "x", "y"]);
1387         assert_eq!(wrap("fooo   x     y", &options), vec!["fooo", "x", "y"]);
1388     }
1389 
1390     #[test]
simple_hyphens_static()1391     fn simple_hyphens_static() {
1392         let options = Options::new(8).splitter(HyphenSplitter);
1393         assert_eq!(wrap("foo bar-baz", &options), vec!["foo bar-", "baz"]);
1394     }
1395 
1396     #[test]
simple_hyphens_dynamic()1397     fn simple_hyphens_dynamic() {
1398         let options: Options = Options::new(8).splitter(Box::new(HyphenSplitter));
1399         assert_eq!(wrap("foo bar-baz", &options), vec!["foo bar-", "baz"]);
1400     }
1401 
1402     #[test]
no_hyphenation_static()1403     fn no_hyphenation_static() {
1404         let options = Options::new(8).splitter(NoHyphenation);
1405         assert_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]);
1406     }
1407 
1408     #[test]
no_hyphenation_dynamic()1409     fn no_hyphenation_dynamic() {
1410         let options: Options = Options::new(8).splitter(Box::new(NoHyphenation));
1411         assert_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]);
1412     }
1413 
1414     #[test]
1415     #[cfg(feature = "hyphenation")]
auto_hyphenation_double_hyphenation_static()1416     fn auto_hyphenation_double_hyphenation_static() {
1417         let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
1418         let options = Options::new(10);
1419         assert_eq!(
1420             wrap("Internationalization", &options),
1421             vec!["Internatio", "nalization"]
1422         );
1423 
1424         let options = Options::new(10).splitter(dictionary);
1425         assert_eq!(
1426             wrap("Internationalization", &options),
1427             vec!["Interna-", "tionaliza-", "tion"]
1428         );
1429     }
1430 
1431     #[test]
1432     #[cfg(feature = "hyphenation")]
auto_hyphenation_double_hyphenation_dynamic()1433     fn auto_hyphenation_double_hyphenation_dynamic() {
1434         let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
1435         let mut options: Options = Options::new(10).splitter(Box::new(HyphenSplitter));
1436         assert_eq!(
1437             wrap("Internationalization", &options),
1438             vec!["Internatio", "nalization"]
1439         );
1440 
1441         options = Options::new(10).splitter(Box::new(dictionary));
1442         assert_eq!(
1443             wrap("Internationalization", &options),
1444             vec!["Interna-", "tionaliza-", "tion"]
1445         );
1446     }
1447 
1448     #[test]
1449     #[cfg(feature = "hyphenation")]
auto_hyphenation_issue_158()1450     fn auto_hyphenation_issue_158() {
1451         let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
1452         let options = Options::new(10);
1453         assert_eq!(
1454             wrap("participation is the key to success", &options),
1455             vec!["participat", "ion is", "the key to", "success"]
1456         );
1457 
1458         let options = Options::new(10).splitter(dictionary);
1459         assert_eq!(
1460             wrap("participation is the key to success", &options),
1461             vec!["partici-", "pation is", "the key to", "success"]
1462         );
1463     }
1464 
1465     #[test]
1466     #[cfg(feature = "hyphenation")]
split_len_hyphenation()1467     fn split_len_hyphenation() {
1468         // Test that hyphenation takes the width of the wihtespace
1469         // into account.
1470         let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
1471         let options = Options::new(15).splitter(dictionary);
1472         assert_eq!(
1473             wrap("garbage   collection", &options),
1474             vec!["garbage   col-", "lection"]
1475         );
1476     }
1477 
1478     #[test]
1479     #[cfg(feature = "hyphenation")]
borrowed_lines()1480     fn borrowed_lines() {
1481         // Lines that end with an extra hyphen are owned, the final
1482         // line is borrowed.
1483         use std::borrow::Cow::{Borrowed, Owned};
1484         let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
1485         let options = Options::new(10).splitter(dictionary);
1486         let lines = wrap("Internationalization", &options);
1487         if let Borrowed(s) = lines[0] {
1488             assert!(false, "should not have been borrowed: {:?}", s);
1489         }
1490         if let Borrowed(s) = lines[1] {
1491             assert!(false, "should not have been borrowed: {:?}", s);
1492         }
1493         if let Owned(ref s) = lines[2] {
1494             assert!(false, "should not have been owned: {:?}", s);
1495         }
1496     }
1497 
1498     #[test]
1499     #[cfg(feature = "hyphenation")]
auto_hyphenation_with_hyphen()1500     fn auto_hyphenation_with_hyphen() {
1501         let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
1502         let options = Options::new(8).break_words(false);
1503         assert_eq!(
1504             wrap("over-caffinated", &options),
1505             vec!["over-", "caffinated"]
1506         );
1507 
1508         let options = options.splitter(dictionary);
1509         assert_eq!(
1510             wrap("over-caffinated", &options),
1511             vec!["over-", "caffi-", "nated"]
1512         );
1513     }
1514 
1515     #[test]
break_words()1516     fn break_words() {
1517         assert_eq!(wrap("foobarbaz", 3), vec!["foo", "bar", "baz"]);
1518     }
1519 
1520     #[test]
break_words_wide_characters()1521     fn break_words_wide_characters() {
1522         // Even the poor man's version of `ch_width` counts these
1523         // characters as wide.
1524         assert_eq!(wrap("Hello", 5), vec!["He", "ll", "o"]);
1525     }
1526 
1527     #[test]
break_words_zero_width()1528     fn break_words_zero_width() {
1529         assert_eq!(wrap("foobar", 0), vec!["f", "o", "o", "b", "a", "r"]);
1530     }
1531 
1532     #[test]
break_long_first_word()1533     fn break_long_first_word() {
1534         assert_eq!(wrap("testx y", 4), vec!["test", "x y"]);
1535     }
1536 
1537     #[test]
break_words_line_breaks()1538     fn break_words_line_breaks() {
1539         assert_eq!(fill("ab\ncdefghijkl", 5), "ab\ncdefg\nhijkl");
1540         assert_eq!(fill("abcdefgh\nijkl", 5), "abcde\nfgh\nijkl");
1541     }
1542 
1543     #[test]
break_words_empty_lines()1544     fn break_words_empty_lines() {
1545         assert_eq!(
1546             fill("foo\nbar", &Options::new(2).break_words(false)),
1547             "foo\nbar"
1548         );
1549     }
1550 
1551     #[test]
preserve_line_breaks()1552     fn preserve_line_breaks() {
1553         assert_eq!(fill("", 80), "");
1554         assert_eq!(fill("\n", 80), "\n");
1555         assert_eq!(fill("\n\n\n", 80), "\n\n\n");
1556         assert_eq!(fill("test\n", 80), "test\n");
1557         assert_eq!(fill("test\n\na\n\n", 80), "test\n\na\n\n");
1558         assert_eq!(
1559             fill(
1560                 "1 3 5 7\n1 3 5 7",
1561                 Options::new(7).wrap_algorithm(core::WrapAlgorithm::FirstFit)
1562             ),
1563             "1 3 5 7\n1 3 5 7"
1564         );
1565         assert_eq!(
1566             fill(
1567                 "1 3 5 7\n1 3 5 7",
1568                 Options::new(5).wrap_algorithm(core::WrapAlgorithm::FirstFit)
1569             ),
1570             "1 3 5\n7\n1 3 5\n7"
1571         );
1572     }
1573 
1574     #[test]
preserve_line_breaks_with_whitespace()1575     fn preserve_line_breaks_with_whitespace() {
1576         assert_eq!(fill("  ", 80), "");
1577         assert_eq!(fill("  \n  ", 80), "\n");
1578         assert_eq!(fill("  \n \n  \n ", 80), "\n\n\n");
1579     }
1580 
1581     #[test]
non_breaking_space()1582     fn non_breaking_space() {
1583         let options = Options::new(5).break_words(false);
1584         assert_eq!(fill("foo bar baz", &options), "foo bar baz");
1585     }
1586 
1587     #[test]
non_breaking_hyphen()1588     fn non_breaking_hyphen() {
1589         let options = Options::new(5).break_words(false);
1590         assert_eq!(fill("foo‑bar‑baz", &options), "foo‑bar‑baz");
1591     }
1592 
1593     #[test]
fill_simple()1594     fn fill_simple() {
1595         assert_eq!(fill("foo bar baz", 10), "foo bar\nbaz");
1596     }
1597 
1598     #[test]
fill_colored_text()1599     fn fill_colored_text() {
1600         // The words are much longer than 6 bytes, but they remain
1601         // intact after filling the text.
1602         let green_hello = "\u{1b}[0m\u{1b}[32mHello\u{1b}[0m";
1603         let blue_world = "\u{1b}[0m\u{1b}[34mWorld!\u{1b}[0m";
1604         assert_eq!(
1605             fill(&(String::from(green_hello) + " " + &blue_world), 6),
1606             String::from(green_hello) + "\n" + &blue_world
1607         );
1608     }
1609 
1610     #[test]
cloning_works()1611     fn cloning_works() {
1612         static OPT: Options<HyphenSplitter> = Options::with_splitter(80, HyphenSplitter);
1613         #[allow(clippy::clone_on_copy)]
1614         let opt = OPT.clone();
1615         assert_eq!(opt.width, 80);
1616     }
1617 
1618     #[test]
fill_inplace_empty()1619     fn fill_inplace_empty() {
1620         let mut text = String::from("");
1621         fill_inplace(&mut text, 80);
1622         assert_eq!(text, "");
1623     }
1624 
1625     #[test]
fill_inplace_simple()1626     fn fill_inplace_simple() {
1627         let mut text = String::from("foo bar baz");
1628         fill_inplace(&mut text, 10);
1629         assert_eq!(text, "foo bar\nbaz");
1630     }
1631 
1632     #[test]
fill_inplace_multiple_lines()1633     fn fill_inplace_multiple_lines() {
1634         let mut text = String::from("Some text to wrap over multiple lines");
1635         fill_inplace(&mut text, 12);
1636         assert_eq!(text, "Some text to\nwrap over\nmultiple\nlines");
1637     }
1638 
1639     #[test]
fill_inplace_long_word()1640     fn fill_inplace_long_word() {
1641         let mut text = String::from("Internationalization is hard");
1642         fill_inplace(&mut text, 10);
1643         assert_eq!(text, "Internationalization\nis hard");
1644     }
1645 
1646     #[test]
fill_inplace_no_hyphen_splitting()1647     fn fill_inplace_no_hyphen_splitting() {
1648         let mut text = String::from("A well-chosen example");
1649         fill_inplace(&mut text, 10);
1650         assert_eq!(text, "A\nwell-chosen\nexample");
1651     }
1652 
1653     #[test]
fill_inplace_newlines()1654     fn fill_inplace_newlines() {
1655         let mut text = String::from("foo bar\n\nbaz\n\n\n");
1656         fill_inplace(&mut text, 10);
1657         assert_eq!(text, "foo bar\n\nbaz\n\n\n");
1658     }
1659 
1660     #[test]
fill_inplace_newlines_reset_line_width()1661     fn fill_inplace_newlines_reset_line_width() {
1662         let mut text = String::from("1 3 5\n1 3 5 7 9\n1 3 5 7 9 1 3");
1663         fill_inplace(&mut text, 10);
1664         assert_eq!(text, "1 3 5\n1 3 5 7 9\n1 3 5 7 9\n1 3");
1665     }
1666 
1667     #[test]
fill_inplace_leading_whitespace()1668     fn fill_inplace_leading_whitespace() {
1669         let mut text = String::from("  foo bar baz");
1670         fill_inplace(&mut text, 10);
1671         assert_eq!(text, "  foo bar\nbaz");
1672     }
1673 
1674     #[test]
fill_inplace_trailing_whitespace()1675     fn fill_inplace_trailing_whitespace() {
1676         let mut text = String::from("foo bar baz  ");
1677         fill_inplace(&mut text, 10);
1678         assert_eq!(text, "foo bar\nbaz  ");
1679     }
1680 
1681     #[test]
fill_inplace_interior_whitespace()1682     fn fill_inplace_interior_whitespace() {
1683         // To avoid an unwanted indentation of "baz", it is important
1684         // to replace the final ' ' with '\n'.
1685         let mut text = String::from("foo  bar    baz");
1686         fill_inplace(&mut text, 10);
1687         assert_eq!(text, "foo  bar   \nbaz");
1688     }
1689 
1690     #[test]
unfill_simple()1691     fn unfill_simple() {
1692         let (text, options) = unfill("foo\nbar");
1693         assert_eq!(text, "foo bar");
1694         assert_eq!(options.width, 3);
1695     }
1696 
1697     #[test]
unfill_trailing_newlines()1698     fn unfill_trailing_newlines() {
1699         let (text, options) = unfill("foo\nbar\n\n\n");
1700         assert_eq!(text, "foo bar\n\n\n");
1701         assert_eq!(options.width, 3);
1702     }
1703 
1704     #[test]
unfill_initial_indent()1705     fn unfill_initial_indent() {
1706         let (text, options) = unfill("  foo\nbar\nbaz");
1707         assert_eq!(text, "foo bar baz");
1708         assert_eq!(options.width, 5);
1709         assert_eq!(options.initial_indent, "  ");
1710     }
1711 
1712     #[test]
unfill_differing_indents()1713     fn unfill_differing_indents() {
1714         let (text, options) = unfill("  foo\n    bar\n  baz");
1715         assert_eq!(text, "foo   bar baz");
1716         assert_eq!(options.width, 7);
1717         assert_eq!(options.initial_indent, "  ");
1718         assert_eq!(options.subsequent_indent, "  ");
1719     }
1720 
1721     #[test]
unfill_list_item()1722     fn unfill_list_item() {
1723         let (text, options) = unfill("* foo\n  bar\n  baz");
1724         assert_eq!(text, "foo bar baz");
1725         assert_eq!(options.width, 5);
1726         assert_eq!(options.initial_indent, "* ");
1727         assert_eq!(options.subsequent_indent, "  ");
1728     }
1729 
1730     #[test]
unfill_multiple_char_prefix()1731     fn unfill_multiple_char_prefix() {
1732         let (text, options) = unfill("    // foo bar\n    // baz\n    // quux");
1733         assert_eq!(text, "foo bar baz quux");
1734         assert_eq!(options.width, 14);
1735         assert_eq!(options.initial_indent, "    // ");
1736         assert_eq!(options.subsequent_indent, "    // ");
1737     }
1738 
1739     #[test]
unfill_block_quote()1740     fn unfill_block_quote() {
1741         let (text, options) = unfill("> foo\n> bar\n> baz");
1742         assert_eq!(text, "foo bar baz");
1743         assert_eq!(options.width, 5);
1744         assert_eq!(options.initial_indent, "> ");
1745         assert_eq!(options.subsequent_indent, "> ");
1746     }
1747 
1748     #[test]
unfill_whitespace()1749     fn unfill_whitespace() {
1750         assert_eq!(unfill("foo   bar").0, "foo   bar");
1751     }
1752 
1753     #[test]
trait_object()1754     fn trait_object() {
1755         let opt_a: Options<NoHyphenation> = Options::with_splitter(20, NoHyphenation);
1756         let opt_b: Options<HyphenSplitter> = 10.into();
1757 
1758         let mut dyn_opt: &Options<dyn WordSplitter> = &opt_a;
1759         assert_eq!(wrap("foo bar-baz", dyn_opt), vec!["foo bar-baz"]);
1760 
1761         // Just assign a totally different option
1762         dyn_opt = &opt_b;
1763         assert_eq!(wrap("foo bar-baz", dyn_opt), vec!["foo bar-", "baz"]);
1764     }
1765 
1766     #[test]
trait_object_vec()1767     fn trait_object_vec() {
1768         // Create a vector of referenced trait-objects
1769         let mut vector: Vec<&Options<dyn WordSplitter>> = Vec::new();
1770         // Expected result from each options
1771         let mut results = Vec::new();
1772 
1773         let opt_usize: Options<_> = 10.into();
1774         vector.push(&opt_usize);
1775         results.push(vec!["over-", "caffinated"]);
1776 
1777         #[cfg(feature = "hyphenation")]
1778         let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
1779         #[cfg(feature = "hyphenation")]
1780         let opt_hyp = Options::new(8).splitter(dictionary);
1781         #[cfg(feature = "hyphenation")]
1782         vector.push(&opt_hyp);
1783         #[cfg(feature = "hyphenation")]
1784         results.push(vec!["over-", "caffi-", "nated"]);
1785 
1786         // Actually: Options<Box<dyn WordSplitter>>
1787         let opt_box: Options = Options::new(10)
1788             .break_words(false)
1789             .splitter(Box::new(NoHyphenation));
1790         vector.push(&opt_box);
1791         results.push(vec!["over-caffinated"]);
1792 
1793         // Test each entry
1794         for (opt, expected) in vector.into_iter().zip(results) {
1795             assert_eq!(
1796                 // Just all the totally different options
1797                 wrap("over-caffinated", opt),
1798                 expected
1799             );
1800         }
1801     }
1802 
1803     #[test]
outer_boxing()1804     fn outer_boxing() {
1805         let mut wrapper: Box<Options<dyn WordSplitter>> = Box::new(Options::new(80));
1806 
1807         // We must first deref the Box into a trait object and pass it by-reference
1808         assert_eq!(wrap("foo bar baz", &*wrapper), vec!["foo bar baz"]);
1809 
1810         // Replace the `Options` with a `usize`
1811         wrapper = Box::new(Options::from(5));
1812 
1813         // Deref per-se works as well, it already returns a reference
1814         use std::ops::Deref;
1815         assert_eq!(
1816             wrap("foo bar baz", wrapper.deref()),
1817             vec!["foo", "bar", "baz"]
1818         );
1819     }
1820 
1821     #[test]
wrap_columns_empty_text()1822     fn wrap_columns_empty_text() {
1823         assert_eq!(wrap_columns("", 1, 10, "| ", "", " |"), vec!["|        |"]);
1824     }
1825 
1826     #[test]
wrap_columns_single_column()1827     fn wrap_columns_single_column() {
1828         assert_eq!(
1829             wrap_columns("Foo", 3, 30, "| ", " | ", " |"),
1830             vec!["| Foo    |        |          |"]
1831         );
1832     }
1833 
1834     #[test]
wrap_columns_uneven_columns()1835     fn wrap_columns_uneven_columns() {
1836         // The gaps take up a total of 5 columns, so the columns are
1837         // (21 - 5)/4 = 4 columns wide:
1838         assert_eq!(
1839             wrap_columns("Foo Bar Baz Quux", 4, 21, "|", "|", "|"),
1840             vec!["|Foo |Bar |Baz |Quux|"]
1841         );
1842         // As the total width increases, the last column absorbs the
1843         // excess width:
1844         assert_eq!(
1845             wrap_columns("Foo Bar Baz Quux", 4, 24, "|", "|", "|"),
1846             vec!["|Foo |Bar |Baz |Quux   |"]
1847         );
1848         // Finally, when the width is 25, the columns can be resized
1849         // to a width of (25 - 5)/4 = 5 columns:
1850         assert_eq!(
1851             wrap_columns("Foo Bar Baz Quux", 4, 25, "|", "|", "|"),
1852             vec!["|Foo  |Bar  |Baz  |Quux |"]
1853         );
1854     }
1855 
1856     #[test]
1857     #[cfg(feature = "unicode-width")]
wrap_columns_with_emojis()1858     fn wrap_columns_with_emojis() {
1859         assert_eq!(
1860             wrap_columns(
1861                 "Words and a few emojis �� wrapped in ⓶ columns",
1862                 2,
1863                 30,
1864                 "✨ ",
1865                 " ⚽ ",
1866                 " ��"
1867             ),
1868             vec![
1869                 "✨ Words      ⚽ wrapped in ��",
1870                 "✨ and a few  ⚽ ⓶ columns  ��",
1871                 "✨ emojis ��  ⚽            ��"
1872             ]
1873         );
1874     }
1875 
1876     #[test]
wrap_columns_big_gaps()1877     fn wrap_columns_big_gaps() {
1878         // The column width shrinks to 1 because the gaps take up all
1879         // the space.
1880         assert_eq!(
1881             wrap_columns("xyz", 2, 10, "----> ", " !!! ", " <----"),
1882             vec![
1883                 "----> x !!! z <----", //
1884                 "----> y !!!   <----"
1885             ]
1886         );
1887     }
1888 
1889     #[test]
1890     #[should_panic]
wrap_columns_panic_with_zero_columns()1891     fn wrap_columns_panic_with_zero_columns() {
1892         wrap_columns("", 0, 10, "", "", "");
1893     }
1894 }
1895