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