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