Revision control
Copy as Markdown
Other Tools
//! 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<usize> 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")
);
}
}