1use crate::{wrap, wrap_algorithms, Options, WordSeparator};
4
5pub fn fill<'a, Opt>(text: &str, width_or_options: Opt) -> String
37where
38 Opt: Into<Options<'a>>,
39{
40 let options = width_or_options.into();
41
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 }
47}
48
49pub(crate) fn fill_slow_path(text: &str, options: Options<'_>) -> String {
53 let mut result = String::with_capacity(text.len());
56
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 }
64
65 result
66}
67
68pub fn fill_inplace(text: &mut String, width: usize) {
121 let mut indices = Vec::new();
122
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]);
129
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>();
136
137 line_offset += line_len;
138 indices.push(line_offset - 1);
141 }
142
143 offset += line.len() + 1;
146 }
147
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();
153}
154
155#[cfg(test)]
156mod 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 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 let mut text = String::from("foo bar baz");
295 fill_inplace(&mut text, 10);
296 assert_eq!(text, "foo bar \nbaz");
297 }
298}