1use std::marker::PhantomData;
5
6use papergrid::{
7 records::{empty::EmptyRecords, Records, RecordsMut},
8 util::string_width_multiline,
9 width::CfgWidthFunction,
10 Entity,
11};
12
13use crate::{
14 measurment::Measurment,
15 peaker::{Peaker, PriorityNone},
16 CellOption, Table, TableOption, Width,
17};
18
19use super::{
20 get_table_widths, get_table_widths_with_total,
21 truncate::{decrease_widths, get_decrease_cell_list},
22};
23
24#[derive(Debug, Clone)]
43pub struct Wrap<W = usize, P = PriorityNone> {
44 width: W,
45 keep_words: bool,
46 _priority: PhantomData<P>,
47}
48
49impl<W> Wrap<W>
50where
51 W: Measurment<Width>,
52{
53 pub fn new(width: W) -> Self {
55 Self {
56 width,
57 keep_words: false,
58 _priority: PhantomData::default(),
59 }
60 }
61}
62
63impl<W, P> Wrap<W, P> {
64 pub fn priority<PP>(self) -> Wrap<W, PP> {
77 Wrap {
78 width: self.width,
79 keep_words: self.keep_words,
80 _priority: PhantomData::default(),
81 }
82 }
83
84 pub fn keep_words(mut self) -> Self {
89 self.keep_words = true;
90 self
91 }
92}
93
94impl<W, P, R> CellOption<R> for Wrap<W, P>
95where
96 W: Measurment<Width>,
97 R: Records + RecordsMut<String>,
98{
99 fn change_cell(&mut self, table: &mut Table<R>, entity: Entity) {
100 let width_ctrl = CfgWidthFunction::from_cfg(table.get_config());
101 let width = self.width.measure(table.get_records(), table.get_config());
102
103 let (count_rows, count_cols) = table.shape();
104 for pos in entity.iter(count_rows, count_cols) {
105 let records = table.get_records();
106 let cell_width = records.get_width(pos, &width_ctrl);
107 if cell_width <= width {
108 continue;
109 }
110
111 let text = records.get_text(pos);
112 let text = papergrid::util::replace_tab(text, table.get_config().get_tab_width());
116 let wrapped = wrap_text(&text, width, self.keep_words);
117
118 debug_assert!(
119 width >= string_width_multiline(&wrapped),
120 "width={:?}\n\n content={:?}\n\n wrap={:?}\n",
121 width,
122 text,
123 wrapped
124 );
125
126 let records = table.get_records_mut();
127 records.set(pos, wrapped, &width_ctrl);
128 }
129
130 table.destroy_width_cache();
131 }
132}
133
134impl<W, P, R> TableOption<R> for Wrap<W, P>
135where
136 W: Measurment<Width>,
137 P: Peaker,
138 R: Records + RecordsMut<String>,
139{
140 fn change(&mut self, table: &mut Table<R>) {
141 if table.is_empty() {
142 return;
143 }
144
145 let width = self.width.measure(table.get_records(), table.get_config());
146 let (widths, total_width) =
147 get_table_widths_with_total(table.get_records(), table.get_config());
148 if width >= total_width {
149 return;
150 }
151
152 let priority = P::create();
153 let keep_words = self.keep_words;
154 wrap_total_width(table, widths, total_width, width, keep_words, priority);
155 }
156}
157
158fn wrap_total_width<R, P>(
159 table: &mut Table<R>,
160 mut widths: Vec<usize>,
161 total_width: usize,
162 width: usize,
163 keep_words: bool,
164 priority: P,
165) where
166 P: Peaker,
167 R: Records + RecordsMut<String>,
168{
169 let (count_rows, count_cols) = table.shape();
170 let cfg = table.get_config();
171 let min_widths = get_table_widths(EmptyRecords::new(count_rows, count_cols), cfg);
172
173 decrease_widths(&mut widths, &min_widths, total_width, width, priority);
174
175 let points = get_decrease_cell_list(cfg, &widths, &min_widths, (count_rows, count_cols));
176 let mut wrap = Wrap::new(0);
177 wrap.keep_words = keep_words;
178 for ((row, col), width) in points {
179 wrap.width = width;
180 wrap.change_cell(table, (row, col).into());
181 }
182
183 table.destroy_height_cache();
184 table.destroy_width_cache();
185 table.cache_width(widths);
186}
187
188#[cfg(not(feature = "color"))]
189pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String {
190 if width == 0 {
191 return String::new();
192 }
193
194 if keep_words {
195 split_keeping_words(text, width, "\n")
196 } else {
197 chunks(text, width).join("\n")
198 }
199}
200
201#[cfg(feature = "color")]
202pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String {
203 use papergrid::util::strip_osc;
204
205 if width == 0 {
206 return String::new();
207 }
208
209 let (text, url): (String, Option<String>) = strip_osc(text);
210 let (prefix, suffix) = build_link_prefix_suffix(url);
211
212 if keep_words {
213 split_keeping_words(&text, width, &prefix, &suffix)
214 } else {
215 chunks(&text, width, &prefix, &suffix).join("\n")
216 }
217}
218
219#[cfg(feature = "color")]
220fn build_link_prefix_suffix(url: Option<String>) -> (String, String) {
221 match url {
222 Some(url) => {
223 let osc8 = "\x1b]8;;";
225 let st = "\x1b\\";
226
227 (format!("{}{}{}", osc8, url, st), format!("{}{}", osc8, st))
228 }
229 None => ("".to_string(), "".to_string()),
230 }
231}
232
233#[cfg(not(feature = "color"))]
234fn chunks(s: &str, width: usize) -> Vec<String> {
235 if width == 0 {
236 return Vec::new();
237 }
238
239 const REPLACEMENT: char = '\u{FFFD}';
240
241 let mut buf = String::with_capacity(width);
242 let mut list = Vec::new();
243 let mut i = 0;
244 for c in s.chars() {
245 let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
246 if i + c_width > width {
247 let count_unknowns = width - i;
248 buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns));
249 i += count_unknowns;
250 } else {
251 buf.push(c);
252 i += c_width;
253 }
254
255 if i == width {
256 list.push(buf);
257 buf = String::with_capacity(width);
258 i = 0;
259 }
260 }
261
262 if !buf.is_empty() {
263 list.push(buf);
264 }
265
266 list
267}
268
269#[cfg(feature = "color")]
270fn chunks(s: &str, width: usize, prefix: &str, suffix: &str) -> Vec<String> {
271 use std::fmt::Write;
272
273 if width == 0 {
274 return Vec::new();
275 }
276
277 let mut list = Vec::new();
278 let mut line = String::with_capacity(width);
279 let mut line_width = 0;
280
281 for b in ansi_str::get_blocks(s) {
282 if b.text().is_empty() {
283 continue;
284 }
285
286 line.push_str(prefix);
287 let _ = write!(&mut line, "{}", b.start());
288
289 let mut part = b.text();
290
291 while !part.is_empty() {
292 let available_space = width - line_width;
293
294 let part_width = unicode_width::UnicodeWidthStr::width(part);
295 if part_width <= available_space {
296 line.push_str(part);
297 line_width += part_width;
298
299 if available_space == 0 {
300 let _ = write!(&mut line, "{}", b.end());
301 line.push_str(suffix);
302 list.push(line);
303 line = String::with_capacity(width);
304 line.push_str(prefix);
305 line_width = 0;
306 let _ = write!(&mut line, "{}", b.start());
307 }
308
309 break;
310 }
311
312 let (lhs, rhs, (unknowns, split_char)) = split_string_at(part, available_space);
313
314 part = &rhs[split_char..];
315
316 line.push_str(lhs);
317 line_width += unicode_width::UnicodeWidthStr::width(lhs);
318
319 const REPLACEMENT: char = '\u{FFFD}';
320 line.extend(std::iter::repeat(REPLACEMENT).take(unknowns));
321 line_width += unknowns;
322
323 if line_width == width {
324 let _ = write!(&mut line, "{}", b.end());
325 line.push_str(suffix);
326 list.push(line);
327 line = String::with_capacity(width);
328 line.push_str(prefix);
329 line_width = 0;
330 let _ = write!(&mut line, "{}", b.start());
331 }
332 }
333
334 if line_width > 0 {
335 let _ = write!(&mut line, "{}", b.end());
336 }
337 }
338
339 if line_width > 0 {
340 line.push_str(suffix);
341 list.push(line);
342 }
343
344 list
345}
346
347#[cfg(not(feature = "color"))]
348fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
349 const REPLACEMENT: char = '\u{FFFD}';
350
351 let mut lines = Vec::new();
352 let mut line = String::with_capacity(width);
353 let mut line_width = 0;
354
355 let mut is_first_word = true;
356
357 for word in s.split(' ') {
358 if !is_first_word {
359 let line_has_space = line_width < width;
360 if line_has_space {
361 line.push(' ');
362 line_width += 1;
363 is_first_word = false;
364 }
365 }
366
367 if is_first_word {
368 is_first_word = false;
369 }
370
371 let word_width = unicode_width::UnicodeWidthStr::width(word);
372
373 let line_has_space = line_width + word_width <= width;
374 if line_has_space {
375 line.push_str(word);
376 line_width += word_width;
377 continue;
378 }
379
380 if word_width <= width {
381 line.extend(std::iter::repeat(' ').take(width - line_width));
384 lines.push(line);
385
386 line = String::with_capacity(width);
387 line_width = 0;
388
389 line.push_str(word);
390 line_width += word_width;
391 is_first_word = false;
392 } else {
393 let mut word_part = word;
396 while !word_part.is_empty() {
397 let available_space = width - line_width;
398 let (lhs, rhs, (unknowns, split_char)) =
399 split_string_at(word_part, available_space);
400
401 word_part = &rhs[split_char..];
402 line_width += unicode_width::UnicodeWidthStr::width(lhs) + unknowns;
403
404 line.push_str(lhs);
405 line.extend(std::iter::repeat(REPLACEMENT).take(unknowns));
406
407 if line_width == width {
408 lines.push(line);
409 line = String::with_capacity(width);
410 line_width = 0;
411 is_first_word = true;
412 }
413 }
414 }
415 }
416
417 if line_width > 0 {
418 line.extend(std::iter::repeat(' ').take(width - line_width));
419 lines.push(line);
420 }
421
422 lines.join(sep)
423}
424
425#[cfg(feature = "color")]
426fn split_keeping_words(text: &str, width: usize, prefix: &str, suffix: &str) -> String {
427 use std::fmt::Write;
428
429 use ansi_str::AnsiBlock;
430
431 if text.is_empty() || width == 0 {
432 return String::new();
433 }
434
435 let mut buf = String::new();
436 let mut line_width = 0;
437 let mut word_begin_pos = 0;
438 let mut word_length = 0;
439 let mut is_empty_buf = true;
440
441 let split = |buf: &mut String, block: &AnsiBlock<'_>| {
442 let _ = write!(buf, "{}", block.end());
443 buf.push_str(suffix);
444 buf.push('\n');
445 buf.push_str(prefix);
446 let _ = write!(buf, "{}", block.start());
447 };
448
449 buf.push_str(prefix);
452
453 for block in ansi_str::get_blocks(text) {
454 if block.text().is_empty() {
455 continue;
456 }
457
458 let _ = write!(buf, "{}", block.start());
459
460 for c in block.text().chars() {
461 let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
462 let is_enough_space = line_width + c_width <= width;
463
464 let is_space = c == ' ';
465 if is_space {
466 word_length = 0;
467 word_begin_pos = 0;
468
469 if !is_enough_space {
470 split(&mut buf, &block);
471 line_width = 0;
472 }
473
474 buf.push(c);
475 line_width += 1;
476
477 if is_empty_buf {
478 is_empty_buf = false;
479 }
480 continue;
481 }
482
483 let is_first_c = word_length == 0;
484 if is_first_c {
485 word_begin_pos = buf.len();
486 }
487
488 if is_enough_space {
489 buf.push(c);
490 word_length += c_width;
491 line_width += c_width;
492
493 if is_empty_buf {
494 is_empty_buf = false;
495 }
496 } else {
497 let partial_word_width = word_length + c_width;
500 let is_word_small = partial_word_width <= width;
501 if is_word_small {
502 if !is_empty_buf {
505 let sep = format!("{}{}\n{}{}", block.end(), suffix, prefix, block.start());
508 buf.insert_str(word_begin_pos, &sep);
509 }
510
511 buf.push(c);
512 line_width = partial_word_width;
513 word_length += c_width;
514
515 if is_empty_buf {
516 is_empty_buf = false;
517 }
518 } else {
519 if !is_empty_buf {
522 split(&mut buf, &block);
523 }
524
525 let is_big_char = c_width > width;
526 if is_big_char {
527 const REPLACEMENT: char = '\u{FFFD}';
528 buf.extend(std::iter::repeat(REPLACEMENT).take(width));
529 line_width = width;
530 word_length = width;
531 } else {
532 buf.push(c);
533 line_width = c_width;
534 word_length += c_width;
535 }
536
537 if is_empty_buf {
538 is_empty_buf = false;
539 }
540 }
541 }
542 }
543
544 let _ = write!(buf, "{}", block.end());
545 }
546
547 if line_width > 0 {
548 buf.push_str(suffix);
549 }
550
551 if line_width < width {
553 let rest = width - line_width;
554 buf.extend(std::iter::repeat(' ').take(rest));
555 }
556
557 buf
558}
559
560fn split_string_at(text: &str, at: usize) -> (&str, &str, (usize, usize)) {
561 use papergrid::util::split_at_pos;
562
563 let (length, count_unknowns, split_char_size) = split_at_pos(text, at);
564 let (lhs, rhs) = text.split_at(length);
565
566 (lhs, rhs, (count_unknowns, split_char_size))
567}
568
569#[cfg(test)]
570mod tests {
571 use super::*;
572
573 #[cfg(feature = "color")]
574 #[test]
575 fn test_color_strip() {
576 use owo_colors::{colors::Yellow, OwoColorize};
577 use papergrid::util::cut_str;
578
579 let s = "Collored string"
580 .fg::<Yellow>()
581 .on_truecolor(12, 200, 100)
582 .blink()
583 .to_string();
584 assert_eq!(
585 cut_str(&s, 1),
586 "\u{1b}[5m\u{1b}[48;2;12;200;100m\u{1b}[33mC\u{1b}[25m\u{1b}[39m\u{1b}[49m"
587 )
588 }
589
590 #[test]
591 fn split_test() {
592 #[cfg(not(feature = "color"))]
593 let split = |text, width| chunks(text, width).join("\n");
594
595 #[cfg(feature = "color")]
596 let split = |text, width| chunks(text, width, "", "").join("\n");
597
598 assert_eq!(split("123456", 0), "");
599
600 assert_eq!(split("123456", 1), "1\n2\n3\n4\n5\n6");
601 assert_eq!(split("123456", 2), "12\n34\n56");
602 assert_eq!(split("12345", 2), "12\n34\n5");
603 assert_eq!(split("123456", 6), "123456");
604 assert_eq!(split("123456", 10), "123456");
605
606 assert_eq!(split("π³π³π³π³π³", 1), "οΏ½\nοΏ½\nοΏ½\nοΏ½\nοΏ½");
607 assert_eq!(split("π³π³π³π³π³", 2), "π³\nπ³\nπ³\nπ³\nπ³");
608 assert_eq!(split("π³π³π³π³π³", 3), "π³οΏ½\nπ³οΏ½\nπ³");
609 assert_eq!(split("π³π³π³π³π³", 6), "π³π³π³\nπ³π³");
610 assert_eq!(split("π³π³π³π³π³", 20), "π³π³π³π³π³");
611
612 assert_eq!(split("π³123π³", 1), "οΏ½\n1\n2\n3\nοΏ½");
613 assert_eq!(split("π³12π³3", 1), "οΏ½\n1\n2\nοΏ½\n3");
614 }
615
616 #[test]
617 fn chunks_test() {
618 #[cfg(not(feature = "color"))]
619 let chunks = |text, width| chunks(text, width);
620
621 #[cfg(feature = "color")]
622 let chunks = |text, width| chunks(text, width, "", "");
623
624 assert_eq!(chunks("123456", 0), [""; 0]);
625
626 assert_eq!(chunks("123456", 1), ["1", "2", "3", "4", "5", "6"]);
627 assert_eq!(chunks("123456", 2), ["12", "34", "56"]);
628 assert_eq!(chunks("12345", 2), ["12", "34", "5"]);
629
630 assert_eq!(chunks("π³π³π³π³π³", 1), ["οΏ½", "οΏ½", "οΏ½", "οΏ½", "οΏ½"]);
631 assert_eq!(chunks("π³π³π³π³π³", 2), ["π³", "π³", "π³", "π³", "π³"]);
632 assert_eq!(chunks("π³π³π³π³π³", 3), ["π³οΏ½", "π³οΏ½", "π³"]);
633 }
634
635 #[cfg(not(feature = "color"))]
636 #[test]
637 fn split_by_line_keeping_words_test() {
638 let split_keeping_words = |text, width| split_keeping_words(text, width, "\n");
639
640 assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6");
641 assert_eq!(split_keeping_words("123456", 2), "12\n34\n56");
642 assert_eq!(split_keeping_words("12345", 2), "12\n34\n5 ");
643
644 assert_eq!(split_keeping_words("π³π³π³π³π³", 1), "οΏ½\nοΏ½\nοΏ½\nοΏ½\nοΏ½");
645
646 assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1 ");
647 }
648
649 #[cfg(feature = "color")]
650 #[test]
651 fn split_by_line_keeping_words_test() {
652 #[cfg(feature = "color")]
653 let split_keeping_words = |text, width| split_keeping_words(text, width, "", "");
654
655 assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6");
656 assert_eq!(split_keeping_words("123456", 2), "12\n34\n56");
657 assert_eq!(split_keeping_words("12345", 2), "12\n34\n5 ");
658
659 assert_eq!(split_keeping_words("π³π³π³π³π³", 1), "οΏ½\nοΏ½\nοΏ½\nοΏ½\nοΏ½");
660
661 assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1 ");
662 }
663
664 #[cfg(feature = "color")]
665 #[test]
666 fn split_by_line_keeping_words_color_test() {
667 #[cfg(feature = "color")]
668 let split_keeping_words = |text, width| split_keeping_words(text, width, "", "");
669
670 #[cfg(not(feature = "color"))]
671 let split_keeping_words = |text, width| split_keeping_words(text, width, "\n");
672
673 let text = "\u{1b}[36mJapanese βvacancyβ button\u{1b}[0m";
674
675 println!("{}", split_keeping_words(text, 2));
676 println!("{}", split_keeping_words(text, 1));
677
678 assert_eq!(split_keeping_words(text, 2), "\u{1b}[36mJa\u{1b}[39m\n\u{1b}[36mpa\u{1b}[39m\n\u{1b}[36mne\u{1b}[39m\n\u{1b}[36mse\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36mβv\u{1b}[39m\n\u{1b}[36mac\u{1b}[39m\n\u{1b}[36man\u{1b}[39m\n\u{1b}[36mcy\u{1b}[39m\n\u{1b}[36mβ \u{1b}[39m\n\u{1b}[36mbu\u{1b}[39m\n\u{1b}[36mtt\u{1b}[39m\n\u{1b}[36mon\u{1b}[39m");
679 assert_eq!(split_keeping_words(text, 1), "\u{1b}[36mJ\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mp\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m\n\u{1b}[36me\u{1b}[39m\n\u{1b}[36ms\u{1b}[39m\n\u{1b}[36me\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36mβ\u{1b}[39m\n\u{1b}[36mv\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mc\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m\n\u{1b}[36mc\u{1b}[39m\n\u{1b}[36my\u{1b}[39m\n\u{1b}[36mβ\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36mb\u{1b}[39m\n\u{1b}[36mu\u{1b}[39m\n\u{1b}[36mt\u{1b}[39m\n\u{1b}[36mt\u{1b}[39m\n\u{1b}[36mo\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m");
680 }
681
682 #[cfg(feature = "color")]
683 #[test]
684 fn split_by_line_keeping_words_color_2_test() {
685 use ansi_str::AnsiStr;
686
687 #[cfg(feature = "color")]
688 let split_keeping_words = |text, width| split_keeping_words(text, width, "", "");
689
690 #[cfg(not(feature = "color"))]
691 let split_keeping_words = |text, width| split_keeping_words(text, width, "\n");
692
693 let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m";
694
695 assert_eq!(
696 split_keeping_words(text, 2)
697 .ansi_split("\n")
698 .collect::<Vec<_>>(),
699 [
700 "\u{1b}[37mTi\u{1b}[39m",
701 "\u{1b}[37mgr\u{1b}[39m",
702 "\u{1b}[37me \u{1b}[39m",
703 "\u{1b}[37mEc\u{1b}[39m",
704 "\u{1b}[37mua\u{1b}[39m",
705 "\u{1b}[37mdo\u{1b}[39m",
706 "\u{1b}[37mr \u{1b}[39m",
707 "\u{1b}[37m \u{1b}[39m",
708 "\u{1b}[37mOM\u{1b}[39m",
709 "\u{1b}[37mYA\u{1b}[39m",
710 "\u{1b}[37m \u{1b}[39m",
711 "\u{1b}[37mAn\u{1b}[39m",
712 "\u{1b}[37mdi\u{1b}[39m",
713 "\u{1b}[37mna\u{1b}[39m",
714 "\u{1b}[37m \u{1b}[39m",
715 "\u{1b}[37m \u{1b}[39m",
716 "\u{1b}[37m \u{1b}[39m",
717 "\u{1b}[37m38\u{1b}[39m",
718 "\u{1b}[37m24\u{1b}[39m",
719 "\u{1b}[37m90\u{1b}[39m",
720 "\u{1b}[37m99\u{1b}[39m",
721 "\u{1b}[37m99\u{1b}[39m",
722 "\u{1b}[37m \u{1b}[39m",
723 "\u{1b}[37m \u{1b}[39m",
724 "\u{1b}[37m \u{1b}[39m",
725 "\u{1b}[37mCa\u{1b}[39m",
726 "\u{1b}[37mlc\u{1b}[39m",
727 "\u{1b}[37miu\u{1b}[39m",
728 "\u{1b}[37mm \u{1b}[39m",
729 "\u{1b}[37mca\u{1b}[39m",
730 "\u{1b}[37mrb\u{1b}[39m",
731 "\u{1b}[37mon\u{1b}[39m",
732 "\u{1b}[37mat\u{1b}[39m",
733 "\u{1b}[37me \u{1b}[39m",
734 "\u{1b}[37m \u{1b}[39m",
735 "\u{1b}[37m \u{1b}[39m",
736 "\u{1b}[37m \u{1b}[39m",
737 "\u{1b}[37mCo\u{1b}[39m",
738 "\u{1b}[37mlo\u{1b}[39m",
739 "\u{1b}[37mmb\u{1b}[39m",
740 "\u{1b}[37mia\u{1b}[39m"
741 ]
742 );
743
744 assert_eq!(
745 split_keeping_words(text, 1)
746 .ansi_split("\n")
747 .collect::<Vec<_>>(),
748 [
749 "\u{1b}[37mT\u{1b}[39m",
750 "\u{1b}[37mi\u{1b}[39m",
751 "\u{1b}[37mg\u{1b}[39m",
752 "\u{1b}[37mr\u{1b}[39m",
753 "\u{1b}[37me\u{1b}[39m",
754 "\u{1b}[37m \u{1b}[39m",
755 "\u{1b}[37mE\u{1b}[39m",
756 "\u{1b}[37mc\u{1b}[39m",
757 "\u{1b}[37mu\u{1b}[39m",
758 "\u{1b}[37ma\u{1b}[39m",
759 "\u{1b}[37md\u{1b}[39m",
760 "\u{1b}[37mo\u{1b}[39m",
761 "\u{1b}[37mr\u{1b}[39m",
762 "\u{1b}[37m \u{1b}[39m",
763 "\u{1b}[37m \u{1b}[39m",
764 "\u{1b}[37m \u{1b}[39m",
765 "\u{1b}[37mO\u{1b}[39m",
766 "\u{1b}[37mM\u{1b}[39m",
767 "\u{1b}[37mY\u{1b}[39m",
768 "\u{1b}[37mA\u{1b}[39m",
769 "\u{1b}[37m \u{1b}[39m",
770 "\u{1b}[37mA\u{1b}[39m",
771 "\u{1b}[37mn\u{1b}[39m",
772 "\u{1b}[37md\u{1b}[39m",
773 "\u{1b}[37mi\u{1b}[39m",
774 "\u{1b}[37mn\u{1b}[39m",
775 "\u{1b}[37ma\u{1b}[39m",
776 "\u{1b}[37m \u{1b}[39m",
777 "\u{1b}[37m \u{1b}[39m",
778 "\u{1b}[37m \u{1b}[39m",
779 "\u{1b}[37m \u{1b}[39m",
780 "\u{1b}[37m \u{1b}[39m",
781 "\u{1b}[37m3\u{1b}[39m",
782 "\u{1b}[37m8\u{1b}[39m",
783 "\u{1b}[37m2\u{1b}[39m",
784 "\u{1b}[37m4\u{1b}[39m",
785 "\u{1b}[37m9\u{1b}[39m",
786 "\u{1b}[37m0\u{1b}[39m",
787 "\u{1b}[37m9\u{1b}[39m",
788 "\u{1b}[37m9\u{1b}[39m",
789 "\u{1b}[37m9\u{1b}[39m",
790 "\u{1b}[37m9\u{1b}[39m",
791 "\u{1b}[37m \u{1b}[39m",
792 "\u{1b}[37m \u{1b}[39m",
793 "\u{1b}[37m \u{1b}[39m",
794 "\u{1b}[37m \u{1b}[39m",
795 "\u{1b}[37m \u{1b}[39m",
796 "\u{1b}[37m \u{1b}[39m",
797 "\u{1b}[37mC\u{1b}[39m",
798 "\u{1b}[37ma\u{1b}[39m",
799 "\u{1b}[37ml\u{1b}[39m",
800 "\u{1b}[37mc\u{1b}[39m",
801 "\u{1b}[37mi\u{1b}[39m",
802 "\u{1b}[37mu\u{1b}[39m",
803 "\u{1b}[37mm\u{1b}[39m",
804 "\u{1b}[37m \u{1b}[39m",
805 "\u{1b}[37mc\u{1b}[39m",
806 "\u{1b}[37ma\u{1b}[39m",
807 "\u{1b}[37mr\u{1b}[39m",
808 "\u{1b}[37mb\u{1b}[39m",
809 "\u{1b}[37mo\u{1b}[39m",
810 "\u{1b}[37mn\u{1b}[39m",
811 "\u{1b}[37ma\u{1b}[39m",
812 "\u{1b}[37mt\u{1b}[39m",
813 "\u{1b}[37me\u{1b}[39m",
814 "\u{1b}[37m \u{1b}[39m",
815 "\u{1b}[37m \u{1b}[39m",
816 "\u{1b}[37m \u{1b}[39m",
817 "\u{1b}[37m \u{1b}[39m",
818 "\u{1b}[37m \u{1b}[39m",
819 "\u{1b}[37m \u{1b}[39m",
820 "\u{1b}[37m \u{1b}[39m",
821 "\u{1b}[37mC\u{1b}[39m",
822 "\u{1b}[37mo\u{1b}[39m",
823 "\u{1b}[37ml\u{1b}[39m",
824 "\u{1b}[37mo\u{1b}[39m",
825 "\u{1b}[37mm\u{1b}[39m",
826 "\u{1b}[37mb\u{1b}[39m",
827 "\u{1b}[37mi\u{1b}[39m",
828 "\u{1b}[37ma\u{1b}[39m"
829 ]
830 )
831 }
832
833 #[cfg(feature = "color")]
834 #[test]
835 fn split_by_line_keeping_words_color_3_test() {
836 let split_keeping_words = |text, width| split_keeping_words(text, width, "", "");
837
838 println!(
839 "{}",
840 split_keeping_words("\u{1b}[37mthis is a long sentence\u{1b}[0m", 7)
841 );
842
843 println!(
844 "{}",
845 split_keeping_words(
846 "\u{1b}[37mπ΅π»π΅π»π΅π»π΅π»π΅π»π΅π»π΅π»π΅π»π΅π»π΅π»\u{1b}[0m",
847 3,
848 ),
849 );
850
851 assert_eq!(
852 split_keeping_words(
853 "\u{1b}[37mπ΅π»π΅π»π΅π»π΅π»π΅π»π΅π»π΅π»π΅π»π΅π»π΅π»\u{1b}[0m",
854 3,
855 ),
856 "\u{1b}[37mπ΅\u{1b}[39m\n\u{1b}[37mπ»\u{1b}[39m\n\u{1b}[37mπ΅\u{1b}[39m\n\u{1b}[37mπ»\u{1b}[39m\n\u{1b}[37mπ΅\u{1b}[39m\n\u{1b}[37mπ»\u{1b}[39m\n\u{1b}[37mπ΅\u{1b}[39m\n\u{1b}[37mπ»\u{1b}[39m\n\u{1b}[37mπ΅\u{1b}[39m\n\u{1b}[37mπ»\u{1b}[39m\n\u{1b}[37mπ΅\u{1b}[39m\n\u{1b}[37mπ»\u{1b}[39m\n\u{1b}[37mπ΅\u{1b}[39m\n\u{1b}[37mπ»\u{1b}[39m\n\u{1b}[37mπ΅\u{1b}[39m\n\u{1b}[37mπ»\u{1b}[39m\n\u{1b}[37mπ΅\u{1b}[39m\n\u{1b}[37mπ»\u{1b}[39m\n\u{1b}[37mπ΅\u{1b}[39m\n\u{1b}[37mπ»\u{1b}[39m ",
857 );
858 assert_eq!(
859 split_keeping_words("\u{1b}[37mthis is a long sentence\u{1b}[0m", 7),
860 "\u{1b}[37mthis is\u{1b}[39m\n\u{1b}[37m a long\u{1b}[39m\n\u{1b}[37m \u{1b}[39m\n\u{1b}[37msentenc\u{1b}[39m\n\u{1b}[37me\u{1b}[39m "
861 );
862 assert_eq!(
863 split_keeping_words("\u{1b}[37mHello World\u{1b}[0m", 7),
864 "\u{1b}[37mHello \u{1b}[39m\n\u{1b}[37mWorld\u{1b}[39m "
865 );
866 assert_eq!(
867 split_keeping_words("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 7),
868 "\u{1b}[37mHello \u{1b}[39m\n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m "
869 );
870 assert_eq!(
871 split_keeping_words("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 8),
872 "\u{1b}[37mHello \u{1b}[39m\n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m "
873 );
874 }
875
876 #[cfg(not(feature = "color"))]
877 #[test]
878 fn split_keeping_words_4_test() {
879 let split_keeping_words = |text, width| split_keeping_words(text, width, "\n");
880
881 assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78 ");
882 assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78");
883 }
884
885 #[cfg(feature = "color")]
886 #[test]
887 fn split_keeping_words_4_test() {
888 let split_keeping_words = |text, width| split_keeping_words(text, width, "", "");
889
890 #[cfg(not(feature = "color"))]
891 let split_keeping_words = |text, width| split_keeping_words(text, width, "\n");
892
893 assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78 ");
894 assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78");
895 }
896
897 #[cfg(feature = "color")]
898 #[test]
899 fn chunks_test_with_prefix_and_suffix() {
900 assert_eq!(chunks("123456", 0, "^", "$"), ["^$"; 0]);
901
902 assert_eq!(
903 chunks("123456", 1, "^", "$"),
904 ["^1$", "^2$", "^3$", "^4$", "^5$", "^6$"]
905 );
906 assert_eq!(chunks("123456", 2, "^", "$"), ["^12$", "^34$", "^56$"]);
907 assert_eq!(chunks("12345", 2, "^", "$"), ["^12$", "^34$", "^5$"]);
908
909 assert_eq!(
910 chunks("π³π³π³π³π³", 1, "^", "$"),
911 ["^οΏ½$", "^οΏ½$", "^οΏ½$", "^οΏ½$", "^οΏ½$"]
912 );
913 assert_eq!(
914 chunks("π³π³π³π³π³", 2, "^", "$"),
915 ["^π³$", "^π³$", "^π³$", "^π³$", "^π³$"]
916 );
917 assert_eq!(
918 chunks("π³π³π³π³π³", 3, "^", "$"),
919 ["^π³οΏ½$", "^π³οΏ½$", "^π³$"]
920 );
921 }
922
923 #[cfg(feature = "color")]
924 #[test]
925 fn split_by_line_keeping_words_test_with_prefix_and_suffix() {
926 assert_eq!(
927 split_keeping_words("123456", 1, "^", "$"),
928 "^1$\n^2$\n^3$\n^4$\n^5$\n^6$"
929 );
930 assert_eq!(
931 split_keeping_words("123456", 2, "^", "$"),
932 "^12$\n^34$\n^56$"
933 );
934 assert_eq!(
935 split_keeping_words("12345", 2, "^", "$"),
936 "^12$\n^34$\n^5$ "
937 );
938
939 assert_eq!(
940 split_keeping_words("π³π³π³π³π³", 1, "^", "$"),
941 "^οΏ½$\n^οΏ½$\n^οΏ½$\n^οΏ½$\n^οΏ½$"
942 );
943 }
944
945 #[cfg(feature = "color")]
946 #[test]
947 fn split_by_line_keeping_words_color_2_test_with_prefix_and_suffix() {
948 use ansi_str::AnsiStr;
949
950 let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m";
951
952 assert_eq!(
953 split_keeping_words(text, 2, "^", "$")
954 .ansi_split("\n")
955 .collect::<Vec<_>>(),
956 [
957 "^\u{1b}[37mTi\u{1b}[39m$",
958 "^\u{1b}[37mgr\u{1b}[39m$",
959 "^\u{1b}[37me \u{1b}[39m$",
960 "^\u{1b}[37mEc\u{1b}[39m$",
961 "^\u{1b}[37mua\u{1b}[39m$",
962 "^\u{1b}[37mdo\u{1b}[39m$",
963 "^\u{1b}[37mr \u{1b}[39m$",
964 "^\u{1b}[37m \u{1b}[39m$",
965 "^\u{1b}[37mOM\u{1b}[39m$",
966 "^\u{1b}[37mYA\u{1b}[39m$",
967 "^\u{1b}[37m \u{1b}[39m$",
968 "^\u{1b}[37mAn\u{1b}[39m$",
969 "^\u{1b}[37mdi\u{1b}[39m$",
970 "^\u{1b}[37mna\u{1b}[39m$",
971 "^\u{1b}[37m \u{1b}[39m$",
972 "^\u{1b}[37m \u{1b}[39m$",
973 "^\u{1b}[37m \u{1b}[39m$",
974 "^\u{1b}[37m38\u{1b}[39m$",
975 "^\u{1b}[37m24\u{1b}[39m$",
976 "^\u{1b}[37m90\u{1b}[39m$",
977 "^\u{1b}[37m99\u{1b}[39m$",
978 "^\u{1b}[37m99\u{1b}[39m$",
979 "^\u{1b}[37m \u{1b}[39m$",
980 "^\u{1b}[37m \u{1b}[39m$",
981 "^\u{1b}[37m \u{1b}[39m$",
982 "^\u{1b}[37mCa\u{1b}[39m$",
983 "^\u{1b}[37mlc\u{1b}[39m$",
984 "^\u{1b}[37miu\u{1b}[39m$",
985 "^\u{1b}[37mm \u{1b}[39m$",
986 "^\u{1b}[37mca\u{1b}[39m$",
987 "^\u{1b}[37mrb\u{1b}[39m$",
988 "^\u{1b}[37mon\u{1b}[39m$",
989 "^\u{1b}[37mat\u{1b}[39m$",
990 "^\u{1b}[37me \u{1b}[39m$",
991 "^\u{1b}[37m \u{1b}[39m$",
992 "^\u{1b}[37m \u{1b}[39m$",
993 "^\u{1b}[37m \u{1b}[39m$",
994 "^\u{1b}[37mCo\u{1b}[39m$",
995 "^\u{1b}[37mlo\u{1b}[39m$",
996 "^\u{1b}[37mmb\u{1b}[39m$",
997 "^\u{1b}[37mia\u{1b}[39m$"
998 ]
999 );
1000
1001 assert_eq!(
1002 split_keeping_words(text, 1, "^", "$")
1003 .ansi_split("\n")
1004 .collect::<Vec<_>>(),
1005 [
1006 "^\u{1b}[37mT\u{1b}[39m$",
1007 "^\u{1b}[37mi\u{1b}[39m$",
1008 "^\u{1b}[37mg\u{1b}[39m$",
1009 "^\u{1b}[37mr\u{1b}[39m$",
1010 "^\u{1b}[37me\u{1b}[39m$",
1011 "^\u{1b}[37m \u{1b}[39m$",
1012 "^\u{1b}[37mE\u{1b}[39m$",
1013 "^\u{1b}[37mc\u{1b}[39m$",
1014 "^\u{1b}[37mu\u{1b}[39m$",
1015 "^\u{1b}[37ma\u{1b}[39m$",
1016 "^\u{1b}[37md\u{1b}[39m$",
1017 "^\u{1b}[37mo\u{1b}[39m$",
1018 "^\u{1b}[37mr\u{1b}[39m$",
1019 "^\u{1b}[37m \u{1b}[39m$",
1020 "^\u{1b}[37m \u{1b}[39m$",
1021 "^\u{1b}[37m \u{1b}[39m$",
1022 "^\u{1b}[37mO\u{1b}[39m$",
1023 "^\u{1b}[37mM\u{1b}[39m$",
1024 "^\u{1b}[37mY\u{1b}[39m$",
1025 "^\u{1b}[37mA\u{1b}[39m$",
1026 "^\u{1b}[37m \u{1b}[39m$",
1027 "^\u{1b}[37mA\u{1b}[39m$",
1028 "^\u{1b}[37mn\u{1b}[39m$",
1029 "^\u{1b}[37md\u{1b}[39m$",
1030 "^\u{1b}[37mi\u{1b}[39m$",
1031 "^\u{1b}[37mn\u{1b}[39m$",
1032 "^\u{1b}[37ma\u{1b}[39m$",
1033 "^\u{1b}[37m \u{1b}[39m$",
1034 "^\u{1b}[37m \u{1b}[39m$",
1035 "^\u{1b}[37m \u{1b}[39m$",
1036 "^\u{1b}[37m \u{1b}[39m$",
1037 "^\u{1b}[37m \u{1b}[39m$",
1038 "^\u{1b}[37m3\u{1b}[39m$",
1039 "^\u{1b}[37m8\u{1b}[39m$",
1040 "^\u{1b}[37m2\u{1b}[39m$",
1041 "^\u{1b}[37m4\u{1b}[39m$",
1042 "^\u{1b}[37m9\u{1b}[39m$",
1043 "^\u{1b}[37m0\u{1b}[39m$",
1044 "^\u{1b}[37m9\u{1b}[39m$",
1045 "^\u{1b}[37m9\u{1b}[39m$",
1046 "^\u{1b}[37m9\u{1b}[39m$",
1047 "^\u{1b}[37m9\u{1b}[39m$",
1048 "^\u{1b}[37m \u{1b}[39m$",
1049 "^\u{1b}[37m \u{1b}[39m$",
1050 "^\u{1b}[37m \u{1b}[39m$",
1051 "^\u{1b}[37m \u{1b}[39m$",
1052 "^\u{1b}[37m \u{1b}[39m$",
1053 "^\u{1b}[37m \u{1b}[39m$",
1054 "^\u{1b}[37mC\u{1b}[39m$",
1055 "^\u{1b}[37ma\u{1b}[39m$",
1056 "^\u{1b}[37ml\u{1b}[39m$",
1057 "^\u{1b}[37mc\u{1b}[39m$",
1058 "^\u{1b}[37mi\u{1b}[39m$",
1059 "^\u{1b}[37mu\u{1b}[39m$",
1060 "^\u{1b}[37mm\u{1b}[39m$",
1061 "^\u{1b}[37m \u{1b}[39m$",
1062 "^\u{1b}[37mc\u{1b}[39m$",
1063 "^\u{1b}[37ma\u{1b}[39m$",
1064 "^\u{1b}[37mr\u{1b}[39m$",
1065 "^\u{1b}[37mb\u{1b}[39m$",
1066 "^\u{1b}[37mo\u{1b}[39m$",
1067 "^\u{1b}[37mn\u{1b}[39m$",
1068 "^\u{1b}[37ma\u{1b}[39m$",
1069 "^\u{1b}[37mt\u{1b}[39m$",
1070 "^\u{1b}[37me\u{1b}[39m$",
1071 "^\u{1b}[37m \u{1b}[39m$",
1072 "^\u{1b}[37m \u{1b}[39m$",
1073 "^\u{1b}[37m \u{1b}[39m$",
1074 "^\u{1b}[37m \u{1b}[39m$",
1075 "^\u{1b}[37m \u{1b}[39m$",
1076 "^\u{1b}[37m \u{1b}[39m$",
1077 "^\u{1b}[37m \u{1b}[39m$",
1078 "^\u{1b}[37mC\u{1b}[39m$",
1079 "^\u{1b}[37mo\u{1b}[39m$",
1080 "^\u{1b}[37ml\u{1b}[39m$",
1081 "^\u{1b}[37mo\u{1b}[39m$",
1082 "^\u{1b}[37mm\u{1b}[39m$",
1083 "^\u{1b}[37mb\u{1b}[39m$",
1084 "^\u{1b}[37mi\u{1b}[39m$",
1085 "^\u{1b}[37ma\u{1b}[39m$"
1086 ]
1087 )
1088 }
1089
1090 #[cfg(feature = "color")]
1091 #[test]
1092 fn chunks_wrap_2() {
1093 let text = "\u{1b}[30mDebian\u{1b}[0m\u{1b}[31mDebian\u{1b}[0m\u{1b}[32mDebian\u{1b}[0m\u{1b}[33mDebian\u{1b}[0m\u{1b}[34mDebian\u{1b}[0m\u{1b}[35mDebian\u{1b}[0m\u{1b}[36mDebian\u{1b}[0m\u{1b}[37mDebian\u{1b}[0m\u{1b}[40mDebian\u{1b}[0m\u{1b}[41mDebian\u{1b}[0m\u{1b}[42mDebian\u{1b}[0m\u{1b}[43mDebian\u{1b}[0m\u{1b}[44mDebian\u{1b}[0m";
1094
1095 assert_eq!(
1096 chunks(text, 30, "", ""),
1097 [
1098 "\u{1b}[30mDebian\u{1b}[39m\u{1b}[31mDebian\u{1b}[39m\u{1b}[32mDebian\u{1b}[39m\u{1b}[33mDebian\u{1b}[39m\u{1b}[34mDebian\u{1b}[39m\u{1b}[35m\u{1b}[39m", "\u{1b}[35mDebian\u{1b}[39m\u{1b}[36mDebian\u{1b}[39m\u{1b}[37mDebian\u{1b}[39m\u{1b}[40mDebian\u{1b}[49m\u{1b}[41mDebian\u{1b}[49m\u{1b}[42m\u{1b}[49m", "\u{1b}[42mDebian\u{1b}[49m\u{1b}[43mDebian\u{1b}[49m\u{1b}[44mDebian\u{1b}[49m"
1099 ]
1100 );
1101 }
1102}