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