Coverage Report

Created: 2025-12-31 06:52

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