tabled/features/width/
wrap.rs

1//! This module contains [`Wrap`] structure, used to decrease width of a [`Table`]s or a cell on a [`Table`] by wrapping it's content
2//! to a new line.
3
4use 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/// Wrap wraps a string to a new line in case it exceeds the provided max boundary.
25/// Otherwise keeps the content of a cell untouched.
26///
27/// The function is color aware if a `color` feature is on.
28///
29/// Be aware that it doesn't consider padding.
30/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0.
31///
32/// ## Example
33///
34/// ```
35/// use tabled::{object::Segment, Width, Modify, Table};
36///
37/// let table = Table::new(&["Hello World!"])
38///     .with(Modify::new(Segment::all()).with(Width::wrap(3)));
39/// ```
40///
41/// [`Padding`]: crate::Padding
42#[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    /// Creates a [`Wrap`] object
54    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    /// Priority defines the logic by which a truncate will be applied when is done for the whole table.
65    ///
66    /// - [`PriorityNone`] which cuts the columns one after another.
67    /// - [`PriorityMax`] cuts the biggest columns first.
68    /// - [`PriorityMin`] cuts the lowest columns first.
69    ///
70    /// Be aware that it doesn't consider padding.
71    /// So if you want to set a exact width you might need to use [`Padding`] to set it to 0.
72    ///
73    /// [`Padding`]: crate::Padding
74    /// [`PriorityMax`]: crate::peaker::PriorityMax
75    /// [`PriorityMin`]: crate::peaker::PriorityMin
76    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    /// Set the keep words option.
85    ///
86    /// If a wrapping point will be in a word, [`Wrap`] will
87    /// preserve a word (if possible) and wrap the string before it.
88    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            // todo: Think about it.
113            //       We could eliminate this allcation if we would be allowed to cut '\t' with unknown characters.
114            //       Currently we don't do that.
115            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            // https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
224            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            // the word can be fit to 'width' so we put it on new line
382
383            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            // the word is too long any way so we split it
394
395            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    // go char by char and split string afterwords
450
451    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                // we can't say if the word is really fits in at this time because we may not have the whole word,
498                // but it's good enough.
499                let partial_word_width = word_length + c_width;
500                let is_word_small = partial_word_width <= width;
501                if is_word_small {
502                    // move it to other line
503
504                    if !is_empty_buf {
505                        // we don't fill the rest of the prev line here
506
507                        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                    // it's not small so we can't do anything about it.
520
521                    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    // fill the remainings in a last line if it has any.
552    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}