1 //! `textwrap` provides functions for word wrapping and filling text. 2 //! 3 //! Wrapping text can be very useful in commandline programs where you 4 //! want to format dynamic output nicely so it looks good in a 5 //! terminal. A quick example: 6 //! 7 //! ```no_run 8 //! fn main() { 9 //! let text = "textwrap: a small library for wrapping text."; 10 //! println!("{}", textwrap::fill(text, 18)); 11 //! } 12 //! ``` 13 //! 14 //! When you run this program, it will display the following output: 15 //! 16 //! ```text 17 //! textwrap: a small 18 //! library for 19 //! wrapping text. 20 //! ``` 21 //! 22 //! If you enable the `hyphenation` feature, you can get automatic 23 //! hyphenation for a number of languages: 24 //! 25 //! ```no_run 26 //! # #[cfg(feature = "hyphenation")] 27 //! use hyphenation::{Language, Load, Standard}; 28 //! use textwrap::Wrapper; 29 //! 30 //! # #[cfg(feature = "hyphenation")] 31 //! fn main() { 32 //! let text = "textwrap: a small library for wrapping text."; 33 //! let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); 34 //! let wrapper = Wrapper::with_splitter(18, dictionary); 35 //! println!("{}", wrapper.fill(text)); 36 //! } 37 //! 38 //! # #[cfg(not(feature = "hyphenation"))] 39 //! # fn main() { } 40 //! ``` 41 //! 42 //! The program will now output: 43 //! 44 //! ```text 45 //! textwrap: a small 46 //! library for wrap- 47 //! ping text. 48 //! ``` 49 //! 50 //! # Wrapping Strings at Compile Time 51 //! 52 //! If your strings are known at compile time, please take a look at 53 //! the procedural macros from the [`textwrap-macros` crate]. 54 //! 55 //! # Displayed Width vs Byte Size 56 //! 57 //! To word wrap text, one must know the width of each word so one can 58 //! know when to break lines. This library measures the width of text 59 //! using the [displayed width][unicode-width], not the size in bytes. 60 //! 61 //! This is important for non-ASCII text. ASCII characters such as `a` 62 //! and `!` are simple and take up one column each. This means that 63 //! the displayed width is equal to the string length in bytes. 64 //! However, non-ASCII characters and symbols take up more than one 65 //! byte when UTF-8 encoded: `é` is `0xc3 0xa9` (two bytes) and `⚙` is 66 //! `0xe2 0x9a 0x99` (three bytes) in UTF-8, respectively. 67 //! 68 //! This is why we take care to use the displayed width instead of the 69 //! byte count when computing line lengths. All functions in this 70 //! library handle Unicode characters like this. 71 //! 72 //! # Cargo Features 73 //! 74 //! The library has two optional features: 75 //! 76 //! * `terminal_size`: enables automatic detection of the terminal 77 //! width via the [terminal_size][] crate. See the 78 //! [`Wrapper::with_termwidth`] constructor for details. 79 //! 80 //! * `hyphenation`: enables language-sentive hyphenation via the 81 //! [hyphenation][] crate. See the [`WordSplitter`] trait for 82 //! details. 83 //! 84 //! [`textwrap-macros` crate]: https://crates.io/crates/textwrap-macros 85 //! [unicode-width]: https://docs.rs/unicode-width/ 86 //! [terminal_size]: https://crates.io/crates/terminal_size 87 //! [hyphenation]: https://crates.io/crates/hyphenation 88 //! [`Wrapper::with_termwidth`]: struct.Wrapper.html#method.with_termwidth 89 //! [`WordSplitter`]: trait.WordSplitter.html 90 91 #![doc(html_root_url = "https://docs.rs/textwrap/0.12.1")] 92 #![deny(missing_docs)] 93 #![deny(missing_debug_implementations)] 94 #![allow(clippy::redundant_field_names)] 95 96 use std::borrow::Cow; 97 use std::str::CharIndices; 98 99 use unicode_width::UnicodeWidthChar; 100 use unicode_width::UnicodeWidthStr; 101 102 /// A non-breaking space. 103 const NBSP: char = '\u{a0}'; 104 105 /// The CSI or "Control Sequence Introducer" introduces an ANSI escape 106 /// sequence. This is typically used for colored text and will be 107 /// ignored when computing the text width. 108 const CSI: (char, char) = ('\u{1b}', '['); 109 /// The final bytes of an ANSI escape sequence must be in this range. 110 const ANSI_FINAL_BYTE: std::ops::RangeInclusive<char> = '\x40'..='\x7e'; 111 112 mod indentation; 113 pub use crate::indentation::dedent; 114 pub use crate::indentation::indent; 115 116 mod splitting; 117 pub use crate::splitting::{HyphenSplitter, NoHyphenation, WordSplitter}; 118 119 /// A Wrapper holds settings for wrapping and filling text. Use it 120 /// when the convenience [`wrap_iter`], [`wrap`] and [`fill`] functions 121 /// are not flexible enough. 122 /// 123 /// [`wrap_iter`]: fn.wrap_iter.html 124 /// [`wrap`]: fn.wrap.html 125 /// [`fill`]: fn.fill.html 126 /// 127 /// The algorithm used by the `WrapIter` iterator (returned from the 128 /// `wrap_iter` method) works by doing successive partial scans over 129 /// words in the input string (where each single scan yields a single 130 /// line) so that the overall time and memory complexity is O(*n*) where 131 /// *n* is the length of the input string. 132 #[derive(Clone, Debug)] 133 pub struct Wrapper<'a, S: WordSplitter> { 134 /// The width in columns at which the text will be wrapped. 135 pub width: usize, 136 /// Indentation used for the first line of output. 137 pub initial_indent: &'a str, 138 /// Indentation used for subsequent lines of output. 139 pub subsequent_indent: &'a str, 140 /// Allow long words to be broken if they cannot fit on a line. 141 /// When set to `false`, some lines may be longer than 142 /// `self.width`. 143 pub break_words: bool, 144 /// The method for splitting words. If the `hyphenation` feature 145 /// is enabled, you can use a `hyphenation::Standard` dictionary 146 /// here to get language-aware hyphenation. 147 pub splitter: S, 148 } 149 150 impl<'a> Wrapper<'a, HyphenSplitter> { 151 /// Create a new Wrapper for wrapping at the specified width. By 152 /// default, we allow words longer than `width` to be broken. A 153 /// [`HyphenSplitter`] will be used by default for splitting 154 /// words. See the [`WordSplitter`] trait for other options. 155 /// 156 /// [`HyphenSplitter`]: struct.HyphenSplitter.html 157 /// [`WordSplitter`]: trait.WordSplitter.html 158 pub fn new(width: usize) -> Wrapper<'a, HyphenSplitter> { 159 Wrapper::with_splitter(width, HyphenSplitter) 160 } 161 162 /// Create a new Wrapper for wrapping text at the current terminal 163 /// width. If the terminal width cannot be determined (typically 164 /// because the standard input and output is not connected to a 165 /// terminal), a width of 80 characters will be used. Other 166 /// settings use the same defaults as `Wrapper::new`. 167 /// 168 /// Equivalent to: 169 /// 170 /// ```no_run 171 /// # #![allow(unused_variables)] 172 /// use textwrap::{Wrapper, termwidth}; 173 /// 174 /// let wrapper = Wrapper::new(termwidth()); 175 /// ``` 176 /// 177 /// **Note:** Only available when the `terminal_size` feature is 178 /// enabled. 179 #[cfg(feature = "terminal_size")] 180 pub fn with_termwidth() -> Wrapper<'a, HyphenSplitter> { 181 Wrapper::new(termwidth()) 182 } 183 } 184 185 impl<'a, S: WordSplitter> Wrapper<'a, S> { 186 /// Use the given [`WordSplitter`] to create a new Wrapper for 187 /// wrapping at the specified width. By default, we allow words 188 /// longer than `width` to be broken. 189 /// 190 /// [`WordSplitter`]: trait.WordSplitter.html 191 pub fn with_splitter(width: usize, splitter: S) -> Wrapper<'a, S> { 192 Wrapper { 193 width: width, 194 initial_indent: "", 195 subsequent_indent: "", 196 break_words: true, 197 splitter: splitter, 198 } 199 } 200 201 /// Change [`self.initial_indent`]. The initial indentation is 202 /// used on the very first line of output. 203 /// 204 /// # Examples 205 /// 206 /// Classic paragraph indentation can be achieved by specifying an 207 /// initial indentation and wrapping each paragraph by itself: 208 /// 209 /// ```no_run 210 /// # #![allow(unused_variables)] 211 /// use textwrap::Wrapper; 212 /// 213 /// let wrapper = Wrapper::new(15).initial_indent(" "); 214 /// ``` 215 /// 216 /// [`self.initial_indent`]: #structfield.initial_indent 217 pub fn initial_indent(self, indent: &'a str) -> Wrapper<'a, S> { 218 Wrapper { 219 initial_indent: indent, 220 ..self 221 } 222 } 223 224 /// Change [`self.subsequent_indent`]. The subsequent indentation 225 /// is used on lines following the first line of output. 226 /// 227 /// # Examples 228 /// 229 /// Combining initial and subsequent indentation lets you format a 230 /// single paragraph as a bullet list: 231 /// 232 /// ```no_run 233 /// # #![allow(unused_variables)] 234 /// use textwrap::Wrapper; 235 /// 236 /// let wrapper = Wrapper::new(15) 237 /// .initial_indent("* ") 238 /// .subsequent_indent(" "); 239 /// ``` 240 /// 241 /// [`self.subsequent_indent`]: #structfield.subsequent_indent 242 pub fn subsequent_indent(self, indent: &'a str) -> Wrapper<'a, S> { 243 Wrapper { 244 subsequent_indent: indent, 245 ..self 246 } 247 } 248 249 /// Change [`self.break_words`]. This controls if words longer 250 /// than `self.width` can be broken, or if they will be left 251 /// sticking out into the right margin. 252 /// 253 /// [`self.break_words`]: #structfield.break_words 254 pub fn break_words(self, setting: bool) -> Wrapper<'a, S> { 255 Wrapper { 256 break_words: setting, 257 ..self 258 } 259 } 260 261 /// Fill a line of text at `self.width` characters. 262 /// 263 /// The result is a string with newlines between each line. Use 264 /// the `wrap` method if you need access to the individual lines. 265 /// 266 /// # Complexities 267 /// 268 /// This method simply joins the lines produced by `wrap_iter`. As 269 /// such, it inherits the O(*n*) time and memory complexity where 270 /// *n* is the input string length. 271 /// 272 /// # Examples 273 /// 274 /// ``` 275 /// use textwrap::Wrapper; 276 /// 277 /// let wrapper = Wrapper::new(15); 278 /// assert_eq!(wrapper.fill("Memory safety without garbage collection."), 279 /// "Memory safety\nwithout garbage\ncollection."); 280 /// ``` 281 pub fn fill(&self, s: &str) -> String { 282 // This will avoid reallocation in simple cases (no 283 // indentation, no hyphenation). 284 let mut result = String::with_capacity(s.len()); 285 286 for (i, line) in self.wrap_iter(s).enumerate() { 287 if i > 0 { 288 result.push('\n'); 289 } 290 result.push_str(&line); 291 } 292 293 result 294 } 295 296 /// Wrap a line of text at `self.width` characters. 297 /// 298 /// # Complexities 299 /// 300 /// This method simply collects the lines produced by `wrap_iter`. 301 /// As such, it inherits the O(*n*) overall time and memory 302 /// complexity where *n* is the input string length. 303 /// 304 /// # Examples 305 /// 306 /// ``` 307 /// use textwrap::Wrapper; 308 /// 309 /// let wrap15 = Wrapper::new(15); 310 /// assert_eq!(wrap15.wrap("Concurrency without data races."), 311 /// vec!["Concurrency", 312 /// "without data", 313 /// "races."]); 314 /// 315 /// let wrap20 = Wrapper::new(20); 316 /// assert_eq!(wrap20.wrap("Concurrency without data races."), 317 /// vec!["Concurrency without", 318 /// "data races."]); 319 /// ``` 320 /// 321 /// Notice that newlines in the input are preserved. This means 322 /// that they force a line break, regardless of how long the 323 /// current line is: 324 /// 325 /// ``` 326 /// use textwrap::Wrapper; 327 /// 328 /// let wrapper = Wrapper::new(40); 329 /// assert_eq!(wrapper.wrap("First line.\nSecond line."), 330 /// vec!["First line.", "Second line."]); 331 /// ``` 332 /// 333 pub fn wrap(&self, s: &'a str) -> Vec<Cow<'a, str>> { 334 self.wrap_iter(s).collect::<Vec<_>>() 335 } 336 337 /// Lazily wrap a line of text at `self.width` characters. 338 /// 339 /// The [`WordSplitter`] stored in [`self.splitter`] is used 340 /// whenever when a word is too large to fit on the current line. 341 /// By changing the field, different hyphenation strategies can be 342 /// implemented. 343 /// 344 /// # Complexities 345 /// 346 /// This method returns a [`WrapIter`] iterator which borrows this 347 /// `Wrapper`. The algorithm used has a linear complexity, so 348 /// getting the next line from the iterator will take O(*w*) time, 349 /// where *w* is the wrapping width. Fully processing the iterator 350 /// will take O(*n*) time for an input string of length *n*. 351 /// 352 /// When no indentation is used, each line returned is a slice of 353 /// the input string and the memory overhead is thus constant. 354 /// Otherwise new memory is allocated for each line returned. 355 /// 356 /// # Examples 357 /// 358 /// ``` 359 /// use std::borrow::Cow::Borrowed; 360 /// use textwrap::Wrapper; 361 /// 362 /// let wrap20 = Wrapper::new(20); 363 /// let mut wrap20_iter = wrap20.wrap_iter("Zero-cost abstractions."); 364 /// assert_eq!(wrap20_iter.next(), Some(Borrowed("Zero-cost"))); 365 /// assert_eq!(wrap20_iter.next(), Some(Borrowed("abstractions."))); 366 /// assert_eq!(wrap20_iter.next(), None); 367 /// 368 /// let wrap25 = Wrapper::new(25); 369 /// let mut wrap25_iter = wrap25.wrap_iter("Zero-cost abstractions."); 370 /// assert_eq!(wrap25_iter.next(), Some(Borrowed("Zero-cost abstractions."))); 371 /// assert_eq!(wrap25_iter.next(), None); 372 /// ``` 373 /// 374 /// [`self.splitter`]: #structfield.splitter 375 /// [`WordSplitter`]: trait.WordSplitter.html 376 /// [`WrapIter`]: struct.WrapIter.html 377 pub fn wrap_iter<'w>(&'w self, s: &'a str) -> WrapIter<'w, 'a, S> { 378 WrapIter { 379 wrapper: self, 380 inner: WrapIterImpl::new(self, s), 381 } 382 } 383 384 /// Lazily wrap a line of text at `self.width` characters. 385 /// 386 /// The [`WordSplitter`] stored in [`self.splitter`] is used 387 /// whenever when a word is too large to fit on the current line. 388 /// By changing the field, different hyphenation strategies can be 389 /// implemented. 390 /// 391 /// # Complexities 392 /// 393 /// This method consumes the `Wrapper` and returns a 394 /// [`IntoWrapIter`] iterator. Fully processing the iterator has 395 /// the same O(*n*) time complexity as [`wrap_iter`], where *n* is 396 /// the length of the input string. 397 /// 398 /// # Examples 399 /// 400 /// ``` 401 /// use std::borrow::Cow::Borrowed; 402 /// use textwrap::Wrapper; 403 /// 404 /// let wrap20 = Wrapper::new(20); 405 /// let mut wrap20_iter = wrap20.into_wrap_iter("Zero-cost abstractions."); 406 /// assert_eq!(wrap20_iter.next(), Some(Borrowed("Zero-cost"))); 407 /// assert_eq!(wrap20_iter.next(), Some(Borrowed("abstractions."))); 408 /// assert_eq!(wrap20_iter.next(), None); 409 /// ``` 410 /// 411 /// [`self.splitter`]: #structfield.splitter 412 /// [`WordSplitter`]: trait.WordSplitter.html 413 /// [`IntoWrapIter`]: struct.IntoWrapIter.html 414 /// [`wrap_iter`]: #method.wrap_iter 415 pub fn into_wrap_iter(self, s: &'a str) -> IntoWrapIter<'a, S> { 416 let inner = WrapIterImpl::new(&self, s); 417 418 IntoWrapIter { 419 wrapper: self, 420 inner: inner, 421 } 422 } 423 } 424 425 /// An iterator over the lines of the input string which owns a 426 /// `Wrapper`. An instance of `IntoWrapIter` is typically obtained 427 /// through either [`wrap_iter`] or [`Wrapper::into_wrap_iter`]. 428 /// 429 /// Each call of `.next()` method yields a line wrapped in `Some` if the 430 /// input hasn't been fully processed yet. Otherwise it returns `None`. 431 /// 432 /// [`wrap_iter`]: fn.wrap_iter.html 433 /// [`Wrapper::into_wrap_iter`]: struct.Wrapper.html#method.into_wrap_iter 434 #[derive(Debug)] 435 pub struct IntoWrapIter<'a, S: WordSplitter> { 436 wrapper: Wrapper<'a, S>, 437 inner: WrapIterImpl<'a>, 438 } 439 440 impl<'a, S: WordSplitter> Iterator for IntoWrapIter<'a, S> { 441 type Item = Cow<'a, str>; 442 443 fn next(&mut self) -> Option<Cow<'a, str>> { 444 self.inner.next(&self.wrapper) 445 } 446 } 447 448 /// An iterator over the lines of the input string which borrows a 449 /// `Wrapper`. An instance of `WrapIter` is typically obtained 450 /// through the [`Wrapper::wrap_iter`] method. 451 /// 452 /// Each call of `.next()` method yields a line wrapped in `Some` if the 453 /// input hasn't been fully processed yet. Otherwise it returns `None`. 454 /// 455 /// [`Wrapper::wrap_iter`]: struct.Wrapper.html#method.wrap_iter 456 #[derive(Debug)] 457 pub struct WrapIter<'w, 'a: 'w, S: WordSplitter> { 458 wrapper: &'w Wrapper<'a, S>, 459 inner: WrapIterImpl<'a>, 460 } 461 462 impl<'w, 'a: 'w, S: WordSplitter> Iterator for WrapIter<'w, 'a, S> { 463 type Item = Cow<'a, str>; 464 465 fn next(&mut self) -> Option<Cow<'a, str>> { 466 self.inner.next(self.wrapper) 467 } 468 } 469 470 /// Like `char::is_whitespace`, but non-breaking spaces don't count. 471 #[inline] 472 fn is_whitespace(ch: char) -> bool { 473 ch.is_whitespace() && ch != NBSP 474 } 475 476 /// Common implementation details for `WrapIter` and `IntoWrapIter`. 477 #[derive(Debug)] 478 struct WrapIterImpl<'a> { 479 // String to wrap. 480 source: &'a str, 481 // CharIndices iterator over self.source. 482 char_indices: CharIndices<'a>, 483 // Byte index where the current line starts. 484 start: usize, 485 // Byte index of the last place where the string can be split. 486 split: usize, 487 // Size in bytes of the character at self.source[self.split]. 488 split_len: usize, 489 // Width of self.source[self.start..idx]. 490 line_width: usize, 491 // Width of self.source[self.start..self.split]. 492 line_width_at_split: usize, 493 // Tracking runs of whitespace characters. 494 in_whitespace: bool, 495 // Has iterator finished producing elements? 496 finished: bool, 497 } 498 499 impl<'a> WrapIterImpl<'a> { 500 fn new<S: WordSplitter>(wrapper: &Wrapper<'a, S>, s: &'a str) -> WrapIterImpl<'a> { 501 WrapIterImpl { 502 source: s, 503 char_indices: s.char_indices(), 504 start: 0, 505 split: 0, 506 split_len: 0, 507 line_width: wrapper.initial_indent.width(), 508 line_width_at_split: wrapper.initial_indent.width(), 509 in_whitespace: false, 510 finished: false, 511 } 512 } 513 514 fn create_result_line<S: WordSplitter>(&self, wrapper: &Wrapper<'a, S>) -> Cow<'a, str> { 515 if self.start == 0 { 516 Cow::from(wrapper.initial_indent) 517 } else { 518 Cow::from(wrapper.subsequent_indent) 519 } 520 } 521 522 fn next<S: WordSplitter>(&mut self, wrapper: &Wrapper<'a, S>) -> Option<Cow<'a, str>> { 523 if self.finished { 524 return None; 525 } 526 527 while let Some((idx, ch)) = self.char_indices.next() { 528 if ch == CSI.0 && self.char_indices.next().map(|(_, ch)| ch) == Some(CSI.1) { 529 // We have found the start of an ANSI escape code, 530 // typically used for colored text. We ignore all 531 // characters until we find a "final byte" in the 532 // range 0x40–0x7E. 533 while let Some((_, ch)) = self.char_indices.next() { 534 if ANSI_FINAL_BYTE.contains(&ch) { 535 break; 536 } 537 } 538 // Done with the escape sequence, we continue with 539 // next character in the outer loop. 540 continue; 541 } 542 543 let char_width = ch.width().unwrap_or(0); 544 let char_len = ch.len_utf8(); 545 if ch == '\n' { 546 self.split = idx; 547 self.split_len = char_len; 548 self.line_width_at_split = self.line_width; 549 self.in_whitespace = false; 550 551 // If this is not the final line, return the current line. Otherwise, 552 // we will return the line with its line break after exiting the loop 553 if self.split + self.split_len < self.source.len() { 554 let mut line = self.create_result_line(wrapper); 555 line += &self.source[self.start..self.split]; 556 557 self.start = self.split + self.split_len; 558 self.line_width = wrapper.subsequent_indent.width(); 559 560 return Some(line); 561 } 562 } else if is_whitespace(ch) { 563 // Extend the previous split or create a new one. 564 if self.in_whitespace { 565 self.split_len += char_len; 566 } else { 567 self.split = idx; 568 self.split_len = char_len; 569 } 570 self.line_width_at_split = self.line_width + char_width; 571 self.in_whitespace = true; 572 } else if self.line_width + char_width > wrapper.width { 573 // There is no room for this character on the current 574 // line. Try to split the final word. 575 self.in_whitespace = false; 576 let remaining_text = &self.source[self.split + self.split_len..]; 577 let final_word = match remaining_text.find(is_whitespace) { 578 Some(i) => &remaining_text[..i], 579 None => remaining_text, 580 }; 581 582 let mut hyphen = ""; 583 let splits = wrapper.splitter.split(final_word); 584 for &(head, hyp, _) in splits.iter().rev() { 585 if self.line_width_at_split + head.width() + hyp.width() <= wrapper.width { 586 // We can fit head into the current line. 587 // Advance the split point by the width of the 588 // whitespace and the head length. 589 self.split += self.split_len + head.len(); 590 // The new `split_len` is equal to the stretch 591 // of whitespace following the split. 592 self.split_len = remaining_text[head.len()..] 593 .char_indices() 594 .skip_while(|(_, ch)| is_whitespace(*ch)) 595 .next() 596 .map_or(0, |(idx, _)| idx); 597 self.line_width_at_split += head.width() + hyp.width(); 598 hyphen = hyp; 599 break; 600 } 601 } 602 603 if self.start >= self.split { 604 // The word is too big to fit on a single line. 605 if wrapper.break_words { 606 // Break work at current index. 607 self.split = idx; 608 self.split_len = 0; 609 self.line_width_at_split = self.line_width; 610 } else { 611 // Add smallest split. 612 self.split += self.split_len + splits[0].0.len(); 613 // The new `split_len` is equal to the stretch 614 // of whitespace following the smallest split. 615 self.split_len = remaining_text[splits[0].0.len()..] 616 .char_indices() 617 .skip_while(|(_, ch)| is_whitespace(*ch)) 618 .next() 619 .map_or(0, |(idx, _)| idx); 620 self.line_width_at_split = self.line_width; 621 } 622 } 623 624 if self.start < self.split { 625 let mut line = self.create_result_line(wrapper); 626 line += &self.source[self.start..self.split]; 627 line += hyphen; 628 629 self.start = self.split + self.split_len; 630 self.line_width += wrapper.subsequent_indent.width(); 631 self.line_width -= self.line_width_at_split; 632 self.line_width += char_width; 633 self.line_width_at_split = wrapper.subsequent_indent.width(); 634 635 return Some(line); 636 } 637 } else { 638 self.in_whitespace = false; 639 } 640 self.line_width += char_width; 641 } 642 643 self.finished = true; 644 645 // Add final line. 646 if self.start < self.source.len() { 647 let mut line = self.create_result_line(wrapper); 648 line += &self.source[self.start..]; 649 return Some(line); 650 } 651 652 None 653 } 654 } 655 656 /// Return the current terminal width. If the terminal width cannot be 657 /// determined (typically because the standard output is not connected 658 /// to a terminal), a default width of 80 characters will be used. 659 /// 660 /// # Examples 661 /// 662 /// Create a `Wrapper` for the current terminal with a two column 663 /// margin: 664 /// 665 /// ```no_run 666 /// # #![allow(unused_variables)] 667 /// use textwrap::{Wrapper, NoHyphenation, termwidth}; 668 /// 669 /// let width = termwidth() - 4; // Two columns on each side. 670 /// let wrapper = Wrapper::with_splitter(width, NoHyphenation) 671 /// .initial_indent(" ") 672 /// .subsequent_indent(" "); 673 /// ``` 674 /// 675 /// **Note:** Only available when the `terminal_size` feature is 676 /// enabled. 677 #[cfg(feature = "terminal_size")] 678 pub fn termwidth() -> usize { 679 terminal_size::terminal_size().map_or(80, |(terminal_size::Width(w), _)| w.into()) 680 } 681 682 /// Fill a line of text at `width` characters. 683 /// 684 /// The result is a string with newlines between each line. Use 685 /// [`wrap`] if you need access to the individual lines or 686 /// [`wrap_iter`] for its iterator counterpart. 687 /// 688 /// ``` 689 /// use textwrap::fill; 690 /// 691 /// assert_eq!(fill("Memory safety without garbage collection.", 15), 692 /// "Memory safety\nwithout garbage\ncollection."); 693 /// ``` 694 /// 695 /// This function creates a Wrapper on the fly with default settings. 696 /// If you need to set a language corpus for automatic hyphenation, or 697 /// need to fill many strings, then it is suggested to create a Wrapper 698 /// and call its [`fill` method]. 699 /// 700 /// [`wrap`]: fn.wrap.html 701 /// [`wrap_iter`]: fn.wrap_iter.html 702 /// [`fill` method]: struct.Wrapper.html#method.fill 703 pub fn fill(s: &str, width: usize) -> String { 704 Wrapper::new(width).fill(s) 705 } 706 707 /// Wrap a line of text at `width` characters. 708 /// 709 /// This function creates a Wrapper on the fly with default settings. 710 /// If you need to set a language corpus for automatic hyphenation, or 711 /// need to wrap many strings, then it is suggested to create a Wrapper 712 /// and call its [`wrap` method]. 713 /// 714 /// The result is a vector of strings. Use [`wrap_iter`] if you need an 715 /// iterator version. 716 /// 717 /// # Examples 718 /// 719 /// ``` 720 /// use textwrap::wrap; 721 /// 722 /// assert_eq!(wrap("Concurrency without data races.", 15), 723 /// vec!["Concurrency", 724 /// "without data", 725 /// "races."]); 726 /// 727 /// assert_eq!(wrap("Concurrency without data races.", 20), 728 /// vec!["Concurrency without", 729 /// "data races."]); 730 /// ``` 731 /// 732 /// [`wrap_iter`]: fn.wrap_iter.html 733 /// [`wrap` method]: struct.Wrapper.html#method.wrap 734 pub fn wrap(s: &str, width: usize) -> Vec<Cow<'_, str>> { 735 Wrapper::new(width).wrap(s) 736 } 737 738 /// Lazily wrap a line of text at `width` characters. 739 /// 740 /// This function creates a Wrapper on the fly with default settings. 741 /// It then calls the [`into_wrap_iter`] method. Hence, the return 742 /// value is an [`IntoWrapIter`], not a [`WrapIter`] as the function 743 /// name would otherwise suggest. 744 /// 745 /// If you need to set a language corpus for automatic hyphenation, or 746 /// need to wrap many strings, then it is suggested to create a Wrapper 747 /// and call its [`wrap_iter`] or [`into_wrap_iter`] methods. 748 /// 749 /// # Examples 750 /// 751 /// ``` 752 /// use std::borrow::Cow::Borrowed; 753 /// use textwrap::wrap_iter; 754 /// 755 /// let mut wrap20_iter = wrap_iter("Zero-cost abstractions.", 20); 756 /// assert_eq!(wrap20_iter.next(), Some(Borrowed("Zero-cost"))); 757 /// assert_eq!(wrap20_iter.next(), Some(Borrowed("abstractions."))); 758 /// assert_eq!(wrap20_iter.next(), None); 759 /// 760 /// let mut wrap25_iter = wrap_iter("Zero-cost abstractions.", 25); 761 /// assert_eq!(wrap25_iter.next(), Some(Borrowed("Zero-cost abstractions."))); 762 /// assert_eq!(wrap25_iter.next(), None); 763 /// ``` 764 /// 765 /// [`wrap_iter`]: struct.Wrapper.html#method.wrap_iter 766 /// [`into_wrap_iter`]: struct.Wrapper.html#method.into_wrap_iter 767 /// [`IntoWrapIter`]: struct.IntoWrapIter.html 768 /// [`WrapIter`]: struct.WrapIter.html 769 pub fn wrap_iter(s: &str, width: usize) -> IntoWrapIter<'_, HyphenSplitter> { 770 Wrapper::new(width).into_wrap_iter(s) 771 } 772 773 #[cfg(test)] 774 mod tests { 775 use super::*; 776 #[cfg(feature = "hyphenation")] 777 use hyphenation::{Language, Load, Standard}; 778 779 #[test] 780 fn no_wrap() { 781 assert_eq!(wrap("foo", 10), vec!["foo"]); 782 } 783 784 #[test] 785 fn simple() { 786 assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]); 787 } 788 789 #[test] 790 fn multi_word_on_line() { 791 assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]); 792 } 793 794 #[test] 795 fn long_word() { 796 assert_eq!(wrap("foo", 0), vec!["f", "o", "o"]); 797 } 798 799 #[test] 800 fn long_words() { 801 assert_eq!(wrap("foo bar", 0), vec!["f", "o", "o", "b", "a", "r"]); 802 } 803 804 #[test] 805 fn max_width() { 806 assert_eq!(wrap("foo bar", usize::max_value()), vec!["foo bar"]); 807 } 808 809 #[test] 810 fn leading_whitespace() { 811 assert_eq!(wrap(" foo bar", 6), vec![" foo", "bar"]); 812 } 813 814 #[test] 815 fn trailing_whitespace() { 816 assert_eq!(wrap("foo bar ", 6), vec!["foo", "bar "]); 817 } 818 819 #[test] 820 fn interior_whitespace() { 821 assert_eq!(wrap("foo: bar baz", 10), vec!["foo: bar", "baz"]); 822 } 823 824 #[test] 825 fn extra_whitespace_start_of_line() { 826 // Whitespace is only significant inside a line. After a line 827 // gets too long and is broken, the first word starts in 828 // column zero and is not indented. The line before might end 829 // up with trailing whitespace. 830 assert_eq!(wrap("foo bar", 5), vec!["foo", "bar"]); 831 } 832 833 #[test] 834 fn issue_99() { 835 // We did not reset the in_whitespace flag correctly and did 836 // not handle single-character words after a line break. 837 assert_eq!( 838 wrap("aaabbbccc x yyyzzzwww", 9), 839 vec!["aaabbbccc", "x", "yyyzzzwww"] 840 ); 841 } 842 843 #[test] 844 fn issue_129() { 845 // The dash is an em-dash which takes up four bytes. We used 846 // to panic since we tried to index into the character. 847 assert_eq!(wrap("x – x", 1), vec!["x", "–", "x"]); 848 } 849 850 #[test] 851 fn wide_character_handling() { 852 assert_eq!(wrap("Hello, World!", 15), vec!["Hello, World!"]); 853 assert_eq!( 854 wrap("Hello, World!", 15), 855 vec!["Hello,", "World!"] 856 ); 857 } 858 859 #[test] 860 fn empty_input_not_indented() { 861 let wrapper = Wrapper::new(10).initial_indent("!!!"); 862 assert_eq!(wrapper.fill(""), ""); 863 } 864 865 #[test] 866 fn indent_single_line() { 867 let wrapper = Wrapper::new(10).initial_indent(">>>"); // No trailing space 868 assert_eq!(wrapper.fill("foo"), ">>>foo"); 869 } 870 871 #[test] 872 fn indent_multiple_lines() { 873 let wrapper = Wrapper::new(6).initial_indent("* ").subsequent_indent(" "); 874 assert_eq!(wrapper.wrap("foo bar baz"), vec!["* foo", " bar", " baz"]); 875 } 876 877 #[test] 878 fn indent_break_words() { 879 let wrapper = Wrapper::new(5).initial_indent("* ").subsequent_indent(" "); 880 assert_eq!(wrapper.wrap("foobarbaz"), vec!["* foo", " bar", " baz"]); 881 } 882 883 #[test] 884 fn hyphens() { 885 assert_eq!(wrap("foo-bar", 5), vec!["foo-", "bar"]); 886 } 887 888 #[test] 889 fn trailing_hyphen() { 890 let wrapper = Wrapper::new(5).break_words(false); 891 assert_eq!(wrapper.wrap("foobar-"), vec!["foobar-"]); 892 } 893 894 #[test] 895 fn multiple_hyphens() { 896 assert_eq!(wrap("foo-bar-baz", 5), vec!["foo-", "bar-", "baz"]); 897 } 898 899 #[test] 900 fn hyphens_flag() { 901 let wrapper = Wrapper::new(5).break_words(false); 902 assert_eq!( 903 wrapper.wrap("The --foo-bar flag."), 904 vec!["The", "--foo-", "bar", "flag."] 905 ); 906 } 907 908 #[test] 909 fn repeated_hyphens() { 910 let wrapper = Wrapper::new(4).break_words(false); 911 assert_eq!(wrapper.wrap("foo--bar"), vec!["foo--bar"]); 912 } 913 914 #[test] 915 fn hyphens_alphanumeric() { 916 assert_eq!(wrap("Na2-CH4", 5), vec!["Na2-", "CH4"]); 917 } 918 919 #[test] 920 fn hyphens_non_alphanumeric() { 921 let wrapper = Wrapper::new(5).break_words(false); 922 assert_eq!(wrapper.wrap("foo(-)bar"), vec!["foo(-)bar"]); 923 } 924 925 #[test] 926 fn multiple_splits() { 927 assert_eq!(wrap("foo-bar-baz", 9), vec!["foo-bar-", "baz"]); 928 } 929 930 #[test] 931 fn forced_split() { 932 let wrapper = Wrapper::new(5).break_words(false); 933 assert_eq!(wrapper.wrap("foobar-baz"), vec!["foobar-", "baz"]); 934 } 935 936 #[test] 937 fn multiple_unbroken_words_issue_193() { 938 let wrapper = Wrapper::new(3).break_words(false); 939 assert_eq!( 940 wrapper.wrap("small large tiny"), 941 vec!["small", "large", "tiny"] 942 ); 943 assert_eq!( 944 wrapper.wrap("small large tiny"), 945 vec!["small", "large", "tiny"] 946 ); 947 } 948 949 #[test] 950 fn very_narrow_lines_issue_193() { 951 let wrapper = Wrapper::new(1).break_words(false); 952 assert_eq!(wrapper.wrap("fooo x y"), vec!["fooo", "x", "y"]); 953 assert_eq!(wrapper.wrap("fooo x y"), vec!["fooo", "x", "y"]); 954 } 955 956 #[test] 957 fn no_hyphenation() { 958 let wrapper = Wrapper::with_splitter(8, NoHyphenation); 959 assert_eq!(wrapper.wrap("foo bar-baz"), vec!["foo", "bar-baz"]); 960 } 961 962 #[test] 963 #[cfg(feature = "hyphenation")] 964 fn auto_hyphenation() { 965 let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); 966 let wrapper = Wrapper::new(10); 967 assert_eq!( 968 wrapper.wrap("Internationalization"), 969 vec!["Internatio", "nalization"] 970 ); 971 972 let wrapper = Wrapper::with_splitter(10, dictionary); 973 assert_eq!( 974 wrapper.wrap("Internationalization"), 975 vec!["Interna-", "tionaliza-", "tion"] 976 ); 977 } 978 979 #[test] 980 #[cfg(feature = "hyphenation")] 981 fn auto_hyphenation_issue_158() { 982 let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); 983 let wrapper = Wrapper::new(10); 984 assert_eq!( 985 wrapper.wrap("participation is the key to success"), 986 vec!["participat", "ion is the", "key to", "success"] 987 ); 988 989 let wrapper = Wrapper::with_splitter(10, dictionary); 990 assert_eq!( 991 wrapper.wrap("participation is the key to success"), 992 vec!["participa-", "tion is the", "key to", "success"] 993 ); 994 } 995 996 #[test] 997 #[cfg(feature = "hyphenation")] 998 fn split_len_hyphenation() { 999 // Test that hyphenation takes the width of the wihtespace 1000 // into account. 1001 let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); 1002 let wrapper = Wrapper::with_splitter(15, dictionary); 1003 assert_eq!( 1004 wrapper.wrap("garbage collection"), 1005 vec!["garbage col-", "lection"] 1006 ); 1007 } 1008 1009 #[test] 1010 #[cfg(feature = "hyphenation")] 1011 fn borrowed_lines() { 1012 // Lines that end with an extra hyphen are owned, the final 1013 // line is borrowed. 1014 use std::borrow::Cow::{Borrowed, Owned}; 1015 let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); 1016 let wrapper = Wrapper::with_splitter(10, dictionary); 1017 let lines = wrapper.wrap("Internationalization"); 1018 if let Borrowed(s) = lines[0] { 1019 assert!(false, "should not have been borrowed: {:?}", s); 1020 } 1021 if let Borrowed(s) = lines[1] { 1022 assert!(false, "should not have been borrowed: {:?}", s); 1023 } 1024 if let Owned(ref s) = lines[2] { 1025 assert!(false, "should not have been owned: {:?}", s); 1026 } 1027 } 1028 1029 #[test] 1030 #[cfg(feature = "hyphenation")] 1031 fn auto_hyphenation_with_hyphen() { 1032 let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); 1033 let wrapper = Wrapper::new(8).break_words(false); 1034 assert_eq!(wrapper.wrap("over-caffinated"), vec!["over-", "caffinated"]); 1035 1036 let wrapper = Wrapper::with_splitter(8, dictionary).break_words(false); 1037 assert_eq!( 1038 wrapper.wrap("over-caffinated"), 1039 vec!["over-", "caffi-", "nated"] 1040 ); 1041 } 1042 1043 #[test] 1044 fn break_words() { 1045 assert_eq!(wrap("foobarbaz", 3), vec!["foo", "bar", "baz"]); 1046 } 1047 1048 #[test] 1049 fn break_words_wide_characters() { 1050 assert_eq!(wrap("Hello", 5), vec!["He", "ll", "o"]); 1051 } 1052 1053 #[test] 1054 fn break_words_zero_width() { 1055 assert_eq!(wrap("foobar", 0), vec!["f", "o", "o", "b", "a", "r"]); 1056 } 1057 1058 #[test] 1059 fn break_words_line_breaks() { 1060 assert_eq!(fill("ab\ncdefghijkl", 5), "ab\ncdefg\nhijkl"); 1061 assert_eq!(fill("abcdefgh\nijkl", 5), "abcde\nfgh\nijkl"); 1062 } 1063 1064 #[test] 1065 fn preserve_line_breaks() { 1066 assert_eq!(fill("test\n", 11), "test\n"); 1067 assert_eq!(fill("test\n\na\n\n", 11), "test\n\na\n\n"); 1068 assert_eq!(fill("1 3 5 7\n1 3 5 7", 7), "1 3 5 7\n1 3 5 7"); 1069 } 1070 1071 #[test] 1072 fn wrap_preserve_line_breaks() { 1073 assert_eq!(fill("1 3 5 7\n1 3 5 7", 5), "1 3 5\n7\n1 3 5\n7"); 1074 } 1075 1076 #[test] 1077 fn non_breaking_space() { 1078 let wrapper = Wrapper::new(5).break_words(false); 1079 assert_eq!(wrapper.fill("foo bar baz"), "foo bar baz"); 1080 } 1081 1082 #[test] 1083 fn non_breaking_hyphen() { 1084 let wrapper = Wrapper::new(5).break_words(false); 1085 assert_eq!(wrapper.fill("foo‑bar‑baz"), "foo‑bar‑baz"); 1086 } 1087 1088 #[test] 1089 fn fill_simple() { 1090 assert_eq!(fill("foo bar baz", 10), "foo bar\nbaz"); 1091 } 1092 1093 #[test] 1094 fn fill_colored_text() { 1095 // The words are much longer than 6 bytes, but they remain 1096 // intact after filling the text. 1097 let green_hello = "\u{1b}[0m\u{1b}[32mHello\u{1b}[0m"; 1098 let blue_world = "\u{1b}[0m\u{1b}[34mWorld!\u{1b}[0m"; 1099 assert_eq!( 1100 fill(&(String::from(green_hello) + " " + &blue_world), 6), 1101 String::from(green_hello) + "\n" + &blue_world 1102 ); 1103 } 1104 } 1105