Skip to main content

textwrap/
options.rs

1//! Options for wrapping text.
2
3use crate::{LineEnding, WordSeparator, WordSplitter, WrapAlgorithm};
4
5/// Holds configuration options for wrapping and filling text.
6#[non_exhaustive]
7#[derive(Debug, Clone)]
8pub struct Options<'a> {
9    /// The width in columns at which the text will be wrapped.
10    pub width: usize,
11    /// Line ending used for breaking lines.
12    pub line_ending: LineEnding,
13    /// Indentation used for the first line of output. See the
14    /// [`Options::initial_indent`] method.
15    pub initial_indent: &'a str,
16    /// Indentation used for subsequent lines of output. See the
17    /// [`Options::subsequent_indent`] method.
18    pub subsequent_indent: &'a str,
19    /// Allow long words to be broken if they cannot fit on a line.
20    /// When set to `false`, some lines may be longer than
21    /// `self.width`. See the [`Options::break_words`] method.
22    pub break_words: bool,
23    /// Wrapping algorithm to use, see the [`WrapAlgorithm`] enum for
24    /// details.
25    pub wrap_algorithm: WrapAlgorithm,
26    /// The line breaking algorithm to use, see the [`WordSeparator`]
27    /// enum for an overview and possible implementations.
28    pub word_separator: WordSeparator,
29    /// The method for splitting words. This can be used to prohibit
30    /// splitting words on hyphens, or it can be used to implement
31    /// language-aware machine hyphenation.
32    pub word_splitter: WordSplitter,
33    /// Allow trailing spaces to be preserved at the end of the line.
34    pub preserve_trailing_space: bool,
35}
36
37impl<'a> From<&'a Options<'a>> for Options<'a> {
38    fn from(options: &'a Options<'a>) -> Self {
39        Self {
40            width: options.width,
41            line_ending: options.line_ending,
42            initial_indent: options.initial_indent,
43            subsequent_indent: options.subsequent_indent,
44            break_words: options.break_words,
45            word_separator: options.word_separator,
46            wrap_algorithm: options.wrap_algorithm,
47            word_splitter: options.word_splitter.clone(),
48            preserve_trailing_space: options.preserve_trailing_space,
49        }
50    }
51}
52
53impl From<usize> for Options<'_> {
54    fn from(width: usize) -> Self {
55        Options::new(width)
56    }
57}
58
59impl<'a> Options<'a> {
60    /// Creates a new [`Options`] with the specified width.
61    ///
62    /// The other fields are given default values as follows:
63    ///
64    /// ```
65    /// # use textwrap::{LineEnding, Options, WordSplitter, WordSeparator, WrapAlgorithm};
66    /// # let width = 80;
67    /// let options = Options::new(width);
68    /// assert_eq!(options.line_ending, LineEnding::LF);
69    /// assert_eq!(options.initial_indent, "");
70    /// assert_eq!(options.subsequent_indent, "");
71    /// assert_eq!(options.break_words, true);
72    /// assert_eq!(options.preserve_trailing_space, false);
73    ///
74    /// #[cfg(feature = "unicode-linebreak")]
75    /// assert_eq!(options.word_separator, WordSeparator::UnicodeBreakProperties);
76    /// #[cfg(not(feature = "unicode-linebreak"))]
77    /// assert_eq!(options.word_separator, WordSeparator::AsciiSpace);
78    ///
79    /// #[cfg(feature = "smawk")]
80    /// assert_eq!(options.wrap_algorithm, WrapAlgorithm::new_optimal_fit());
81    /// #[cfg(not(feature = "smawk"))]
82    /// assert_eq!(options.wrap_algorithm, WrapAlgorithm::FirstFit);
83    ///
84    /// assert_eq!(options.word_splitter, WordSplitter::HyphenSplitter);
85    /// ```
86    ///
87    /// Note that the default word separator and wrap algorithms
88    /// change based on the available Cargo features. The best
89    /// available algorithms are used by default.
90    pub const fn new(width: usize) -> Self {
91        Options {
92            width,
93            line_ending: LineEnding::LF,
94            initial_indent: "",
95            subsequent_indent: "",
96            break_words: true,
97            word_separator: WordSeparator::new(),
98            wrap_algorithm: WrapAlgorithm::new(),
99            word_splitter: WordSplitter::HyphenSplitter,
100            preserve_trailing_space: false,
101        }
102    }
103
104    /// Change [`self.line_ending`]. This specifies which of the
105    /// supported line endings should be used to break the lines of the
106    /// input text.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use textwrap::{refill, LineEnding, Options};
112    ///
113    /// let options = Options::new(15).line_ending(LineEnding::CRLF);
114    /// assert_eq!(refill("This is a little example.", options),
115    ///            "This is a\r\nlittle example.");
116    /// ```
117    ///
118    /// [`self.line_ending`]: #structfield.line_ending
119    pub fn line_ending(self, line_ending: LineEnding) -> Self {
120        Options {
121            line_ending,
122            ..self
123        }
124    }
125
126    /// Set [`self.width`] to the given value.
127    ///
128    /// [`self.width`]: #structfield.width
129    pub fn width(self, width: usize) -> Self {
130        Options { width, ..self }
131    }
132
133    /// Change [`self.initial_indent`]. The initial indentation is
134    /// used on the very first line of output.
135    ///
136    /// # Examples
137    ///
138    /// Classic paragraph indentation can be achieved by specifying an
139    /// initial indentation and wrapping each paragraph by itself:
140    ///
141    /// ```
142    /// use textwrap::{wrap, Options};
143    ///
144    /// let options = Options::new(16).initial_indent("    ");
145    /// assert_eq!(wrap("This is a little example.", options),
146    ///            vec!["    This is a",
147    ///                 "little example."]);
148    /// ```
149    ///
150    /// [`self.initial_indent`]: #structfield.initial_indent
151    pub fn initial_indent(self, initial_indent: &'a str) -> Self {
152        Options {
153            initial_indent,
154            ..self
155        }
156    }
157
158    /// Change [`self.subsequent_indent`]. The subsequent indentation
159    /// is used on lines following the first line of output.
160    ///
161    /// # Examples
162    ///
163    /// Combining initial and subsequent indentation lets you format a
164    /// single paragraph as a bullet list:
165    ///
166    /// ```
167    /// use textwrap::{wrap, Options};
168    ///
169    /// let options = Options::new(12)
170    ///     .initial_indent("* ")
171    ///     .subsequent_indent("  ");
172    /// #[cfg(feature = "smawk")]
173    /// assert_eq!(wrap("This is a little example.", options),
174    ///            vec!["* This is",
175    ///                 "  a little",
176    ///                 "  example."]);
177    ///
178    /// // Without the `smawk` feature, the wrapping is a little different:
179    /// #[cfg(not(feature = "smawk"))]
180    /// assert_eq!(wrap("This is a little example.", options),
181    ///            vec!["* This is a",
182    ///                 "  little",
183    ///                 "  example."]);
184    /// ```
185    ///
186    /// [`self.subsequent_indent`]: #structfield.subsequent_indent
187    pub fn subsequent_indent(self, subsequent_indent: &'a str) -> Self {
188        Options {
189            subsequent_indent,
190            ..self
191        }
192    }
193
194    /// Change [`self.break_words`]. This controls if words longer
195    /// than `self.width` can be broken, or if they will be left
196    /// protruding into the right margin.
197    ///
198    /// See [`Options::word_splitter`] instead if you want to control
199    /// hyphenation.
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// use textwrap::{wrap, Options};
205    ///
206    /// let options = Options::new(4).break_words(true);
207    /// assert_eq!(wrap("This is a little example.", options),
208    ///            vec!["This",
209    ///                 "is a",
210    ///                 "litt",
211    ///                 "le",
212    ///                 "exam",
213    ///                 "ple."]);
214    /// ```
215    ///
216    /// [`self.break_words`]: #structfield.break_words
217    pub fn break_words(self, break_words: bool) -> Self {
218        Options {
219            break_words,
220            ..self
221        }
222    }
223
224    /// Change [`self.word_separator`].
225    ///
226    /// See the [`WordSeparator`] enum for details on the choices.
227    ///
228    /// [`self.word_separator`]: #structfield.word_separator
229    pub fn word_separator(self, word_separator: WordSeparator) -> Options<'a> {
230        Options {
231            word_separator,
232            ..self
233        }
234    }
235
236    /// Change [`self.wrap_algorithm`].
237    ///
238    /// See the [`WrapAlgorithm`] enum for details on the choices.
239    ///
240    /// [`self.wrap_algorithm`]: #structfield.wrap_algorithm
241    pub fn wrap_algorithm(self, wrap_algorithm: WrapAlgorithm) -> Options<'a> {
242        Options {
243            wrap_algorithm,
244            ..self
245        }
246    }
247
248    /// Change [`self.word_splitter`]. The [`WordSplitter`] is used to
249    /// fit part of a word into the current line when wrapping text.
250    ///
251    /// See [`Options::break_words`] instead if you want to control the
252    /// handling of words longer than the line width.
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use textwrap::{wrap, Options, WordSplitter};
258    ///
259    /// // The default is WordSplitter::HyphenSplitter.
260    /// let options = Options::new(5);
261    /// assert_eq!(wrap("foo-bar-baz", &options),
262    ///            vec!["foo-", "bar-", "baz"]);
263    ///
264    /// // The word is now so long that break_words kicks in:
265    /// let options = Options::new(5)
266    ///     .word_splitter(WordSplitter::NoHyphenation);
267    /// assert_eq!(wrap("foo-bar-baz", &options),
268    ///            vec!["foo-b", "ar-ba", "z"]);
269    ///
270    /// // If you want no breaks at all, disable both:
271    /// let options = Options::new(5)
272    ///     .break_words(false)
273    ///     .word_splitter(WordSplitter::NoHyphenation);
274    /// assert_eq!(wrap("foo-bar-baz", &options),
275    ///            vec!["foo-bar-baz"]);
276    /// ```
277    ///
278    /// [`self.word_splitter`]: #structfield.word_splitter
279    pub fn word_splitter(self, word_splitter: WordSplitter) -> Options<'a> {
280        Options {
281            word_splitter,
282            ..self
283        }
284    }
285
286    /// Change [`self.preserve_trailing_space`]. This controls if the
287    /// trailing spaces at the end of a line are preserved or trimmed.
288    ///
289    /// [`self.preserve_trailing_space`]: #structfield.preserve_trailing_space
290    pub fn preserve_trailing_space(self, preserve_trailing_space: bool) -> Options<'a> {
291        Options {
292            preserve_trailing_space,
293            ..self
294        }
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301
302    #[test]
303    fn options_agree_with_usize() {
304        let opt_usize = Options::from(42_usize);
305        let opt_options = Options::new(42);
306
307        assert_eq!(opt_usize.width, opt_options.width);
308        assert_eq!(opt_usize.initial_indent, opt_options.initial_indent);
309        assert_eq!(opt_usize.subsequent_indent, opt_options.subsequent_indent);
310        assert_eq!(opt_usize.break_words, opt_options.break_words);
311        assert_eq!(
312            opt_usize.word_splitter.split_points("hello-world"),
313            opt_options.word_splitter.split_points("hello-world")
314        );
315    }
316}