1use ansi_parser::AnsiSequence;
22use ansi_parser::{AnsiParser, Output};
23use std::ops::{Bound, RangeBounds};
24
25pub trait AnsiCut {
28 fn cut<R>(&self, range: R) -> String
50 where
51 R: RangeBounds<usize>;
52}
53
54impl AnsiCut for &str {
55 fn cut<R>(&self, range: R) -> String
56 where
57 R: RangeBounds<usize>,
58 {
59 crate::cut(&self, range)
60 }
61}
62
63impl AnsiCut for String {
64 fn cut<R>(&self, range: R) -> String
65 where
66 R: RangeBounds<usize>,
67 {
68 crate::cut(&self, range)
69 }
70}
71
72pub fn chunks(s: &str, chunk_size: usize) -> Vec<String> {
93 assert!(chunk_size > 0);
94
95 let stripped = srip_ansi_sequences(s);
96 let count_chars = stripped.chars().count();
97 let mut chunks = Vec::new();
98 let mut start_pos = 0;
99
100 while start_pos < count_chars {
101 let start = stripped
102 .chars()
103 .map(|c| c.len_utf8())
104 .take(start_pos)
105 .sum::<usize>();
106 let end_pos = std::cmp::min(start_pos + chunk_size, count_chars);
107 let end = stripped
108 .chars()
109 .map(|c| c.len_utf8())
110 .take(end_pos)
111 .sum::<usize>();
112 let part = s.cut(start..end);
113 start_pos = end_pos;
114
115 if part.is_empty() {
116 break;
117 }
118
119 chunks.push(part);
120 }
121
122 chunks
123}
124
125fn cut<S, R>(string: S, bounds: R) -> String
128where
129 S: AsRef<str>,
130 R: RangeBounds<usize>,
131{
132 let string = string.as_ref();
133 let (start, end) = bounds_to_usize(bounds.start_bound(), bounds.end_bound());
134
135 cut_str(string, start, end)
136}
137
138fn cut_str(string: &str, lower_bound: usize, upper_bound: Option<usize>) -> String {
139 let mut asci_state = AnsiState::default();
140 let tokens = string.ansi_parse();
141 let mut buf = String::new();
142 let mut index = 0;
143
144 '_tokens_loop: for token in tokens {
145 match token {
146 Output::TextBlock(text) => {
147 let block_end_index = index + text.len();
148 if lower_bound > block_end_index {
149 index += text.len();
150 continue;
151 };
152
153 let mut start = 0;
154 if lower_bound > index {
155 start = lower_bound - index;
156 }
157
158 let mut end = text.len();
159 let mut done = false;
160 if let Some(upper_bound) = upper_bound {
161 if upper_bound > index && upper_bound < block_end_index {
162 end = upper_bound - index;
163 done = true;
164 }
165 }
166
167 index += text.len();
168
169 match text.get(start..end) {
170 Some(text) => {
171 buf.push_str(text);
172 if done {
173 break '_tokens_loop;
174 }
175 }
176 None => {
177 panic!("One of indexes are not on a UTF-8 code point boundary");
178 }
179 }
180 }
181 Output::Escape(seq) => {
182 let seq_str = seq.to_string();
183 buf.push_str(&seq_str);
184 if let AnsiSequence::SetGraphicsMode(v) = seq {
185 update_ansi_state(&mut asci_state, v.as_ref());
186 }
187 }
188 }
189 }
190
191 complete_ansi_sequences(&asci_state, &mut buf);
192
193 buf
194}
195
196#[derive(Debug, Clone, Default)]
197struct AnsiState {
198 fg_color: Option<AnsiColor>,
199 bg_color: Option<AnsiColor>,
200 undr_color: Option<AnsiColor>,
201 bold: bool,
202 faint: bool,
203 italic: bool,
204 underline: bool,
205 double_underline: bool,
206 slow_blink: bool,
207 rapid_blink: bool,
208 inverse: bool,
209 hide: bool,
210 crossedout: bool,
211 reset: bool,
212 framed: bool,
213 encircled: bool,
214 font: Option<u8>,
215 fraktur: bool,
216 proportional_spacing: bool,
217 overlined: bool,
218 igrm_underline: bool,
219 igrm_double_underline: bool,
220 igrm_overline: bool,
221 igrm_double_overline: bool,
222 igrm_stress_marking: bool,
223 superscript: bool,
224 subscript: bool,
225 unknown: bool,
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
229enum AnsiColor {
230 Bit4 { index: u8 },
231 Bit8 { index: u8 },
232 Bit24 { r: u8, g: u8, b: u8 },
233}
234
235fn update_ansi_state(state: &mut AnsiState, mode: &[u8]) {
236 let mut ptr = mode;
237 loop {
238 if ptr.is_empty() {
239 break;
240 }
241
242 let tag = ptr[0];
243
244 match tag {
245 0 => {
246 *state = AnsiState::default();
247 state.reset = true;
248 }
249 1 => state.bold = true,
250 2 => state.faint = true,
251 3 => state.italic = true,
252 4 => state.underline = true,
253 5 => state.slow_blink = true,
254 6 => state.rapid_blink = true,
255 7 => state.inverse = true,
256 8 => state.hide = true,
257 9 => state.crossedout = true,
258 10 => state.font = None,
259 n @ 11..=19 => state.font = Some(n),
260 20 => state.fraktur = true,
261 21 => state.double_underline = true,
262 22 => {
263 state.faint = false;
264 state.bold = false;
265 }
266 23 => {
267 state.italic = false;
268 }
269 24 => {
270 state.underline = false;
271 state.double_underline = false;
272 }
273 25 => {
274 state.slow_blink = false;
275 state.rapid_blink = false;
276 }
277 26 => {
278 state.proportional_spacing = true;
279 }
280 28 => {
281 state.inverse = false;
282 }
283 29 => {
284 state.crossedout = false;
285 }
286 n @ 30..=37 | n @ 90..=97 => {
287 state.fg_color = Some(AnsiColor::Bit4 { index: n });
288 }
289 38 => {
290 if let Some((color, n)) = parse_ansi_color(ptr) {
291 state.fg_color = Some(color);
292 ptr = &ptr[n..];
293 }
294 }
295 39 => {
296 state.fg_color = None;
297 }
298 n @ 40..=47 | n @ 100..=107 => {
299 state.bg_color = Some(AnsiColor::Bit4 { index: n });
300 }
301 48 => {
302 if let Some((color, n)) = parse_ansi_color(ptr) {
303 state.bg_color = Some(color);
304 ptr = &ptr[n..];
305 }
306 }
307 49 => {
308 state.bg_color = None;
309 }
310 50 => {
311 state.proportional_spacing = false;
312 }
313 51 => {
314 state.framed = true;
315 }
316 52 => {
317 state.encircled = true;
318 }
319 53 => {
320 state.overlined = true;
321 }
322 54 => {
323 state.encircled = false;
324 state.framed = false;
325 }
326 55 => {
327 state.overlined = false;
328 }
329 58 => {
330 if let Some((color, n)) = parse_ansi_color(ptr) {
331 state.undr_color = Some(color);
332 ptr = &ptr[n..];
333 }
334 }
335 59 => {
336 state.undr_color = None;
337 }
338 60 => {
339 state.igrm_underline = true;
340 }
341 61 => {
342 state.igrm_double_underline = true;
343 }
344 62 => {
345 state.igrm_overline = true;
346 }
347 63 => {
348 state.igrm_double_overline = true;
349 }
350 64 => {
351 state.igrm_stress_marking = true;
352 }
353 65 => {
354 state.igrm_underline = false;
355 state.igrm_double_underline = false;
356 state.igrm_overline = false;
357 state.igrm_double_overline = false;
358 state.igrm_stress_marking = false;
359 }
360 73 => {
361 state.superscript = true;
362 }
363 74 => {
364 state.subscript = true;
365 }
366 75 => {
367 state.subscript = false;
368 state.superscript = false;
369 }
370 _ => {
371 state.unknown = true;
372 }
373 }
374
375 ptr = &ptr[1..];
376 }
377}
378
379fn parse_ansi_color(buf: &[u8]) -> Option<(AnsiColor, usize)> {
380 match buf {
381 [b'2', b';', index, ..] => Some((AnsiColor::Bit8 { index: *index }, 3)),
382 [b'5', b';', r, b';', g, b';', b, ..] => Some((
383 AnsiColor::Bit24 {
384 r: *r,
385 g: *g,
386 b: *b,
387 },
388 7,
389 )),
390 _ => None,
391 }
392}
393
394fn complete_ansi_sequences(state: &AnsiState, buf: &mut String) {
395 macro_rules! emit_static {
396 ($s:expr) => {
397 buf.push_str(concat!("\u{1b}[", $s, "m"))
398 };
399 }
400
401 if state.unknown && state.reset {
402 emit_static!("0");
403 }
404
405 if state.font.is_some() {
406 emit_static!("10");
407 }
408
409 if state.bold || state.faint {
410 emit_static!("22");
411 }
412
413 if state.italic {
414 emit_static!("23");
415 }
416
417 if state.underline || state.double_underline {
418 emit_static!("24");
419 }
420
421 if state.slow_blink || state.rapid_blink {
422 emit_static!("25");
423 }
424
425 if state.inverse {
426 emit_static!("28");
427 }
428
429 if state.crossedout {
430 emit_static!("29");
431 }
432
433 if state.fg_color.is_some() {
434 emit_static!("39");
435 }
436
437 if state.bg_color.is_some() {
438 emit_static!("49");
439 }
440
441 if state.proportional_spacing {
442 emit_static!("50");
443 }
444
445 if state.encircled || state.framed {
446 emit_static!("54");
447 }
448
449 if state.overlined {
450 emit_static!("55");
451 }
452
453 if state.igrm_underline
454 || state.igrm_double_underline
455 || state.igrm_overline
456 || state.igrm_double_overline
457 || state.igrm_stress_marking
458 {
459 emit_static!("65");
460 }
461
462 if state.undr_color.is_some() {
463 emit_static!("59");
464 }
465
466 if state.subscript || state.superscript {
467 emit_static!("75");
468 }
469
470 if state.unknown {
471 emit_static!("0");
472 }
473}
474
475fn bounds_to_usize(left: Bound<&usize>, right: Bound<&usize>) -> (usize, Option<usize>) {
476 match (left, right) {
477 (Bound::Included(x), Bound::Included(y)) => (*x, Some(y + 1)),
478 (Bound::Included(x), Bound::Excluded(y)) => (*x, Some(*y)),
479 (Bound::Included(x), Bound::Unbounded) => (*x, None),
480 (Bound::Unbounded, Bound::Unbounded) => (0, None),
481 (Bound::Unbounded, Bound::Included(y)) => (0, Some(y + 1)),
482 (Bound::Unbounded, Bound::Excluded(y)) => (0, Some(*y)),
483 (Bound::Excluded(_), Bound::Unbounded)
484 | (Bound::Excluded(_), Bound::Included(_))
485 | (Bound::Excluded(_), Bound::Excluded(_)) => {
486 unreachable!("A start bound can't be excluded")
487 }
488 }
489}
490
491fn srip_ansi_sequences(string: &str) -> String {
492 let tokens = string.ansi_parse();
493 let mut buf = String::new();
494 for token in tokens {
495 match token {
496 Output::TextBlock(text) => {
497 buf.push_str(text);
498 }
499 Output::Escape(_) => {}
500 }
501 }
502
503 buf
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn parse_ansi_color_test() {
512 let tests: Vec<(&[u8], _)> = vec![
513 (&[b'2', b';', 200], Some(AnsiColor::Bit8 { index: 200 })),
514 (
515 &[b'2', b';', 100, b';', 123, b';', 39],
516 Some(AnsiColor::Bit8 { index: 100 }),
517 ),
518 (
519 &[b'2', b';', 100, 1, 2, 3],
520 Some(AnsiColor::Bit8 { index: 100 }),
521 ),
522 (&[b'2', b';'], None),
523 (&[b'2', 1, 2, 3], None),
524 (&[b'2'], None),
525 (
526 &[b'5', b';', 100, b';', 123, b';', 39],
527 Some(AnsiColor::Bit24 {
528 r: 100,
529 g: 123,
530 b: 39,
531 }),
532 ),
533 (
534 &[b'5', b';', 100, b';', 123, b';', 39, 1, 2, 3],
535 Some(AnsiColor::Bit24 {
536 r: 100,
537 g: 123,
538 b: 39,
539 }),
540 ),
541 (
542 &[b'5', b';', 100, b';', 123, b';', 39, 1, 2, 3],
543 Some(AnsiColor::Bit24 {
544 r: 100,
545 g: 123,
546 b: 39,
547 }),
548 ),
549 (&[b'5', b';', 100, b';', 123, b';'], None),
550 (&[b'5', b';', 100, b';', 123], None),
551 (&[b'5', b';', 100, b';'], None),
552 (&[b'5', b';', 100], None),
553 (&[b'5', b';'], None),
554 (&[b'5'], None),
555 (&[], None),
556 ];
557
558 for (i, (bytes, expected)) in tests.into_iter().enumerate() {
559 assert_eq!(parse_ansi_color(bytes).map(|a| a.0), expected, "test={}", i);
560 }
561 }
562
563 #[test]
564 fn cut_colored_fg_test() {
565 let colored_s = "\u{1b}[30mTEXT\u{1b}[39m";
566 assert_eq!(colored_s, colored_s.cut(..));
567 assert_eq!(colored_s, colored_s.cut(0..4));
568 assert_eq!("\u{1b}[30mEXT\u{1b}[39m", colored_s.cut(1..));
569 assert_eq!("\u{1b}[30mTEX\u{1b}[39m", colored_s.cut(..3));
570 assert_eq!("\u{1b}[30mEX\u{1b}[39m", colored_s.cut(1..3));
571
572 assert_eq!("TEXT", srip_ansi_sequences(&colored_s.cut(..)));
573 assert_eq!("TEX", srip_ansi_sequences(&colored_s.cut(..3)));
574 assert_eq!("EX", srip_ansi_sequences(&colored_s.cut(1..3)));
575
576 let colored_s = "\u{1b}[30mTEXT\u{1b}[39m \u{1b}[31mTEXT\u{1b}[39m";
577 assert_eq!(colored_s, colored_s.cut(..));
578 assert_eq!(colored_s, colored_s.cut(0..9));
579 assert_eq!(
580 "\u{1b}[30mXT\u{1b}[39m \u{1b}[31mTEXT\u{1b}[39m",
581 colored_s.cut(2..)
582 );
583 assert_eq!(
584 "\u{1b}[30mTEXT\u{1b}[39m \u{1b}[31mT\u{1b}[39m",
585 colored_s.cut(..6)
586 );
587 assert_eq!(
588 "\u{1b}[30mXT\u{1b}[39m \u{1b}[31mT\u{1b}[39m",
589 colored_s.cut(2..6)
590 );
591
592 assert_eq!("TEXT TEXT", srip_ansi_sequences(&colored_s.cut(..)));
593 assert_eq!("TEXT T", srip_ansi_sequences(&colored_s.cut(..6)));
594 assert_eq!("XT T", srip_ansi_sequences(&colored_s.cut(2..6)));
595
596 assert_eq!("\u{1b}[30m\u{1b}[39m", cut("\u{1b}[30m\u{1b}[39m", ..));
597 }
598
599 #[test]
600 fn cut_colored_bg_test() {
601 let colored_s = "\u{1b}[40mTEXT\u{1b}[49m";
602 assert_eq!(colored_s, colored_s.cut(..));
603 assert_eq!(colored_s, colored_s.cut(0..4));
604 assert_eq!("\u{1b}[40mEXT\u{1b}[49m", colored_s.cut(1..));
605 assert_eq!("\u{1b}[40mTEX\u{1b}[49m", colored_s.cut(..3));
606 assert_eq!("\u{1b}[40mEX\u{1b}[49m", colored_s.cut(1..3));
607
608 assert_eq!("\u{1b}[40m\u{1b}[49m", colored_s.cut(3..3));
610
611 assert_eq!("TEXT", srip_ansi_sequences(&colored_s.cut(..)));
612 assert_eq!("TEX", srip_ansi_sequences(&colored_s.cut(..3)));
613 assert_eq!("EX", srip_ansi_sequences(&colored_s.cut(1..3)));
614
615 let colored_s = "\u{1b}[40mTEXT\u{1b}[49m \u{1b}[41mTEXT\u{1b}[49m";
616 assert_eq!(colored_s, colored_s.cut(..));
617 assert_eq!(colored_s, colored_s.cut(0..9));
618 assert_eq!(
619 "\u{1b}[40mXT\u{1b}[49m \u{1b}[41mTEXT\u{1b}[49m",
620 colored_s.cut(2..)
621 );
622 assert_eq!(
623 "\u{1b}[40mTEXT\u{1b}[49m \u{1b}[41mT\u{1b}[49m",
624 colored_s.cut(..6)
625 );
626 assert_eq!(
627 "\u{1b}[40mXT\u{1b}[49m \u{1b}[41mT\u{1b}[49m",
628 colored_s.cut(2..6)
629 );
630
631 assert_eq!("TEXT TEXT", srip_ansi_sequences(&colored_s.cut(..)));
632 assert_eq!("TEXT T", srip_ansi_sequences(&colored_s.cut(..6)));
633 assert_eq!("XT T", srip_ansi_sequences(&colored_s.cut(2..6)));
634
635 assert_eq!("\u{1b}[40m\u{1b}[49m", cut("\u{1b}[40m\u{1b}[49m", ..));
636 }
637
638 #[test]
639 fn cut_colored_bg_fg_test() {
640 let colored_s = "\u{1b}[31;40mTEXT\u{1b}[0m";
641 assert_eq!(colored_s, colored_s.cut(..));
642 assert_eq!(colored_s, colored_s.cut(0..4));
643 assert_eq!("\u{1b}[31;40mEXT\u{1b}[0m", colored_s.cut(1..));
644 assert_eq!("\u{1b}[31;40mTEX\u{1b}[39m\u{1b}[49m", colored_s.cut(..3));
645 assert_eq!("\u{1b}[31;40mEX\u{1b}[39m\u{1b}[49m", colored_s.cut(1..3));
646
647 assert_eq!("TEXT", srip_ansi_sequences(&colored_s.cut(..)));
648 assert_eq!("TEX", srip_ansi_sequences(&colored_s.cut(..3)));
649 assert_eq!("EX", srip_ansi_sequences(&colored_s.cut(1..3)));
650
651 let colored_s = "\u{1b}[31;40mTEXT\u{1b}[0m \u{1b}[34;42mTEXT\u{1b}[0m";
652 assert_eq!(colored_s, colored_s.cut(..));
653 assert_eq!(colored_s, colored_s.cut(0..9));
654 assert_eq!(
655 "\u{1b}[31;40mXT\u{1b}[0m \u{1b}[34;42mTEXT\u{1b}[0m",
656 colored_s.cut(2..)
657 );
658 assert_eq!(
659 "\u{1b}[31;40mTEXT\u{1b}[0m \u{1b}[34;42mT\u{1b}[39m\u{1b}[49m",
660 colored_s.cut(..6)
661 );
662 assert_eq!(
663 "\u{1b}[31;40mXT\u{1b}[0m \u{1b}[34;42mT\u{1b}[39m\u{1b}[49m",
664 colored_s.cut(2..6)
665 );
666
667 assert_eq!("TEXT TEXT", srip_ansi_sequences(&colored_s.cut(..)));
668 assert_eq!("TEXT T", srip_ansi_sequences(&colored_s.cut(..6)));
669 assert_eq!("XT T", srip_ansi_sequences(&colored_s.cut(2..6)));
670
671 assert_eq!("\u{1b}[40m\u{1b}[49m", cut("\u{1b}[40m\u{1b}[49m", ..));
672 }
673
674 #[test]
675 fn cut_colored_test() {}
676
677 #[test]
678 fn cut_no_colored_str() {
679 assert_eq!("something", cut("something", ..));
680 assert_eq!("som", cut("something", ..3));
681 assert_eq!("some", cut("something", ..=3));
682 assert_eq!("et", cut("something", 3..5));
683 assert_eq!("eth", cut("something", 3..=5));
684 assert_eq!("ething", cut("something", 3..));
685 assert_eq!("something", cut("something", ..));
686 assert_eq!("", cut("", ..));
687 }
688
689 #[test]
690 fn dont_panic_on_exceeding_upper_bound() {
691 assert_eq!("TEXT", cut("TEXT", ..50));
692 assert_eq!("EXT", cut("TEXT", 1..50));
693 assert_eq!(
694 "\u{1b}[31;40mTEXT\u{1b}[0m",
695 cut("\u{1b}[31;40mTEXT\u{1b}[0m", ..50)
696 );
697 assert_eq!(
698 "\u{1b}[31;40mEXT\u{1b}[0m",
699 cut("\u{1b}[31;40mTEXT\u{1b}[0m", 1..50)
700 );
701 }
702
703 #[test]
704 fn dont_panic_on_exceeding_lower_bound() {
705 assert_eq!("", cut("TEXT", 10..));
706 assert_eq!("", cut("TEXT", 10..50));
707 }
708
709 #[test]
710 #[should_panic = "One of indexes are not on a UTF-8 code point boundary"]
711 fn cut_a_mid_of_emojie_2_test() {
712 cut("😀", 1..2);
713 }
714
715 #[test]
716 #[should_panic = "One of indexes are not on a UTF-8 code point boundary"]
717 fn cut_a_mid_of_emojie_1_test() {
718 cut("😀", 1..);
719 }
720
721 #[test]
722 #[should_panic = "One of indexes are not on a UTF-8 code point boundary"]
723 fn cut_a_mid_of_emojie_0_test() {
724 cut("😀", ..1);
725 }
726
727 #[test]
728 fn cut_emojies_test() {
729 let emojes = "😀😃😄😁😆😅😂🤣🥲😊";
730 assert_eq!(emojes, emojes.cut(..));
731 assert_eq!("😀", emojes.cut(..4));
732 assert_eq!("😃😄", emojes.cut(4..12));
733 assert_eq!("🤣🥲😊", emojes.cut(emojes.find('🤣').unwrap()..));
734 }
735
736 #[test]
737 fn cut_colored_x_x_test() {
739 assert_ne!("", cut("\u{1b}[31;40mTEXT\u{1b}[0m", 3..3));
740 assert_ne!(
741 "",
742 cut(
743 "\u{1b}[31;40mTEXT\u{1b}[0m \u{1b}[34;42mTEXT\u{1b}[0m",
744 1..1
745 )
746 );
747 assert_ne!("", cut("\u{1b}[31;40mTEXT\u{1b}[0m", ..0));
748 }
749
750 #[test]
751 fn cut_partially_colored_str_test() {
752 let s = "zxc_\u{1b}[31;40mTEXT\u{1b}[0m_qwe";
753 assert_eq!("zxc", s.cut(..3));
754 assert_eq!("zxc_\u{1b}[31;40mT\u{1b}[39m\u{1b}[49m", s.cut(..5));
755 assert_eq!("\u{1b}[31;40mEXT\u{1b}[0m_q", s.cut(5..10));
756 assert_eq!("\u{1b}[31;40m\u{1b}[0m", s.cut(12..));
757 }
758
759 #[test]
760 fn chunks_not_colored_test() {
761 assert_eq!(
762 vec!["som".to_string(), "eth".to_string(), "ing".to_string()],
763 chunks("something", 3)
764 );
765 assert_eq!(
766 vec![
767 "so".to_string(),
768 "me".to_string(),
769 "th".to_string(),
770 "in".to_string(),
771 "g".to_string()
772 ],
773 chunks("something", 2)
774 );
775 assert_eq!(
776 vec!["a".to_string(), "b".to_string(), "c".to_string()],
777 chunks("abc", 1)
778 );
779 assert_eq!(vec!["something".to_string()], chunks("something", 99));
780 }
781
782 #[test]
783 #[should_panic]
784 fn chunks_panic_when_n_is_zero() {
785 chunks("something", 0);
786 }
787
788 #[test]
789 fn chunks_colored() {
790 let text = "\u{1b}[31;40mTEXT\u{1b}[0m";
791 assert_eq!(
792 vec![
793 "\u{1b}[31;40mT\u{1b}[39m\u{1b}[49m",
794 "\u{1b}[31;40mE\u{1b}[39m\u{1b}[49m",
795 "\u{1b}[31;40mX\u{1b}[39m\u{1b}[49m",
796 "\u{1b}[31;40mT\u{1b}[0m"
797 ],
798 chunks(text, 1)
799 );
800 assert_eq!(
801 vec![
802 "\u{1b}[31;40mTE\u{1b}[39m\u{1b}[49m",
803 "\u{1b}[31;40mXT\u{1b}[0m"
804 ],
805 chunks(text, 2)
806 );
807 assert_eq!(
808 vec![
809 "\u{1b}[31;40mTEX\u{1b}[39m\u{1b}[49m",
810 "\u{1b}[31;40mT\u{1b}[0m"
811 ],
812 chunks(text, 3)
813 );
814 }
815
816 #[test]
817 fn chunk_emojies_test() {
818 let emojes = "😀😃😄😁😆😅😂🤣🥲😊";
819 assert_eq!(
820 vec!["😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "🥲", "😊",],
821 chunks(emojes, 1)
822 );
823 assert_eq!(
824 vec!["😀😃", "😄😁", "😆😅", "😂🤣", "🥲😊",],
825 chunks(emojes, 2)
826 );
827 assert_eq!(vec!["😀😃😄", "😁😆😅", "😂🤣🥲", "😊",], chunks(emojes, 3));
828 assert_eq!(vec!["😀😃😄😁", "😆😅😂🤣", "🥲😊",], chunks(emojes, 4));
829 assert_eq!(vec!["😀😃😄😁😆", "😅😂🤣🥲😊",], chunks(emojes, 5));
830 assert_eq!(vec!["😀😃😄😁😆😅", "😂🤣🥲😊",], chunks(emojes, 6));
831 assert_eq!(vec!["😀😃😄😁😆😅😂", "🤣🥲😊",], chunks(emojes, 7));
832 assert_eq!(vec!["😀😃😄😁😆😅😂🤣", "🥲😊",], chunks(emojes, 8));
833 assert_eq!(vec!["😀😃😄😁😆😅😂🤣🥲", "😊",], chunks(emojes, 9));
834 assert_eq!(vec!["😀😃😄😁😆😅😂🤣🥲😊"], chunks(emojes, 10));
835 assert_eq!(vec!["😀😃😄😁😆😅😂🤣🥲😊"], chunks(emojes, 11));
836 }
837}