1use papergrid::dimension::{iterable::IterGridDimension, Estimate};
7
8use crate::{
9 grid::{
10 config::{ColoredConfig, Entity, Position, SpannedConfig},
11 dimension::CompleteDimension,
12 records::{
13 vec_records::Cell, EmptyRecords, ExactRecords, IntoRecords, PeekableRecords, Records,
14 RecordsMut,
15 },
16 util::string::{get_char_width, get_string_width},
17 },
18 settings::{
19 measurement::Measurement,
20 peaker::{Peaker, PriorityNone},
21 width::Width,
22 CellOption, TableOption,
23 },
24};
25
26use super::util::get_table_total_width;
27
28#[derive(Debug, Clone)]
47pub struct Wrap<W = usize, P = PriorityNone> {
48 width: W,
49 keep_words: bool,
50 priority: P,
51}
52
53impl<W> Wrap<W> {
54 pub fn new(width: W) -> Self
56 where
57 W: Measurement<Width>,
58 {
59 Wrap {
60 width,
61 keep_words: false,
62 priority: PriorityNone::new(),
63 }
64 }
65}
66
67impl<W, P> Wrap<W, P> {
68 pub fn priority<PP: Peaker>(self, priority: PP) -> Wrap<W, PP> {
81 Wrap {
82 width: self.width,
83 keep_words: self.keep_words,
84 priority,
85 }
86 }
87
88 pub fn keep_words(mut self, on: bool) -> Self {
93 self.keep_words = on;
94 self
95 }
96}
97
98impl Wrap<(), ()> {
99 pub fn wrap(text: &str, width: usize, keeping_words: bool) -> String {
101 wrap_text(text, width, keeping_words)
102 }
103}
104
105impl<W, P, R> TableOption<R, ColoredConfig, CompleteDimension> for Wrap<W, P>
106where
107 W: Measurement<Width>,
108 P: Peaker,
109 R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
110 for<'a> &'a R: Records,
111 for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: Cell + AsRef<str>,
112{
113 fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut CompleteDimension) {
114 if records.count_rows() == 0 || records.count_columns() == 0 {
115 return;
116 }
117
118 let width = self.width.measure(&*records, cfg);
119
120 dims.estimate(&*records, cfg);
121 let widths = dims.get_widths().expect("must be found");
122
123 let total = get_table_total_width(widths, cfg);
124 if width >= total {
125 return;
126 }
127
128 let w = Wrap {
129 keep_words: self.keep_words,
130 priority: self.priority,
131 width,
132 };
133 let widths = wrap_total_width(records, cfg, widths, total, w);
134
135 dims.set_widths(widths);
136 }
137
138 fn hint_change(&self) -> Option<Entity> {
139 Some(Entity::Row(0))
142 }
143}
144
145impl<W, R, P> CellOption<R, ColoredConfig> for Wrap<W, P>
146where
147 W: Measurement<Width>,
148 R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
149 for<'a> &'a R: Records,
150 for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef<str>,
151{
152 fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
153 let count_rows = records.count_rows();
154 let count_columns = records.count_columns();
155 let max_pos = Position::new(count_rows, count_columns);
156
157 let width = self.width.measure(&*records, cfg);
158
159 for pos in entity.iter(count_rows, count_columns) {
160 if !max_pos.has_coverage(pos) {
161 continue;
162 }
163
164 let cell_width = records.get_width(pos);
165 if cell_width <= width {
166 continue;
167 }
168
169 let text = records.get_text(pos);
170 let wrapped = wrap_text(text, width, self.keep_words);
171 records.set(pos, wrapped);
172 }
173 }
174}
175
176fn wrap_total_width<R, P>(
177 records: &mut R,
178 cfg: &mut ColoredConfig,
179 widths: &[usize],
180 total: usize,
181 w: Wrap<usize, P>,
182) -> Vec<usize>
183where
184 R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
185 P: Peaker,
186 for<'a> &'a R: Records,
187 for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef<str>,
188{
189 let count_rows = records.count_rows();
190 let count_columns = records.count_columns();
191
192 let min_widths = IterGridDimension::width(EmptyRecords::new(count_rows, count_columns), cfg);
196
197 let mut widths = widths.to_vec();
198 decrease_widths(&mut widths, &min_widths, total, w.width, w.priority);
199
200 let points = get_decrease_cell_list(cfg, &widths, &min_widths, count_rows, count_columns);
201
202 for (pos, width) in points {
203 let text = records.get_text(pos);
204 let wrapped = wrap_text(text, width, w.keep_words);
205 records.set(pos, wrapped);
206 }
207
208 widths
209}
210
211fn wrap_text(text: &str, width: usize, keep_words: bool) -> String {
212 if width == 0 {
213 return String::new();
214 }
215
216 #[cfg(not(feature = "ansi"))]
217 {
218 if keep_words {
219 wrap_text_keeping_words_noansi(text, width)
220 } else {
221 wrap_text_basic(text, width)
222 }
223 }
224
225 #[cfg(feature = "ansi")]
226 {
227 use crate::util::string::{build_link, strip_osc};
228
229 let (text, url) = strip_osc(text);
230 let (prefix, suffix) = build_link(url);
231
232 if keep_words {
233 wrap_text_keeping_words(&text, width, &prefix, &suffix)
234 } else {
235 wrap_text_basic(&text, width, &prefix, &suffix)
236 }
237 }
238}
239
240#[cfg(not(feature = "ansi"))]
241fn wrap_text_basic(s: &str, width: usize) -> String {
242 const REPLACEMENT: char = '\u{FFFD}';
243
244 if width == 0 {
245 return String::new();
246 }
247
248 let mut buf = String::new();
249 let mut current_width = 0;
250 for c in s.chars() {
251 if c == '\n' {
252 buf.push('\n');
253 current_width = 0;
254 continue;
255 }
256
257 if current_width == width {
258 buf.push('\n');
259 current_width = 0;
260 }
261
262 let char_width = std::cmp::max(1, get_char_width(c));
263 let has_line_space = current_width + char_width <= width;
264 if !has_line_space {
265 let is_char_small = char_width <= width;
266 if !is_char_small {
267 let count_unknowns = width - current_width;
268 buf.extend(std::iter::repeat_n(REPLACEMENT, count_unknowns));
269 current_width += count_unknowns;
270 } else {
271 buf.push('\n');
272 buf.push(c);
273 current_width = char_width;
274 }
275 } else {
276 buf.push(c);
277 current_width += char_width;
278 }
279 }
280
281 buf
282}
283
284#[cfg(feature = "ansi")]
285fn wrap_text_basic(text: &str, width: usize, line_prefix: &str, line_suffix: &str) -> String {
286 use std::fmt::Write;
287
288 const REPLACEMENT: char = '\u{FFFD}';
289
290 if width == 0 || text.is_empty() {
291 return String::new();
292 }
293
294 let mut buf = String::with_capacity(width);
295 let mut line_width = 0;
296
297 buf.push_str(line_prefix);
298
299 for block in ansi_str::get_blocks(text) {
300 let style = block.style();
301 let text = block.text();
302 if text.is_empty() {
303 continue;
304 }
305
306 let available = width - line_width;
307 if available == 0 {
308 buf.push('\n');
309 line_width = 0;
310 }
311
312 let _ = write!(&mut buf, "{}", style.start());
313
314 for c in text.chars() {
315 if c == '\n' {
316 let _ = write!(&mut buf, "{}", style.end());
317 buf.push_str(line_suffix);
318 buf.push('\n');
319 line_width = 0;
320 buf.push_str(line_prefix);
321 let _ = write!(&mut buf, "{}", style.start());
322 continue;
323 }
324
325 let char_width = std::cmp::max(1, get_char_width(c));
326 let line_has_space = line_width + char_width <= width;
327 if line_has_space {
328 buf.push(c);
329 line_width += char_width;
330 continue;
331 }
332
333 let is_char_small = char_width <= width;
334 if is_char_small {
335 let _ = write!(&mut buf, "{}", style.end());
336 buf.push_str(line_suffix);
337 buf.push('\n');
338 line_width = 0;
339 buf.push_str(line_prefix);
340 let _ = write!(&mut buf, "{}", style.start());
341
342 buf.push(c);
343 line_width += char_width;
344 continue;
345 }
346
347 if line_width == width {
348 let _ = write!(&mut buf, "{}", style.end());
349 buf.push_str(line_suffix);
350 buf.push('\n');
351 line_width = 0;
352 buf.push_str(line_prefix);
353 let _ = write!(&mut buf, "{}", style.start());
354 }
355
356 let count_unknowns = width - line_width;
357 buf.extend(std::iter::repeat_n(REPLACEMENT, count_unknowns));
358 line_width += count_unknowns;
359 }
360
361 if line_width > 0 {
362 let _ = write!(&mut buf, "{}", style.end());
363 }
364 }
365
366 if line_width > 0 {
367 buf.push_str(line_suffix);
368 }
369
370 buf
371}
372
373fn wrap_text_keeping_words_noansi(text: &str, width: usize) -> String {
374 const REPLACEMENT: char = '\u{FFFD}';
375
376 if width == 0 || text.is_empty() {
377 return String::new();
378 }
379
380 let mut buf = String::with_capacity(width);
381 let mut line_width = 0;
382
383 for (i, word) in text.split(' ').enumerate() {
384 let line_has_space = line_width < width;
386 if i > 0 {
387 if line_has_space {
388 buf.push(' ');
389 line_width += 1;
390 } else {
391 buf.push('\n');
392 line_width = 0;
393
394 buf.push(' ');
395 line_width += 1;
396 }
397 }
398
399 let word_width = get_string_width(word);
400
401 let line_has_space = line_width + word_width <= width;
402 if line_has_space {
403 buf.push_str(word);
404 line_width += word_width;
405 continue;
406 }
407
408 let is_small_word = word_width <= width;
409 if is_small_word {
410 buf.push('\n');
411 line_width = 0;
412
413 buf.push_str(word);
414 line_width += word_width;
415 continue;
416 }
417
418 for c in word.chars() {
419 let char_width = std::cmp::max(1, get_char_width(c));
422 let line_has_space = line_width + char_width <= width;
423 if !line_has_space {
424 let is_char_small = char_width <= width;
425 if !is_char_small {
426 if line_width == width {
427 buf.push('\n');
428 line_width = 0;
429 }
430
431 let available = width - line_width;
436 buf.extend(std::iter::repeat_n(REPLACEMENT, available));
437 line_width = width;
438 continue;
439 }
440
441 buf.push('\n');
442 line_width = 0;
443 }
444
445 buf.push(c);
446 line_width += char_width;
447 }
448 }
449
450 buf
451}
452
453#[cfg(feature = "ansi")]
454fn wrap_text_keeping_words(text: &str, width: usize, prefix: &str, suffix: &str) -> String {
455 const REPLACEMENT: char = '\u{FFFD}';
456
457 struct Blocks<'a> {
458 iter: ansi_str::AnsiBlockIter<'a>,
459 last_block: Option<BlockSlice<'a>>,
460 }
461
462 struct BlockSlice<'a> {
463 block: ansi_str::AnsiBlock<'a>,
464 pos: usize,
465 }
466
467 impl<'a> Blocks<'a> {
468 fn new(text: &'a str) -> Self {
469 Self {
470 iter: ansi_str::get_blocks(text),
471 last_block: None,
472 }
473 }
474
475 fn read(&mut self, buf: &mut String, nbytes: usize) {
476 use std::fmt::Write;
477
478 debug_assert_ne!(nbytes, 0);
479
480 let mut size: usize = nbytes;
481
482 if let Some(mut b) = self.last_block.take() {
483 let text = b.block.text();
484 let slice = &text[b.pos..];
485 let slice_size = slice.len();
486
487 match slice_size.cmp(&size) {
488 std::cmp::Ordering::Less => {
489 buf.push_str(slice);
490 let _ = write!(buf, "{}", b.block.style().end());
491 size -= slice_size;
492 }
493 std::cmp::Ordering::Equal => {
494 buf.push_str(slice);
495 let _ = write!(buf, "{}", b.block.style().end());
496 return;
497 }
498 std::cmp::Ordering::Greater => {
499 let truncated = &slice[..size];
500 buf.push_str(truncated);
501 b.pos += size;
502 self.last_block = Some(b);
503 return;
504 }
505 }
506 }
507
508 for block in self.iter.by_ref() {
509 let text = block.text();
510 if text.is_empty() {
511 continue;
512 }
513
514 let text_size = text.len();
515
516 match text_size.cmp(&size) {
517 std::cmp::Ordering::Less => {
518 let _ = write!(buf, "{}", block.style().start());
519 buf.push_str(text);
520 let _ = write!(buf, "{}", block.style().end());
521 size -= text_size;
522 }
523 std::cmp::Ordering::Equal => {
524 let _ = write!(buf, "{}", block.style().start());
525 buf.push_str(text);
526 let _ = write!(buf, "{}", block.style().end());
527 return;
528 }
529 std::cmp::Ordering::Greater => {
530 let _ = write!(buf, "{}", block.style().start());
531 let slice = &text[..size];
532 buf.push_str(slice);
533 self.last_block = Some(BlockSlice { block, pos: size });
534
535 return;
536 }
537 }
538 }
539 }
540
541 fn read_char(&mut self, buf: &mut String) {
542 use std::fmt::Write;
543
544 if let Some(mut b) = self.last_block.take() {
545 let text = b.block.text();
546 let slice = &text[b.pos..];
547
548 let mut chars = slice.chars();
549 let ch = chars.next().expect("ok");
550 let ch_size = ch.len_utf8();
551
552 buf.push(ch);
553
554 if chars.next().is_none() {
555 let _ = write!(buf, "{}", b.block.style().end());
556 return;
557 } else {
558 debug_assert_ne!(ch_size, 0);
559
560 b.pos += ch_size;
561 self.last_block = Some(b);
562
563 return;
564 }
565 }
566
567 for block in self.iter.by_ref() {
568 let text = block.text();
569 if text.is_empty() {
570 continue;
571 }
572
573 let mut chars = text.chars();
574 let ch = chars.next().expect("ok");
575 let ch_size = ch.len_utf8();
576
577 let _ = write!(buf, "{}", block.style().start());
578 buf.push(ch);
579
580 if chars.next().is_none() {
581 let _ = write!(buf, "{}", block.style().end());
582 return;
583 } else {
584 debug_assert_ne!(ch_size, 0);
585
586 self.last_block = Some(BlockSlice {
587 block,
588 pos: ch_size,
589 });
590 return;
591 }
592 }
593 }
594
595 fn skip(&mut self, buf: &mut String, nbytes: usize) {
596 use std::fmt::Write;
597
598 debug_assert_ne!(nbytes, 0);
599
600 let mut size = nbytes;
601
602 if let Some(mut b) = self.last_block.take() {
603 let text = b.block.text();
604 let slice = &text[b.pos..];
605 let slice_size = slice.len();
606
607 match slice_size.cmp(&size) {
608 std::cmp::Ordering::Less => {
609 let _ = write!(buf, "{}", b.block.style().end());
610 size -= slice_size;
611 }
612 std::cmp::Ordering::Equal => {
613 let _ = write!(buf, "{}", b.block.style().end());
614 return;
615 }
616 std::cmp::Ordering::Greater => {
617 b.pos += size;
618 self.last_block = Some(b);
619 return;
620 }
621 }
622 }
623
624 for block in self.iter.by_ref() {
625 let text = block.text();
626 let text_size = text.len();
627
628 if text.is_empty() {
629 continue;
630 }
631
632 match text_size.cmp(&size) {
633 std::cmp::Ordering::Less => {
634 size -= text_size;
635 }
636 std::cmp::Ordering::Equal => {
637 return;
638 }
639 std::cmp::Ordering::Greater => {
640 let _ = write!(buf, "{}", block.style().start());
641 self.last_block = Some(BlockSlice { block, pos: size });
642 return;
643 }
644 }
645 }
646 }
647
648 fn finish(&mut self, buf: &mut String) {
649 use std::fmt::Write;
650
651 if let Some(b) = &mut self.last_block {
652 let _ = write!(buf, "{}", b.block.style().end());
653 }
654 }
655
656 fn start(&mut self, buf: &mut String) {
657 use std::fmt::Write;
658
659 if let Some(b) = &mut self.last_block {
660 let _ = write!(buf, "{}", b.block.style().start());
661 }
662 }
663 }
664
665 if width == 0 || text.is_empty() {
666 return String::new();
667 }
668
669 let stripped = ansi_str::AnsiStr::ansi_strip(text);
670 let is_simple_text = stripped.len() == text.len() && prefix.is_empty() && suffix.is_empty();
671 if is_simple_text {
672 return wrap_text_keeping_words_noansi(text, width);
673 }
674
675 let mut buf = String::with_capacity(width + prefix.len() + suffix.len());
676 let mut line_width = 0;
677 let mut blocks = Blocks::new(text);
678
679 buf.push_str(prefix);
680
681 for (i, word) in stripped.split(' ').enumerate() {
682 let word_width = get_string_width(word);
683 let word_size = word.len();
684
685 if i > 0 {
687 let line_has_space = line_width < width;
688 if line_has_space {
689 blocks.read(&mut buf, 1);
690 line_width += 1;
691 } else {
692 blocks.finish(&mut buf);
693 buf.push_str(suffix);
694 buf.push('\n');
695 buf.push_str(prefix);
696 blocks.start(&mut buf);
697 blocks.read(&mut buf, 1);
698 line_width = 1;
699 }
700 }
701
702 if word_width == 0 {
703 continue;
704 }
705
706 let line_has_space = line_width + word_width <= width;
707 if line_has_space {
708 blocks.read(&mut buf, word_size);
709 line_width += word_width;
710 continue;
711 }
712
713 let is_small_word = word_width <= width;
714 if is_small_word {
715 blocks.finish(&mut buf);
716 buf.push_str(suffix);
717 buf.push('\n');
718 buf.push_str(prefix);
719 blocks.start(&mut buf);
720 blocks.read(&mut buf, word_size);
721 line_width = word_width;
722 continue;
723 }
724
725 for c in word.chars() {
727 let char_width = std::cmp::max(1, get_char_width(c));
728 let char_size = c.len_utf8();
729
730 let line_has_space = line_width + char_width <= width;
731 if line_has_space {
732 blocks.read_char(&mut buf);
733 line_width += char_width;
734 continue;
735 }
736
737 let is_char_small = char_width <= width;
738 if is_char_small {
739 blocks.finish(&mut buf);
740 buf.push_str(suffix);
741 buf.push('\n');
742 buf.push_str(prefix);
743 blocks.start(&mut buf);
744 blocks.read_char(&mut buf);
745 line_width = char_width;
746 continue;
747 }
748
749 if line_width == width {
750 blocks.finish(&mut buf);
751 buf.push_str(suffix);
752 buf.push('\n');
753 buf.push_str(prefix);
754 blocks.start(&mut buf);
755 line_width = 0;
756 }
757
758 let available = width - line_width;
763 buf.extend(std::iter::repeat_n(REPLACEMENT, available));
764 line_width = width;
765 blocks.skip(&mut buf, char_size);
766 }
767 }
768
769 buf.push_str(suffix);
770
771 buf
772}
773
774fn decrease_widths<F>(
775 widths: &mut [usize],
776 min_widths: &[usize],
777 total_width: usize,
778 mut width: usize,
779 mut peeaker: F,
780) where
781 F: Peaker,
782{
783 let mut empty_list = 0;
784 for col in 0..widths.len() {
785 if widths[col] == 0 || widths[col] <= min_widths[col] {
786 empty_list += 1;
787 }
788 }
789
790 while width != total_width {
791 if empty_list == widths.len() {
792 break;
793 }
794
795 let col = match peeaker.peak(min_widths, widths) {
796 Some(col) => col,
797 None => break,
798 };
799
800 if widths[col] == 0 || widths[col] <= min_widths[col] {
801 continue;
802 }
803
804 widths[col] -= 1;
805
806 if widths[col] == 0 || widths[col] <= min_widths[col] {
807 empty_list += 1;
808 }
809
810 width += 1;
811 }
812}
813
814fn get_decrease_cell_list(
815 cfg: &SpannedConfig,
816 widths: &[usize],
817 min_widths: &[usize],
818 count_rows: usize,
819 count_columns: usize,
820) -> Vec<(Position, usize)> {
821 let mut points = Vec::new();
822 for col in 0..count_columns {
823 for row in 0..count_rows {
824 let pos = Position::new(row, col);
825 if !cfg.is_cell_visible(pos) {
826 continue;
827 }
828
829 let (width, width_min) = match cfg.get_column_span(pos) {
830 Some(span) => {
831 let width = (col..col + span).map(|i| widths[i]).sum::<usize>();
832 let min_width = (col..col + span).map(|i| min_widths[i]).sum::<usize>();
833 let count_borders = count_borders(cfg, col, col + span, count_columns);
834 (width + count_borders, min_width + count_borders)
835 }
836 None => (widths[col], min_widths[col]),
837 };
838
839 if width >= width_min {
840 let padding = cfg.get_padding(pos);
841 let width = width.saturating_sub(padding.left.size + padding.right.size);
842
843 points.push((pos, width));
844 }
845 }
846 }
847
848 points
849}
850
851fn count_borders(cfg: &SpannedConfig, start: usize, end: usize, count_columns: usize) -> usize {
852 (start..end)
853 .skip(1)
854 .filter(|&i| cfg.has_vertical(i, count_columns))
855 .count()
856}
857
858#[cfg(test)]
859mod tests {
860 use super::*;
861
862 #[test]
863 fn split_test() {
864 #[cfg(not(feature = "ansi"))]
865 let split = |text, width| wrap_text_basic(text, width);
866
867 #[cfg(feature = "ansi")]
868 let split = |text, width| wrap_text_basic(text, width, "", "");
869
870 assert_eq!(split("123456", 0), "");
871
872 assert_eq!(split("123456", 1), "1\n2\n3\n4\n5\n6");
873 assert_eq!(split("123456", 2), "12\n34\n56");
874 assert_eq!(split("12345", 2), "12\n34\n5");
875 assert_eq!(split("123456", 6), "123456");
876 assert_eq!(split("123456", 10), "123456");
877
878 assert_eq!(split("😳😳😳😳😳", 1), "�\n�\n�\n�\n�");
879 assert_eq!(split("😳😳😳😳😳", 2), "😳\n😳\n😳\n😳\n😳");
880 assert_eq!(split("😳😳😳😳😳", 3), "😳\n😳\n😳\n😳\n😳");
881 assert_eq!(split("😳😳😳😳😳", 6), "😳😳😳\n😳😳");
882 assert_eq!(split("😳😳😳😳😳", 20), "😳😳😳😳😳");
883
884 assert_eq!(split("😳123😳", 1), "�\n1\n2\n3\n�");
885 assert_eq!(split("😳12😳3", 1), "�\n1\n2\n�\n3");
886 }
887
888 #[test]
889 fn chunks_test() {
890 #[allow(clippy::redundant_closure)]
891 #[cfg(not(feature = "ansi"))]
892 let chunks = |text, width| wrap_text_basic(text, width);
893
894 #[cfg(feature = "ansi")]
895 let chunks = |text, width| wrap_text_basic(text, width, "", "");
896
897 assert_eq!(chunks("123456", 0), "");
898
899 assert_eq!(
900 chunks("123456", 1),
901 ["1", "2", "3", "4", "5", "6"].join("\n")
902 );
903 assert_eq!(chunks("123456", 2), ["12", "34", "56"].join("\n"));
904 assert_eq!(chunks("12345", 2), ["12", "34", "5"].join("\n"));
905
906 assert_eq!(
907 chunks("😳😳😳😳😳", 1),
908 ["�", "�", "�", "�", "�"].join("\n")
909 );
910 assert_eq!(
911 chunks("😳😳😳😳😳", 2),
912 ["😳", "😳", "😳", "😳", "😳"].join("\n")
913 );
914 assert_eq!(
915 chunks("😳😳😳😳😳", 3),
916 ["😳", "😳", "😳", "😳", "😳"].join("\n")
917 );
918 }
919
920 #[test]
921 fn split_by_line_keeping_words_test() {
922 #[cfg(not(feature = "ansi"))]
923 let split_keeping_words = |text, width| wrap_text_keeping_words_noansi(text, width);
924 #[cfg(feature = "ansi")]
925 let split_keeping_words = |text, width| wrap_text_keeping_words(text, width, "", "");
926
927 assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6");
928 assert_eq!(split_keeping_words("123456", 2), "12\n34\n56");
929 assert_eq!(split_keeping_words("12345", 2), "12\n34\n5");
930
931 assert_eq!(split_keeping_words("😳😳😳😳😳", 1), "�\n�\n�\n�\n�");
932
933 assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1");
934 }
935
936 #[cfg(feature = "ansi")]
937 #[test]
938 fn split_by_line_keeping_words_color_test() {
939 let split_keeping_words = |text, width| wrap_text_keeping_words(text, width, "", "");
940
941 let text = "\u{1b}[36mJapanese “vacancy” button\u{1b}[0m";
942 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}[36mva\u{1b}[39m\n\u{1b}[36mca\u{1b}[39m\n\u{1b}[36mnc\u{1b}[39m\n\u{1b}[36my”\u{1b}[39m\n\u{1b}[36m b\u{1b}[39m\n\u{1b}[36mut\u{1b}[39m\n\u{1b}[36mto\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m");
943 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");
944 }
945
946 #[cfg(feature = "ansi")]
947 #[test]
948 fn split_by_line_keeping_words_color_2_test() {
949 use ansi_str::AnsiStr;
950
951 let split_keeping_words = |text, width| wrap_text_keeping_words(text, width, "", "");
952
953 let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m";
954
955 assert_eq!(
956 split_keeping_words(text, 2)
957 .ansi_split("\n")
958 .collect::<Vec<_>>(),
959 [
960 "\u{1b}[37mTi\u{1b}[39m",
961 "\u{1b}[37mgr\u{1b}[39m",
962 "\u{1b}[37me \u{1b}[39m",
963 "\u{1b}[37mEc\u{1b}[39m",
964 "\u{1b}[37mua\u{1b}[39m",
965 "\u{1b}[37mdo\u{1b}[39m",
966 "\u{1b}[37mr \u{1b}[39m",
967 "\u{1b}[37m \u{1b}[39m",
968 "\u{1b}[37mOM\u{1b}[39m",
969 "\u{1b}[37mYA\u{1b}[39m",
970 "\u{1b}[37m A\u{1b}[39m",
971 "\u{1b}[37mnd\u{1b}[39m",
972 "\u{1b}[37min\u{1b}[39m",
973 "\u{1b}[37ma \u{1b}[39m",
974 "\u{1b}[37m \u{1b}[39m",
975 "\u{1b}[37m \u{1b}[39m",
976 "\u{1b}[37m38\u{1b}[39m",
977 "\u{1b}[37m24\u{1b}[39m",
978 "\u{1b}[37m90\u{1b}[39m",
979 "\u{1b}[37m99\u{1b}[39m",
980 "\u{1b}[37m99\u{1b}[39m",
981 "\u{1b}[37m \u{1b}[39m",
982 "\u{1b}[37m \u{1b}[39m",
983 "\u{1b}[37m \u{1b}[39m",
984 "\u{1b}[37mCa\u{1b}[39m",
985 "\u{1b}[37mlc\u{1b}[39m",
986 "\u{1b}[37miu\u{1b}[39m",
987 "\u{1b}[37mm \u{1b}[39m",
988 "\u{1b}[37mca\u{1b}[39m",
989 "\u{1b}[37mrb\u{1b}[39m",
990 "\u{1b}[37mon\u{1b}[39m",
991 "\u{1b}[37mat\u{1b}[39m",
992 "\u{1b}[37me \u{1b}[39m",
993 "\u{1b}[37m \u{1b}[39m",
994 "\u{1b}[37m \u{1b}[39m",
995 "\u{1b}[37m \u{1b}[39m",
996 "\u{1b}[37mCo\u{1b}[39m",
997 "\u{1b}[37mlo\u{1b}[39m",
998 "\u{1b}[37mmb\u{1b}[39m",
999 "\u{1b}[37mia\u{1b}[39m"
1000 ]
1001 );
1002
1003 assert_eq!(
1004 split_keeping_words(text, 1)
1005 .ansi_split("\n")
1006 .collect::<Vec<_>>(),
1007 [
1008 "\u{1b}[37mT\u{1b}[39m",
1009 "\u{1b}[37mi\u{1b}[39m",
1010 "\u{1b}[37mg\u{1b}[39m",
1011 "\u{1b}[37mr\u{1b}[39m",
1012 "\u{1b}[37me\u{1b}[39m",
1013 "\u{1b}[37m \u{1b}[39m",
1014 "\u{1b}[37mE\u{1b}[39m",
1015 "\u{1b}[37mc\u{1b}[39m",
1016 "\u{1b}[37mu\u{1b}[39m",
1017 "\u{1b}[37ma\u{1b}[39m",
1018 "\u{1b}[37md\u{1b}[39m",
1019 "\u{1b}[37mo\u{1b}[39m",
1020 "\u{1b}[37mr\u{1b}[39m",
1021 "\u{1b}[37m \u{1b}[39m",
1022 "\u{1b}[37m \u{1b}[39m",
1023 "\u{1b}[37m \u{1b}[39m",
1024 "\u{1b}[37mO\u{1b}[39m",
1025 "\u{1b}[37mM\u{1b}[39m",
1026 "\u{1b}[37mY\u{1b}[39m",
1027 "\u{1b}[37mA\u{1b}[39m",
1028 "\u{1b}[37m \u{1b}[39m",
1029 "\u{1b}[37mA\u{1b}[39m",
1030 "\u{1b}[37mn\u{1b}[39m",
1031 "\u{1b}[37md\u{1b}[39m",
1032 "\u{1b}[37mi\u{1b}[39m",
1033 "\u{1b}[37mn\u{1b}[39m",
1034 "\u{1b}[37ma\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}[37m \u{1b}[39m",
1039 "\u{1b}[37m \u{1b}[39m",
1040 "\u{1b}[37m3\u{1b}[39m",
1041 "\u{1b}[37m8\u{1b}[39m",
1042 "\u{1b}[37m2\u{1b}[39m",
1043 "\u{1b}[37m4\u{1b}[39m",
1044 "\u{1b}[37m9\u{1b}[39m",
1045 "\u{1b}[37m0\u{1b}[39m",
1046 "\u{1b}[37m9\u{1b}[39m",
1047 "\u{1b}[37m9\u{1b}[39m",
1048 "\u{1b}[37m9\u{1b}[39m",
1049 "\u{1b}[37m9\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}[37m \u{1b}[39m",
1055 "\u{1b}[37m \u{1b}[39m",
1056 "\u{1b}[37mC\u{1b}[39m",
1057 "\u{1b}[37ma\u{1b}[39m",
1058 "\u{1b}[37ml\u{1b}[39m",
1059 "\u{1b}[37mc\u{1b}[39m",
1060 "\u{1b}[37mi\u{1b}[39m",
1061 "\u{1b}[37mu\u{1b}[39m",
1062 "\u{1b}[37mm\u{1b}[39m",
1063 "\u{1b}[37m \u{1b}[39m",
1064 "\u{1b}[37mc\u{1b}[39m",
1065 "\u{1b}[37ma\u{1b}[39m",
1066 "\u{1b}[37mr\u{1b}[39m",
1067 "\u{1b}[37mb\u{1b}[39m",
1068 "\u{1b}[37mo\u{1b}[39m",
1069 "\u{1b}[37mn\u{1b}[39m",
1070 "\u{1b}[37ma\u{1b}[39m",
1071 "\u{1b}[37mt\u{1b}[39m",
1072 "\u{1b}[37me\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}[37m \u{1b}[39m",
1079 "\u{1b}[37m \u{1b}[39m",
1080 "\u{1b}[37mC\u{1b}[39m",
1081 "\u{1b}[37mo\u{1b}[39m",
1082 "\u{1b}[37ml\u{1b}[39m",
1083 "\u{1b}[37mo\u{1b}[39m",
1084 "\u{1b}[37mm\u{1b}[39m",
1085 "\u{1b}[37mb\u{1b}[39m",
1086 "\u{1b}[37mi\u{1b}[39m",
1087 "\u{1b}[37ma\u{1b}[39m"
1088 ]
1089 )
1090 }
1091
1092 #[cfg(feature = "ansi")]
1093 #[test]
1094 fn split_by_line_keeping_words_color_3_test() {
1095 let split = |text, width| wrap_text_keeping_words(text, width, "", "");
1096 assert_eq!(
1097 split(
1098 "\u{1b}[37m🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻\u{1b}[0m",
1099 3,
1100 ),
1101 "\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",
1102 );
1103 assert_eq!(
1104 split("\u{1b}[37mthis is a long sentence\u{1b}[0m", 7),
1105 "\u{1b}[37mthis is\u{1b}[39m\n\u{1b}[37m a long\u{1b}[39m\n\u{1b}[37m senten\u{1b}[39m\n\u{1b}[37mce\u{1b}[39m"
1106 );
1107 assert_eq!(
1108 split("\u{1b}[37mHello World\u{1b}[0m", 7),
1109 "\u{1b}[37mHello \u{1b}[39m\n\u{1b}[37mWorld\u{1b}[39m"
1110 );
1111 assert_eq!(
1112 split("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 7),
1113 "\u{1b}[37mHello \u{1b}[39m\n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m"
1114 );
1115 assert_eq!(
1116 split("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 8),
1117 "\u{1b}[37mHello \u{1b}[39m\n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m"
1118 );
1119 }
1120
1121 #[test]
1122 fn split_keeping_words_4_test() {
1123 #[cfg(feature = "ansi")]
1124 let split_keeping_words = |text, width| wrap_text_keeping_words(text, width, "", "");
1125 #[cfg(not(feature = "ansi"))]
1126 let split_keeping_words = |text, width| wrap_text_keeping_words_noansi(text, width);
1127
1128 assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78");
1129 assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78");
1130 }
1131
1132 #[cfg(feature = "ansi")]
1133 #[test]
1134 fn chunks_test_with_prefix_and_suffix() {
1135 assert_eq!(wrap_text_basic("123456", 0, "^", "$"), ["^$"; 0].join("\n"));
1136
1137 assert_eq!(
1138 wrap_text_basic("123456", 1, "^", "$"),
1139 ["^1$", "^2$", "^3$", "^4$", "^5$", "^6$"].join("\n")
1140 );
1141 assert_eq!(
1142 wrap_text_basic("123456", 2, "^", "$"),
1143 ["^12$", "^34$", "^56$"].join("\n")
1144 );
1145 assert_eq!(
1146 wrap_text_basic("12345", 2, "^", "$"),
1147 ["^12$", "^34$", "^5$"].join("\n")
1148 );
1149
1150 assert_eq!(
1151 wrap_text_basic("😳😳😳😳😳", 1, "^", "$"),
1152 ["^�$", "^�$", "^�$", "^�$", "^�$"].join("\n")
1153 );
1154 assert_eq!(
1155 wrap_text_basic("😳😳😳😳😳", 2, "^", "$"),
1156 ["^😳$", "^😳$", "^😳$", "^😳$", "^😳$"].join("\n")
1157 );
1158 assert_eq!(
1159 wrap_text_basic("😳😳😳😳😳", 3, "^", "$"),
1160 "^😳$\n^😳$\n^😳$\n^😳$\n^😳$"
1161 );
1162 }
1163
1164 #[cfg(feature = "ansi")]
1165 #[test]
1166 fn split_by_line_keeping_words_test_with_prefix_and_suffix() {
1167 assert_eq!(
1168 wrap_text_keeping_words("123456", 1, "^", "$"),
1169 "^1$\n^2$\n^3$\n^4$\n^5$\n^6$"
1170 );
1171 assert_eq!(
1172 wrap_text_keeping_words("123456", 2, "^", "$"),
1173 "^12$\n^34$\n^56$"
1174 );
1175 assert_eq!(
1176 wrap_text_keeping_words("12345", 2, "^", "$"),
1177 "^12$\n^34$\n^5$"
1178 );
1179
1180 assert_eq!(
1181 wrap_text_keeping_words("😳😳😳😳😳", 1, "^", "$"),
1182 "^�$\n^�$\n^�$\n^�$\n^�$"
1183 );
1184 }
1185
1186 #[cfg(feature = "ansi")]
1187 #[test]
1188 fn split_by_line_keeping_words_color_2_test_with_prefix_and_suffix() {
1189 use ansi_str::AnsiStr;
1190
1191 let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m";
1192
1193 assert_eq!(
1194 wrap_text_keeping_words(text, 2, "^", "$")
1195 .ansi_split("\n")
1196 .collect::<Vec<_>>(),
1197 [
1198 "^\u{1b}[37mTi\u{1b}[39m$",
1199 "^\u{1b}[37mgr\u{1b}[39m$",
1200 "^\u{1b}[37me \u{1b}[39m$",
1201 "^\u{1b}[37mEc\u{1b}[39m$",
1202 "^\u{1b}[37mua\u{1b}[39m$",
1203 "^\u{1b}[37mdo\u{1b}[39m$",
1204 "^\u{1b}[37mr \u{1b}[39m$",
1205 "^\u{1b}[37m \u{1b}[39m$",
1206 "^\u{1b}[37mOM\u{1b}[39m$",
1207 "^\u{1b}[37mYA\u{1b}[39m$",
1208 "^\u{1b}[37m A\u{1b}[39m$",
1209 "^\u{1b}[37mnd\u{1b}[39m$",
1210 "^\u{1b}[37min\u{1b}[39m$",
1211 "^\u{1b}[37ma \u{1b}[39m$",
1212 "^\u{1b}[37m \u{1b}[39m$",
1213 "^\u{1b}[37m \u{1b}[39m$",
1214 "^\u{1b}[37m38\u{1b}[39m$",
1215 "^\u{1b}[37m24\u{1b}[39m$",
1216 "^\u{1b}[37m90\u{1b}[39m$",
1217 "^\u{1b}[37m99\u{1b}[39m$",
1218 "^\u{1b}[37m99\u{1b}[39m$",
1219 "^\u{1b}[37m \u{1b}[39m$",
1220 "^\u{1b}[37m \u{1b}[39m$",
1221 "^\u{1b}[37m \u{1b}[39m$",
1222 "^\u{1b}[37mCa\u{1b}[39m$",
1223 "^\u{1b}[37mlc\u{1b}[39m$",
1224 "^\u{1b}[37miu\u{1b}[39m$",
1225 "^\u{1b}[37mm \u{1b}[39m$",
1226 "^\u{1b}[37mca\u{1b}[39m$",
1227 "^\u{1b}[37mrb\u{1b}[39m$",
1228 "^\u{1b}[37mon\u{1b}[39m$",
1229 "^\u{1b}[37mat\u{1b}[39m$",
1230 "^\u{1b}[37me \u{1b}[39m$",
1231 "^\u{1b}[37m \u{1b}[39m$",
1232 "^\u{1b}[37m \u{1b}[39m$",
1233 "^\u{1b}[37m \u{1b}[39m$",
1234 "^\u{1b}[37mCo\u{1b}[39m$",
1235 "^\u{1b}[37mlo\u{1b}[39m$",
1236 "^\u{1b}[37mmb\u{1b}[39m$",
1237 "^\u{1b}[37mia\u{1b}[39m$"
1238 ]
1239 );
1240
1241 assert_eq!(
1242 wrap_text_keeping_words(text, 1, "^", "$")
1243 .ansi_split("\n")
1244 .collect::<Vec<_>>(),
1245 [
1246 "^\u{1b}[37mT\u{1b}[39m$",
1247 "^\u{1b}[37mi\u{1b}[39m$",
1248 "^\u{1b}[37mg\u{1b}[39m$",
1249 "^\u{1b}[37mr\u{1b}[39m$",
1250 "^\u{1b}[37me\u{1b}[39m$",
1251 "^\u{1b}[37m \u{1b}[39m$",
1252 "^\u{1b}[37mE\u{1b}[39m$",
1253 "^\u{1b}[37mc\u{1b}[39m$",
1254 "^\u{1b}[37mu\u{1b}[39m$",
1255 "^\u{1b}[37ma\u{1b}[39m$",
1256 "^\u{1b}[37md\u{1b}[39m$",
1257 "^\u{1b}[37mo\u{1b}[39m$",
1258 "^\u{1b}[37mr\u{1b}[39m$",
1259 "^\u{1b}[37m \u{1b}[39m$",
1260 "^\u{1b}[37m \u{1b}[39m$",
1261 "^\u{1b}[37m \u{1b}[39m$",
1262 "^\u{1b}[37mO\u{1b}[39m$",
1263 "^\u{1b}[37mM\u{1b}[39m$",
1264 "^\u{1b}[37mY\u{1b}[39m$",
1265 "^\u{1b}[37mA\u{1b}[39m$",
1266 "^\u{1b}[37m \u{1b}[39m$",
1267 "^\u{1b}[37mA\u{1b}[39m$",
1268 "^\u{1b}[37mn\u{1b}[39m$",
1269 "^\u{1b}[37md\u{1b}[39m$",
1270 "^\u{1b}[37mi\u{1b}[39m$",
1271 "^\u{1b}[37mn\u{1b}[39m$",
1272 "^\u{1b}[37ma\u{1b}[39m$",
1273 "^\u{1b}[37m \u{1b}[39m$",
1274 "^\u{1b}[37m \u{1b}[39m$",
1275 "^\u{1b}[37m \u{1b}[39m$",
1276 "^\u{1b}[37m \u{1b}[39m$",
1277 "^\u{1b}[37m \u{1b}[39m$",
1278 "^\u{1b}[37m3\u{1b}[39m$",
1279 "^\u{1b}[37m8\u{1b}[39m$",
1280 "^\u{1b}[37m2\u{1b}[39m$",
1281 "^\u{1b}[37m4\u{1b}[39m$",
1282 "^\u{1b}[37m9\u{1b}[39m$",
1283 "^\u{1b}[37m0\u{1b}[39m$",
1284 "^\u{1b}[37m9\u{1b}[39m$",
1285 "^\u{1b}[37m9\u{1b}[39m$",
1286 "^\u{1b}[37m9\u{1b}[39m$",
1287 "^\u{1b}[37m9\u{1b}[39m$",
1288 "^\u{1b}[37m \u{1b}[39m$",
1289 "^\u{1b}[37m \u{1b}[39m$",
1290 "^\u{1b}[37m \u{1b}[39m$",
1291 "^\u{1b}[37m \u{1b}[39m$",
1292 "^\u{1b}[37m \u{1b}[39m$",
1293 "^\u{1b}[37m \u{1b}[39m$",
1294 "^\u{1b}[37mC\u{1b}[39m$",
1295 "^\u{1b}[37ma\u{1b}[39m$",
1296 "^\u{1b}[37ml\u{1b}[39m$",
1297 "^\u{1b}[37mc\u{1b}[39m$",
1298 "^\u{1b}[37mi\u{1b}[39m$",
1299 "^\u{1b}[37mu\u{1b}[39m$",
1300 "^\u{1b}[37mm\u{1b}[39m$",
1301 "^\u{1b}[37m \u{1b}[39m$",
1302 "^\u{1b}[37mc\u{1b}[39m$",
1303 "^\u{1b}[37ma\u{1b}[39m$",
1304 "^\u{1b}[37mr\u{1b}[39m$",
1305 "^\u{1b}[37mb\u{1b}[39m$",
1306 "^\u{1b}[37mo\u{1b}[39m$",
1307 "^\u{1b}[37mn\u{1b}[39m$",
1308 "^\u{1b}[37ma\u{1b}[39m$",
1309 "^\u{1b}[37mt\u{1b}[39m$",
1310 "^\u{1b}[37me\u{1b}[39m$",
1311 "^\u{1b}[37m \u{1b}[39m$",
1312 "^\u{1b}[37m \u{1b}[39m$",
1313 "^\u{1b}[37m \u{1b}[39m$",
1314 "^\u{1b}[37m \u{1b}[39m$",
1315 "^\u{1b}[37m \u{1b}[39m$",
1316 "^\u{1b}[37m \u{1b}[39m$",
1317 "^\u{1b}[37m \u{1b}[39m$",
1318 "^\u{1b}[37mC\u{1b}[39m$",
1319 "^\u{1b}[37mo\u{1b}[39m$",
1320 "^\u{1b}[37ml\u{1b}[39m$",
1321 "^\u{1b}[37mo\u{1b}[39m$",
1322 "^\u{1b}[37mm\u{1b}[39m$",
1323 "^\u{1b}[37mb\u{1b}[39m$",
1324 "^\u{1b}[37mi\u{1b}[39m$",
1325 "^\u{1b}[37ma\u{1b}[39m$"
1326 ]
1327 )
1328 }
1329
1330 #[cfg(feature = "ansi")]
1331 #[test]
1332 fn chunks_wrap_2() {
1333 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";
1334 assert_eq!(
1335 wrap_text_basic(text, 30, "", ""),
1336 [
1337 "\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",
1338 "\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",
1339 "\u{1b}[42mDebian\u{1b}[49m\u{1b}[43mDebian\u{1b}[49m\u{1b}[44mDebian\u{1b}[49m",
1340 ].join("\n")
1341 );
1342 }
1343
1344 #[cfg(feature = "ansi")]
1345 #[test]
1346 fn chunks_wrap_3() {
1347 let text = "\u{1b}[37mCreate bytes from the \u{1b}[0m\u{1b}[7;34marg\u{1b}[0m\u{1b}[37muments.\u{1b}[0m";
1348
1349 assert_eq!(
1350 wrap_text_basic(text, 22, "", ""),
1351 [
1352 "\u{1b}[37mCreate bytes from the \u{1b}[39m",
1353 "\u{1b}[7m\u{1b}[34marg\u{1b}[27m\u{1b}[39m\u{1b}[37muments.\u{1b}[39m"
1354 ]
1355 .join("\n")
1356 );
1357 }
1358
1359 #[cfg(feature = "ansi")]
1360 #[test]
1361 fn chunks_wrap_3_keeping_words() {
1362 let text = "\u{1b}[37mCreate bytes from the \u{1b}[0m\u{1b}[7;34marg\u{1b}[0m\u{1b}[37muments.\u{1b}[0m";
1363
1364 assert_eq!(
1365 wrap_text_keeping_words(text, 22, "", ""),
1366 "\u{1b}[37mCreate bytes from the \u{1b}[39m\n\u{1b}[7m\u{1b}[34marg\u{1b}[27m\u{1b}[39m\u{1b}[37muments.\u{1b}[39m"
1367 );
1368 }
1369
1370 #[cfg(feature = "ansi")]
1371 #[test]
1372 fn chunks_wrap_4() {
1373 let text = "\u{1b}[37mReturns the floor of a number (l\u{1b}[0m\u{1b}[41;37marg\u{1b}[0m\u{1b}[37mest integer less than or equal to that number).\u{1b}[0m";
1374
1375 assert_eq!(
1376 wrap_text_basic(text, 10, "", ""),
1377 [
1378 "\u{1b}[37mReturns th\u{1b}[39m",
1379 "\u{1b}[37me floor of\u{1b}[39m",
1380 "\u{1b}[37m a number \u{1b}[39m",
1381 "\u{1b}[37m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest i\u{1b}[39m",
1382 "\u{1b}[37mnteger les\u{1b}[39m",
1383 "\u{1b}[37ms than or \u{1b}[39m",
1384 "\u{1b}[37mequal to t\u{1b}[39m",
1385 "\u{1b}[37mhat number\u{1b}[39m",
1386 "\u{1b}[37m).\u{1b}[39m",
1387 ].join("\n")
1388 );
1389 }
1390
1391 #[cfg(feature = "ansi")]
1392 #[test]
1393 fn chunks_wrap_4_keeping_words() {
1394 let text = "\u{1b}[37mReturns the floor of a number (l\u{1b}[0m\u{1b}[41;37marg\u{1b}[0m\u{1b}[37mest integer less than or equal to that number).\u{1b}[0m";
1395 assert_eq!(
1396 wrap_text_keeping_words(text, 10, "", ""),
1397 concat!(
1398 "\u{1b}[37mReturns \u{1b}[39m\n",
1399 "\u{1b}[37mthe floor \u{1b}[39m\n",
1400 "\u{1b}[37mof a \u{1b}[39m\n",
1401 "\u{1b}[37mnumber \u{1b}[39m\n",
1402 "\u{1b}[37m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m\n",
1403 "\u{1b}[37minteger \u{1b}[39m\n",
1404 "\u{1b}[37mless than \u{1b}[39m\n",
1405 "\u{1b}[37mor equal \u{1b}[39m\n",
1406 "\u{1b}[37mto that \u{1b}[39m\n",
1407 "\u{1b}[37mnumber).\u{1b}[39m",
1408 )
1409 );
1410 }
1411
1412 #[test]
1413 fn chunks_wrap_5_keeping_words() {
1414 #[cfg(feature = "ansi")]
1415 let split_keeping_words = |text, width| wrap_text_keeping_words(text, width, "", "");
1416 #[cfg(not(feature = "ansi"))]
1417 let split_keeping_words = |text, width| wrap_text_keeping_words_noansi(text, width);
1418
1419 let text = "修复 zlib 软件包中 CMake 配置不一致的问题,该问题先前导致部分软件无法正常构建";
1420 assert_eq!(
1421 split_keeping_words(text, 44),
1422 "修复 zlib 软件包中 CMake 配置不一致的问题,\n该问题先前导致部分软件无法正常构建"
1423 );
1424 }
1425
1426 #[test]
1427 fn chunks_chinese_0() {
1428 #[cfg(feature = "ansi")]
1429 let split_keeping_words = |text, width| wrap_text_keeping_words(text, width, "", "");
1430 #[cfg(not(feature = "ansi"))]
1431 let split_keeping_words = |text, width| wrap_text_keeping_words_noansi(text, width);
1432
1433 let text = "(公司{ 名称:\"腾讯科技(深圳)有限公司\",成立时间:\"1998年11月\"}";
1434 assert_eq!(
1435 split_keeping_words(text, 40),
1436 concat!(
1437 "(公司{ 名称:\"腾讯科技(深圳)有限公司\",\n",
1438 "成立时间:\"1998年11月\"}",
1439 ),
1440 );
1441 }
1442
1443 #[test]
1444 fn chunks_keeping_chinese_0() {
1445 #[cfg(feature = "ansi")]
1446 let split_keeping_words = |text, width| wrap_text_keeping_words(text, width, "", "");
1447 #[cfg(not(feature = "ansi"))]
1448 let split_keeping_words = |text, width| wrap_text_keeping_words_noansi(text, width);
1449
1450 let text = "(公司{ 名称:\"腾讯科技(深圳)有限公司\",成立时间:\"1998年11月\"}";
1451 assert_eq!(
1452 split_keeping_words(text, 40),
1453 concat!(
1454 "(公司{ 名称:\"腾讯科技(深圳)有限公司\",\n",
1455 "成立时间:\"1998年11月\"}",
1456 ),
1457 );
1458 }
1459}