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