1use crate::{Options, WordSeparator, wrap, wrap_algorithms};
4
5use alloc::string::String;
6use alloc::vec::Vec;
7
8pub 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
52pub(crate) fn fill_slow_path(text: &str, options: Options<'_>) -> String {
56 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
71pub 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 indices.push(line_offset - 1);
144 }
145
146 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 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 let mut text = String::from("foo bar baz");
298 fill_inplace(&mut text, 10);
299 assert_eq!(text, "foo bar \nbaz");
300 }
301}