Bug 1870642 - Fix Collection deleted snackbar that overlaps the toolbar r=android...
[gecko.git] / third_party / rust / textwrap / src / fill.rs
blobfbcaab9e210b61186d4753377571659cd4028dbc
1 //! Functions for filling text.
3 use crate::{wrap, wrap_algorithms, Options, WordSeparator};
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 pub fn fill<'a, Opt>(text: &str, width_or_options: Opt) -> String
37 where
38     Opt: Into<Options<'a>>,
40     let options = width_or_options.into();
42     if text.len() < options.width && !text.contains('\n') && options.initial_indent.is_empty() {
43         String::from(text.trim_end_matches(' '))
44     } else {
45         fill_slow_path(text, options)
46     }
49 /// Slow path for fill.
50 ///
51 /// This is taken when `text` is longer than `options.width`.
52 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     let mut result = String::with_capacity(text.len());
57     let line_ending_str = options.line_ending.as_str();
58     for (i, line) in wrap(text, options).iter().enumerate() {
59         if i > 0 {
60             result.push_str(line_ending_str);
61         }
62         result.push_str(line);
63     }
65     result
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:
104 /// ```
105 /// let mut text = String::from("Hello   World!");
106 /// textwrap::fill_inplace(&mut text, 10);
107 /// assert_eq!(text, "Hello  \nWorld!");
108 /// ```
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.
114 /// # Performance
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 pub fn fill_inplace(text: &mut String, width: usize) {
121     let mut indices = Vec::new();
123     let mut offset = 0;
124     for line in text.split('\n') {
125         let words = WordSeparator::AsciiSpace
126             .find_words(line)
127             .collect::<Vec<_>>();
128         let wrapped_words = wrap_algorithms::wrap_first_fit(&words, &[width as f64]);
130         let mut line_offset = offset;
131         for words in &wrapped_words[..wrapped_words.len() - 1] {
132             let line_len = words
133                 .iter()
134                 .map(|word| word.len() + word.whitespace.len())
135                 .sum::<usize>();
137             line_offset += line_len;
138             // We've advanced past all ' ' characters -- want to move
139             // one ' ' backwards and insert our '\n' there.
140             indices.push(line_offset - 1);
141         }
143         // Advance past entire line, plus the '\n' which was removed
144         // by the split call above.
145         offset += line.len() + 1;
146     }
148     let mut bytes = std::mem::take(text).into_bytes();
149     for idx in indices {
150         bytes[idx] = b'\n';
151     }
152     *text = String::from_utf8(bytes).unwrap();
155 #[cfg(test)]
156 mod tests {
157     use super::*;
158     use crate::WrapAlgorithm;
160     #[test]
161     fn fill_simple() {
162         assert_eq!(fill("foo bar baz", 10), "foo bar\nbaz");
163     }
165     #[test]
166     fn fill_unicode_boundary() {
167         // https://github.com/mgeisler/textwrap/issues/390
168         fill("\u{1b}!Ͽ", 10);
169     }
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     }
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     }
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     }
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     }
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     }
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     }
227     #[test]
228     fn fill_inplace_empty() {
229         let mut text = String::from("");
230         fill_inplace(&mut text, 80);
231         assert_eq!(text, "");
232     }
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     }
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     }
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     }
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     }
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     }
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     }
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     }
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     }
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     }