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