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