//! Options for wrapping text. use crate::{LineEnding, WordSeparator, WordSplitter, WrapAlgorithm}; /// Holds configuration options for wrapping and filling text. #[non_exhaustive] #[derive(Debug, Clone)] pub struct Options<'a> { /// The width in columns at which the text will be wrapped. pub width: usize, /// Line ending used for breaking lines. pub line_ending: LineEnding, /// Indentation used for the first line of output. See the /// [`Options::initial_indent`] method. pub initial_indent: &'a str, /// Indentation used for subsequent lines of output. See the /// [`Options::subsequent_indent`] method. pub subsequent_indent: &'a str, /// Allow long words to be broken if they cannot fit on a line. /// When set to `false`, some lines may be longer than /// `self.width`. See the [`Options::break_words`] method. pub break_words: bool, /// Wrapping algorithm to use, see the implementations of the /// [`WrapAlgorithm`] trait for details. pub wrap_algorithm: WrapAlgorithm, /// The line breaking algorithm to use, see the [`WordSeparator`] /// trait for an overview and possible implementations. pub word_separator: WordSeparator, /// The method for splitting words. This can be used to prohibit /// splitting words on hyphens, or it can be used to implement /// language-aware machine hyphenation. pub word_splitter: WordSplitter, } impl<'a> From<&'a Options<'a>> for Options<'a> { fn from(options: &'a Options<'a>) -> Self { Self { width: options.width, line_ending: options.line_ending, initial_indent: options.initial_indent, subsequent_indent: options.subsequent_indent, break_words: options.break_words, word_separator: options.word_separator, wrap_algorithm: options.wrap_algorithm, word_splitter: options.word_splitter.clone(), } } } impl<'a> From for Options<'a> { fn from(width: usize) -> Self { Options::new(width) } } impl<'a> Options<'a> { /// Creates a new [`Options`] with the specified width. /// /// The other fields are given default values as follows: /// /// ``` /// # use textwrap::{LineEnding, Options, WordSplitter, WordSeparator, WrapAlgorithm}; /// # let width = 80; /// let options = Options::new(width); /// assert_eq!(options.line_ending, LineEnding::LF); /// assert_eq!(options.initial_indent, ""); /// assert_eq!(options.subsequent_indent, ""); /// assert_eq!(options.break_words, true); /// /// #[cfg(feature = "unicode-linebreak")] /// assert_eq!(options.word_separator, WordSeparator::UnicodeBreakProperties); /// #[cfg(not(feature = "unicode-linebreak"))] /// assert_eq!(options.word_separator, WordSeparator::AsciiSpace); /// /// #[cfg(feature = "smawk")] /// assert_eq!(options.wrap_algorithm, WrapAlgorithm::new_optimal_fit()); /// #[cfg(not(feature = "smawk"))] /// assert_eq!(options.wrap_algorithm, WrapAlgorithm::FirstFit); /// /// assert_eq!(options.word_splitter, WordSplitter::HyphenSplitter); /// ``` /// /// Note that the default word separator and wrap algorithms /// changes based on the available Cargo features. The best /// available algorithms are used by default. pub const fn new(width: usize) -> Self { Options { width, line_ending: LineEnding::LF, initial_indent: "", subsequent_indent: "", break_words: true, word_separator: WordSeparator::new(), wrap_algorithm: WrapAlgorithm::new(), word_splitter: WordSplitter::HyphenSplitter, } } /// Change [`self.line_ending`]. This specifies which of the /// supported line endings should be used to break the lines of the /// input text. /// /// # Examples /// /// ``` /// use textwrap::{refill, LineEnding, Options}; /// /// let options = Options::new(15).line_ending(LineEnding::CRLF); /// assert_eq!(refill("This is a little example.", options), /// "This is a\r\nlittle example."); /// ``` /// /// [`self.line_ending`]: #structfield.line_ending pub fn line_ending(self, line_ending: LineEnding) -> Self { Options { line_ending, ..self } } /// Set [`self.width`] to the given value. /// /// [`self.width`]: #structfield.width pub fn width(self, width: usize) -> Self { Options { width, ..self } } /// Change [`self.initial_indent`]. The initial indentation is /// used on the very first line of output. /// /// # Examples /// /// Classic paragraph indentation can be achieved by specifying an /// initial indentation and wrapping each paragraph by itself: /// /// ``` /// use textwrap::{wrap, Options}; /// /// let options = Options::new(16).initial_indent(" "); /// assert_eq!(wrap("This is a little example.", options), /// vec![" This is a", /// "little example."]); /// ``` /// /// [`self.initial_indent`]: #structfield.initial_indent pub fn initial_indent(self, initial_indent: &'a str) -> Self { Options { initial_indent, ..self } } /// Change [`self.subsequent_indent`]. The subsequent indentation /// is used on lines following the first line of output. /// /// # Examples /// /// Combining initial and subsequent indentation lets you format a /// single paragraph as a bullet list: /// /// ``` /// use textwrap::{wrap, Options}; /// /// let options = Options::new(12) /// .initial_indent("* ") /// .subsequent_indent(" "); /// #[cfg(feature = "smawk")] /// assert_eq!(wrap("This is a little example.", options), /// vec!["* This is", /// " a little", /// " example."]); /// /// // Without the `smawk` feature, the wrapping is a little different: /// #[cfg(not(feature = "smawk"))] /// assert_eq!(wrap("This is a little example.", options), /// vec!["* This is a", /// " little", /// " example."]); /// ``` /// /// [`self.subsequent_indent`]: #structfield.subsequent_indent pub fn subsequent_indent(self, subsequent_indent: &'a str) -> Self { Options { subsequent_indent, ..self } } /// Change [`self.break_words`]. This controls if words longer /// than `self.width` can be broken, or if they will be left /// sticking out into the right margin. /// /// See [`Options::word_splitter`] instead if you want to control /// hyphenation. /// /// # Examples /// /// ``` /// use textwrap::{wrap, Options}; /// /// let options = Options::new(4).break_words(true); /// assert_eq!(wrap("This is a little example.", options), /// vec!["This", /// "is a", /// "litt", /// "le", /// "exam", /// "ple."]); /// ``` /// /// [`self.break_words`]: #structfield.break_words pub fn break_words(self, break_words: bool) -> Self { Options { break_words, ..self } } /// Change [`self.word_separator`]. /// /// See the [`WordSeparator`] trait for details on the choices. /// /// [`self.word_separator`]: #structfield.word_separator pub fn word_separator(self, word_separator: WordSeparator) -> Options<'a> { Options { word_separator, ..self } } /// Change [`self.wrap_algorithm`]. /// /// See the [`WrapAlgorithm`] trait for details on the choices. /// /// [`self.wrap_algorithm`]: #structfield.wrap_algorithm pub fn wrap_algorithm(self, wrap_algorithm: WrapAlgorithm) -> Options<'a> { Options { wrap_algorithm, ..self } } /// Change [`self.word_splitter`]. The [`WordSplitter`] is used to /// fit part of a word into the current line when wrapping text. /// /// See [`Options::break_words`] instead if you want to control the /// handling of words longer than the line width. /// /// # Examples /// /// ``` /// use textwrap::{wrap, Options, WordSplitter}; /// /// // The default is WordSplitter::HyphenSplitter. /// let options = Options::new(5); /// assert_eq!(wrap("foo-bar-baz", &options), /// vec!["foo-", "bar-", "baz"]); /// /// // The word is now so long that break_words kick in: /// let options = Options::new(5) /// .word_splitter(WordSplitter::NoHyphenation); /// assert_eq!(wrap("foo-bar-baz", &options), /// vec!["foo-b", "ar-ba", "z"]); /// /// // If you want to breaks at all, disable both: /// let options = Options::new(5) /// .break_words(false) /// .word_splitter(WordSplitter::NoHyphenation); /// assert_eq!(wrap("foo-bar-baz", &options), /// vec!["foo-bar-baz"]); /// ``` /// /// [`self.word_splitter`]: #structfield.word_splitter pub fn word_splitter(self, word_splitter: WordSplitter) -> Options<'a> { Options { word_splitter, ..self } } } #[cfg(test)] mod tests { use super::*; #[test] fn options_agree_with_usize() { let opt_usize = Options::from(42_usize); let opt_options = Options::new(42); assert_eq!(opt_usize.width, opt_options.width); assert_eq!(opt_usize.initial_indent, opt_options.initial_indent); assert_eq!(opt_usize.subsequent_indent, opt_options.subsequent_indent); assert_eq!(opt_usize.break_words, opt_options.break_words); assert_eq!( opt_usize.word_splitter.split_points("hello-world"), opt_options.word_splitter.split_points("hello-world") ); } }