Skip to main content

textwrap/
fill.rs

1//! Functions for filling text.
2
3use crate::{Options, WordSeparator, wrap, wrap_algorithms};
4
5use alloc::string::String;
6use alloc::vec::Vec;
7
8/// Fill a line of text at a given width.
9///
10/// The result is a [`String`], complete with newlines between each
11/// line. Use [`wrap()`] if you need access to the individual lines.
12///
13/// The easiest way to use this function is to pass an integer for
14/// `width_or_options`:
15///
16/// ```
17/// use textwrap::fill;
18///
19/// assert_eq!(
20///     fill("Memory safety without garbage collection.", 15),
21///     "Memory safety\nwithout garbage\ncollection."
22/// );
23/// ```
24///
25/// If you need to customize the wrapping, you can pass an [`Options`]
26/// instead of an `usize`:
27///
28/// ```
29/// use textwrap::{fill, Options};
30///
31/// let options = Options::new(15)
32///     .initial_indent("- ")
33///     .subsequent_indent("  ");
34/// assert_eq!(
35///     fill("Memory safety without garbage collection.", &options),
36///     "- Memory safety\n  without\n  garbage\n  collection."
37/// );
38/// ```
39pub fn fill<'a, Opt>(text: &str, width_or_options: Opt) -> String
40where
41    Opt: Into<Options<'a>>,
42{
43    let options = width_or_options.into();
44
45    if text.len() < options.width && !text.contains('\n') && options.initial_indent.is_empty() {
46        String::from(text.trim_end_matches(' '))
47    } else {
48        fill_slow_path(text, options)
49    }
50}
51
52/// Slow path for fill.
53///
54/// This is taken when `text` is longer than `options.width`.
55pub(crate) fn fill_slow_path(text: &str, options: Options<'_>) -> String {
56    // This will avoid reallocation in simple cases (no
57    // indentation, no hyphenation).
58    let mut result = String::with_capacity(text.len());
59
60    let line_ending_str = options.line_ending.as_str();
61    for (i, line) in wrap(text, options).iter().enumerate() {
62        if i > 0 {
63            result.push_str(line_ending_str);
64        }
65        result.push_str(line);
66    }
67
68    result
69}
70
71/// Fill `text` in-place without reallocating the input string.
72///
73/// This function works by modifying the input string: some `' '`
74/// characters will be replaced by `'\n'` characters. The rest of the
75/// text remains untouched.
76///
77/// Since we can only replace existing whitespace in the input with
78/// `'\n'` (there is no space for `"\r\n"`), we cannot perform
79/// hyphenation, nor can we split words longer than the line width. We
80/// also need to use `AsciiSpace` as the word separator since we need
81/// `' '` characters between words in order to replace some of them
82/// with a `'\n'`. Indentation is also ruled out. In other words,
83/// `fill_inplace(width)` behaves as if you had called [`fill()`] with
84/// these options:
85///
86/// ```
87/// # use textwrap::{core, LineEnding, Options, WordSplitter, WordSeparator, WrapAlgorithm};
88/// # let width = 80;
89/// Options::new(width)
90///     .break_words(false)
91///     .line_ending(LineEnding::LF)
92///     .word_separator(WordSeparator::AsciiSpace)
93///     .wrap_algorithm(WrapAlgorithm::FirstFit)
94///     .word_splitter(WordSplitter::NoHyphenation);
95/// ```
96///
97/// The wrap algorithm is
98/// [`WrapAlgorithm::FirstFit`](crate::WrapAlgorithm::FirstFit) since
99/// this is the fastest algorithm — and the main reason to use
100/// `fill_inplace` is to get the string broken into newlines as fast
101/// as possible.
102///
103/// A final difference is that (unlike [`fill()`]) `fill_inplace` can
104/// leave trailing whitespace on lines. This is because we wrap by
105/// inserting a `'\n'` at the final whitespace in the input string:
106///
107/// ```
108/// let mut text = String::from("Hello   World!");
109/// textwrap::fill_inplace(&mut text, 10);
110/// assert_eq!(text, "Hello  \nWorld!");
111/// ```
112///
113/// If we didn't do this, the word `World!` would end up being
114/// indented. You can avoid this if you make sure that your input text
115/// has no double spaces.
116///
117/// # Performance
118///
119/// In benchmarks, `fill_inplace` is about twice as fast as
120/// [`fill()`]. Please see the [`linear`
121/// benchmark](https://github.com/mgeisler/textwrap/blob/main/benchmarks/linear.rs)
122/// for details.
123pub fn fill_inplace(text: &mut String, width: usize) {
124    let mut indices = Vec::new();
125
126    let mut offset = 0;
127    for line in text.split('\n') {
128        let words = WordSeparator::AsciiSpace
129            .find_words(line)
130            .collect::<Vec<_>>();
131        let wrapped_words = wrap_algorithms::wrap_first_fit(&words, &[width as f64]);
132
133        let mut line_offset = offset;
134        for words in &wrapped_words[..wrapped_words.len() - 1] {
135            let line_len = words
136                .iter()
137                .map(|word| word.len() + word.whitespace.len())
138                .sum::<usize>();
139
140            line_offset += line_len;
141            // We've advanced past all ' ' characters -- want to move
142            // one ' ' backwards and insert our '\n' there.
143            indices.push(line_offset - 1);
144        }
145
146        // Advance past entire line, plus the '\n' which was removed
147        // by the split call above.
148        offset += line.len() + 1;
149    }
150
151    let mut bytes = core::mem::take(text).into_bytes();
152    for idx in indices {
153        bytes[idx] = b'\n';
154    }
155    *text = String::from_utf8(bytes).unwrap();
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::WrapAlgorithm;
162
163    #[test]
164    fn fill_simple() {
165        assert_eq!(fill("foo bar baz", 10), "foo bar\nbaz");
166    }
167
168    #[test]
169    fn fill_unicode_boundary() {
170        // https://github.com/mgeisler/textwrap/issues/390
171        fill("\u{1b}!Ͽ", 10);
172    }
173
174    #[test]
175    fn non_breaking_space() {
176        let options = Options::new(5).break_words(false);
177        assert_eq!(fill("foo bar baz", &options), "foo bar baz");
178    }
179
180    #[test]
181    fn non_breaking_hyphen() {
182        let options = Options::new(5).break_words(false);
183        assert_eq!(fill("foo‑bar‑baz", &options), "foo‑bar‑baz");
184    }
185
186    #[test]
187    fn fill_preserves_line_breaks_trims_whitespace() {
188        assert_eq!(fill("  ", 80), "");
189        assert_eq!(fill("  \n  ", 80), "\n");
190        assert_eq!(fill("  \n \n  \n ", 80), "\n\n\n");
191    }
192
193    #[test]
194    fn preserve_line_breaks() {
195        assert_eq!(fill("", 80), "");
196        assert_eq!(fill("\n", 80), "\n");
197        assert_eq!(fill("\n\n\n", 80), "\n\n\n");
198        assert_eq!(fill("test\n", 80), "test\n");
199        assert_eq!(fill("test\n\na\n\n", 80), "test\n\na\n\n");
200        assert_eq!(
201            fill(
202                "1 3 5 7\n1 3 5 7",
203                Options::new(7).wrap_algorithm(WrapAlgorithm::FirstFit)
204            ),
205            "1 3 5 7\n1 3 5 7"
206        );
207        assert_eq!(
208            fill(
209                "1 3 5 7\n1 3 5 7",
210                Options::new(5).wrap_algorithm(WrapAlgorithm::FirstFit)
211            ),
212            "1 3 5\n7\n1 3 5\n7"
213        );
214    }
215
216    #[test]
217    fn break_words_line_breaks() {
218        assert_eq!(fill("ab\ncdefghijkl", 5), "ab\ncdefg\nhijkl");
219        assert_eq!(fill("abcdefgh\nijkl", 5), "abcde\nfgh\nijkl");
220    }
221
222    #[test]
223    fn break_words_empty_lines() {
224        assert_eq!(
225            fill("foo\nbar", &Options::new(2).break_words(false)),
226            "foo\nbar"
227        );
228    }
229
230    #[test]
231    fn fill_inplace_empty() {
232        let mut text = String::from("");
233        fill_inplace(&mut text, 80);
234        assert_eq!(text, "");
235    }
236
237    #[test]
238    fn fill_inplace_simple() {
239        let mut text = String::from("foo bar baz");
240        fill_inplace(&mut text, 10);
241        assert_eq!(text, "foo bar\nbaz");
242    }
243
244    #[test]
245    fn fill_inplace_multiple_lines() {
246        let mut text = String::from("Some text to wrap over multiple lines");
247        fill_inplace(&mut text, 12);
248        assert_eq!(text, "Some text to\nwrap over\nmultiple\nlines");
249    }
250
251    #[test]
252    fn fill_inplace_long_word() {
253        let mut text = String::from("Internationalization is hard");
254        fill_inplace(&mut text, 10);
255        assert_eq!(text, "Internationalization\nis hard");
256    }
257
258    #[test]
259    fn fill_inplace_no_hyphen_splitting() {
260        let mut text = String::from("A well-chosen example");
261        fill_inplace(&mut text, 10);
262        assert_eq!(text, "A\nwell-chosen\nexample");
263    }
264
265    #[test]
266    fn fill_inplace_newlines() {
267        let mut text = String::from("foo bar\n\nbaz\n\n\n");
268        fill_inplace(&mut text, 10);
269        assert_eq!(text, "foo bar\n\nbaz\n\n\n");
270    }
271
272    #[test]
273    fn fill_inplace_newlines_reset_line_width() {
274        let mut text = String::from("1 3 5\n1 3 5 7 9\n1 3 5 7 9 1 3");
275        fill_inplace(&mut text, 10);
276        assert_eq!(text, "1 3 5\n1 3 5 7 9\n1 3 5 7 9\n1 3");
277    }
278
279    #[test]
280    fn fill_inplace_leading_whitespace() {
281        let mut text = String::from("  foo bar baz");
282        fill_inplace(&mut text, 10);
283        assert_eq!(text, "  foo bar\nbaz");
284    }
285
286    #[test]
287    fn fill_inplace_trailing_whitespace() {
288        let mut text = String::from("foo bar baz  ");
289        fill_inplace(&mut text, 10);
290        assert_eq!(text, "foo bar\nbaz  ");
291    }
292
293    #[test]
294    fn fill_inplace_interior_whitespace() {
295        // To avoid an unwanted indentation of "baz", it is important
296        // to replace the final ' ' with '\n'.
297        let mut text = String::from("foo  bar    baz");
298        fill_inplace(&mut text, 10);
299        assert_eq!(text, "foo  bar   \nbaz");
300    }
301}