1use std::sync::Arc;
26
27use crate::console::{Console, ConsoleOptions, Justify, Overflow};
28use crate::measure::Measurement;
29use crate::protocol::{LineRenderable, Renderable};
30use crate::r#box::{Box as BoxSet, RowLevel, HEAVY_HEAD};
31use crate::segment::Segment;
32use crate::style::{Style, StyleType};
33use crate::text::Text;
34
35struct Column {
37 header: Cell,
38 footer: Cell,
40 footer_fill: Option<Style>,
43 vertical: crate::align::VerticalAlign,
45 highlight: Option<bool>,
48 justify: Justify,
49 width: Option<usize>,
51 style: Style,
53 header_content_style: Option<Style>,
57 header_fill: Option<Style>,
61 ratio: Option<usize>,
64 min_width: Option<usize>,
66 max_width: Option<usize>,
69 no_wrap: bool,
71 overflow: Overflow,
74}
75
76impl Column {
77 fn new(header: Cell, justify: Justify) -> Self {
79 Column {
80 header,
81 footer: Cell::Markup(String::new()),
82 footer_fill: None,
83 highlight: None,
84 vertical: crate::align::VerticalAlign::Top,
85 justify,
86 width: None,
87 style: Style::new(),
88 header_content_style: None,
89 header_fill: None,
90 ratio: None,
91 min_width: None,
92 max_width: None,
93 no_wrap: false,
94 overflow: Overflow::Ellipsis,
95 }
96 }
97}
98
99struct Row {
102 cells: Vec<Cell>,
103 style: Option<StyleType>,
105 end_section: bool,
107}
108
109#[derive(Clone)]
112enum RowKind {
113 Header,
114 Body(Style),
115 Footer,
116}
117
118#[derive(Clone)]
121pub enum Cell {
122 Text(Text),
125 Markup(String),
130 Renderable(Arc<dyn Renderable + Send + Sync>),
133}
134
135impl From<Text> for Cell {
136 fn from(text: Text) -> Self {
137 Cell::Text(text)
138 }
139}
140
141impl From<&str> for Cell {
143 fn from(text: &str) -> Self {
144 Cell::Markup(text.to_string())
145 }
146}
147
148impl From<String> for Cell {
149 fn from(text: String) -> Self {
150 Cell::Markup(text)
151 }
152}
153
154impl From<&String> for Cell {
155 fn from(text: &String) -> Self {
156 Cell::Markup(text.clone())
157 }
158}
159
160impl Cell {
161 pub(crate) fn to_text(&self, console: &Console, highlight: Option<bool>) -> Option<Text> {
164 match self {
165 Cell::Text(text) => Some(text.clone()),
166 Cell::Markup(markup) => Some(console.render_str(markup, highlight)),
167 Cell::Renderable(_) => None,
168 }
169 }
170
171 pub(crate) fn measure_cell(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
175 match self {
176 Cell::Text(text) => Measurement::get(console, options, text),
177 Cell::Markup(markup) if !markup.contains(['[', ':']) => {
180 if options.max_width < 1 {
181 return Measurement::new(0, 0);
182 }
183 let (minimum, maximum) = crate::text::measure_plain(markup);
184 let width = Measurement::new(minimum, maximum)
185 .normalize()
186 .with_maximum(options.max_width);
187 if width.maximum < 1 {
188 Measurement::new(0, 0)
189 } else {
190 width.normalize()
191 }
192 }
193 Cell::Markup(markup) => {
194 Measurement::get(console, options, &console.render_str(markup, Some(false)))
195 }
196 Cell::Renderable(renderable) => Measurement::get(console, options, renderable.as_ref()),
197 }
198 }
199}
200
201impl Default for Cell {
202 fn default() -> Self {
203 Cell::Text(Text::default())
204 }
205}
206
207#[derive(Clone, Debug)]
210pub struct ColumnOptions {
211 pub justify: Justify,
213 pub width: Option<usize>,
215 pub min_width: Option<usize>,
217 pub max_width: Option<usize>,
219 pub ratio: Option<usize>,
221 pub no_wrap: bool,
223 pub overflow: Overflow,
225 pub style: Style,
227}
228
229impl Default for ColumnOptions {
230 fn default() -> Self {
231 ColumnOptions {
232 justify: Justify::Left,
233 width: None,
234 min_width: None,
235 max_width: None,
236 ratio: None,
237 no_wrap: false,
238 overflow: Overflow::Ellipsis,
239 style: Style::new(),
240 }
241 }
242}
243
244pub struct Table {
246 columns: Vec<Column>,
247 rows: Vec<Row>,
248 box_set: BoxSet,
249 no_box: bool,
251 show_header: bool,
252 show_lines: bool,
253 show_edge: bool,
254 pad_edge: bool,
255 collapse_padding: bool,
256 expand: bool,
257 title: Option<Cell>,
259 caption: Option<Cell>,
260 padding: (usize, usize, usize, usize),
261 header_style: StyleType,
263 footer_style: StyleType,
265 border_style: Style,
266 style: Style,
267 highlight: bool,
269 width: Option<usize>,
271 min_width: Option<usize>,
273 show_footer: bool,
274 leading: usize,
276 row_styles: Vec<StyleType>,
278 title_style: Option<StyleType>,
280 caption_style: Option<StyleType>,
281 title_justify: Justify,
282 caption_justify: Justify,
283 safe_box: Option<bool>,
286}
287
288impl Default for Table {
289 fn default() -> Self {
290 Table {
291 columns: Vec::new(),
292 rows: Vec::new(),
293 box_set: HEAVY_HEAD,
294 no_box: false,
295 show_header: true,
296 show_lines: false,
297 show_edge: true,
298 pad_edge: true,
299 collapse_padding: false,
300 expand: false,
301 title: None,
302 caption: None,
303 padding: (0, 1, 0, 1),
304 header_style: StyleType::Name("table.header".to_string()),
305 footer_style: StyleType::Name("table.footer".to_string()),
306 border_style: Style::new(),
307 style: Style::new(),
308 highlight: false,
309 width: None,
310 min_width: None,
311 show_footer: false,
312 leading: 0,
313 row_styles: Vec::new(),
314 title_style: None,
315 caption_style: None,
316 title_justify: Justify::Center,
317 caption_justify: Justify::Center,
318 safe_box: None,
319 }
320 }
321}
322
323impl Table {
324 pub fn new() -> Self {
325 Table::default()
326 }
327
328 pub fn grid() -> Self {
332 Table {
333 no_box: true,
334 show_header: false,
335 show_edge: false,
336 pad_edge: false,
337 collapse_padding: true,
338 padding: (0, 0, 0, 0),
339 ..Table::default()
340 }
341 }
342
343 pub fn without_box(mut self) -> Self {
345 self.no_box = true;
346 self
347 }
348
349 pub fn padding(mut self, top: usize, right: usize, bottom: usize, left: usize) -> Self {
351 self.padding = (top, right, bottom, left);
352 self
353 }
354
355 pub fn box_set(mut self, box_set: BoxSet) -> Self {
357 self.box_set = box_set;
358 self
359 }
360
361 pub fn border_style(mut self, style: Style) -> Self {
364 self.border_style = style;
365 self
366 }
367
368 pub fn show_header(mut self, show: bool) -> Self {
370 self.show_header = show;
371 self
372 }
373
374 pub fn expand(mut self, expand: bool) -> Self {
376 self.expand = expand;
377 self
378 }
379
380 pub fn show_lines(mut self, show: bool) -> Self {
382 self.show_lines = show;
383 self
384 }
385
386 pub fn show_edge(mut self, show: bool) -> Self {
389 self.show_edge = show;
390 self
391 }
392
393 pub fn pad_edge(mut self, pad: bool) -> Self {
396 self.pad_edge = pad;
397 self
398 }
399
400 pub fn collapse_padding(mut self, collapse: bool) -> Self {
403 self.collapse_padding = collapse;
404 self
405 }
406
407 pub fn style(mut self, style: Style) -> Self {
411 self.style = style;
412 self
413 }
414
415 pub fn highlight(mut self, highlight: bool) -> Self {
419 self.highlight = highlight;
420 self
421 }
422
423 fn cell_padding(&self, index: usize, ncols: usize) -> (usize, usize) {
426 let (_, pr, _, pl) = self.padding;
427 let mut left = if self.collapse_padding && index > 0 {
430 pl.saturating_sub(pr)
431 } else {
432 pl
433 };
434 let mut right = pr;
435 if !self.pad_edge && index == 0 {
437 left = 0;
438 }
439 if !self.pad_edge && index + 1 == ncols {
440 right = 0;
441 }
442 (left, right)
443 }
444
445 pub fn title(mut self, title: impl Into<String>) -> Self {
448 self.title = Some(Cell::Markup(title.into()));
449 self
450 }
451
452 pub fn caption(mut self, caption: impl Into<String>) -> Self {
454 self.caption = Some(Cell::Markup(caption.into()));
455 self
456 }
457
458 pub fn title_text(mut self, title: Text) -> Self {
461 self.title = Some(Cell::Text(title));
462 self
463 }
464
465 pub fn caption_text(mut self, caption: Text) -> Self {
467 self.caption = Some(Cell::Text(caption));
468 self
469 }
470
471 pub fn title_style(mut self, style: impl Into<StyleType>) -> Self {
474 self.title_style = Some(style.into());
475 self
476 }
477
478 pub fn caption_style(mut self, style: impl Into<StyleType>) -> Self {
481 self.caption_style = Some(style.into());
482 self
483 }
484
485 pub fn title_justify(mut self, justify: Justify) -> Self {
487 self.title_justify = justify;
488 self
489 }
490
491 pub fn caption_justify(mut self, justify: Justify) -> Self {
493 self.caption_justify = justify;
494 self
495 }
496
497 pub fn header_style(mut self, style: impl Into<StyleType>) -> Self {
500 self.header_style = style.into();
501 self
502 }
503
504 pub fn footer_style(mut self, style: impl Into<StyleType>) -> Self {
507 self.footer_style = style.into();
508 self
509 }
510
511 pub fn width(mut self, width: Option<usize>) -> Self {
514 self.width = width;
515 self
516 }
517
518 pub fn min_width(mut self, min_width: Option<usize>) -> Self {
520 self.min_width = min_width;
521 self
522 }
523
524 pub fn show_footer(mut self, show: bool) -> Self {
527 self.show_footer = show;
528 self
529 }
530
531 pub fn leading(mut self, leading: usize) -> Self {
534 self.leading = leading;
535 self
536 }
537
538 pub fn row_styles(mut self, styles: Vec<StyleType>) -> Self {
540 self.row_styles = styles;
541 self
542 }
543
544 pub fn safe_box(mut self, safe_box: Option<bool>) -> Self {
547 self.safe_box = safe_box;
548 self
549 }
550
551 pub fn is_expand(&self) -> bool {
554 self.expand || self.width.is_some()
555 }
556
557 pub fn row_count(&self) -> usize {
559 self.rows.len()
560 }
561
562 pub fn column_count(&self) -> usize {
564 self.columns.len()
565 }
566
567 pub fn get_row_style(&self, console: &Console, index: usize) -> Style {
570 let theme = console.theme();
571 let mut style = Style::new();
572 if !self.row_styles.is_empty() {
573 style = style
574 .combine(&theme.get_style_or_null(&self.row_styles[index % self.row_styles.len()]));
575 }
576 if let Some(row_style) = self.rows.get(index).and_then(|row| row.style.as_ref()) {
577 style = style.combine(&theme.get_style_or_null(row_style));
578 }
579 style
580 }
581
582 pub fn add_section(&mut self) -> &mut Self {
585 if let Some(row) = self.rows.last_mut() {
586 row.end_section = true;
587 }
588 self
589 }
590
591 pub fn add_column(&mut self, header: impl Into<String>) -> &mut Self {
594 self.add_column_justify(header, Justify::Left)
595 }
596
597 pub fn add_column_justify(&mut self, header: impl Into<String>, justify: Justify) -> &mut Self {
600 self.add_column_text(Text::default(), justify);
601 if let Some(column) = self.columns.last_mut() {
602 column.header = Cell::Markup(header.into());
603 }
604 self
605 }
606
607 pub fn add_column_text(&mut self, header: Text, justify: Justify) -> &mut Self {
612 self.columns.push(Column::new(Cell::Text(header), justify));
613 self
614 }
615
616 pub fn add_column_cell(&mut self, header: Cell, justify: Justify) -> &mut Self {
619 self.columns.push(Column::new(header, justify));
620 self
621 }
622
623 pub fn column_footer(&mut self, footer: impl Into<Cell>) -> &mut Self {
626 if let Some(column) = self.columns.last_mut() {
627 column.footer = footer.into();
628 }
629 self
630 }
631
632 pub fn column_footer_fill(&mut self, style: Style) -> &mut Self {
635 if let Some(column) = self.columns.last_mut() {
636 column.footer_fill = Some(style);
637 }
638 self
639 }
640
641 pub fn add_column_with(&mut self, header: Text, options: ColumnOptions) -> &mut Self {
644 self.columns.push(Column {
645 width: options.width,
646 style: options.style,
647 ratio: options.ratio,
648 min_width: options.min_width,
649 max_width: options.max_width,
650 no_wrap: options.no_wrap,
651 overflow: options.overflow,
652 ..Column::new(Cell::Text(header), options.justify)
653 });
654 self
655 }
656
657 pub fn column_vertical(&mut self, vertical: crate::align::VerticalAlign) -> &mut Self {
662 if let Some(column) = self.columns.last_mut() {
663 column.vertical = vertical;
664 }
665 self
666 }
667
668 pub fn column_width(&mut self, width: usize) -> &mut Self {
672 if let Some(column) = self.columns.last_mut() {
673 column.width = Some(width);
674 }
675 self
676 }
677
678 pub fn column_ratio(&mut self, ratio: usize) -> &mut Self {
682 if let Some(column) = self.columns.last_mut() {
683 column.ratio = Some(ratio);
684 }
685 self
686 }
687
688 pub fn column_min_width(&mut self, min_width: usize) -> &mut Self {
691 if let Some(column) = self.columns.last_mut() {
692 column.min_width = Some(min_width);
693 }
694 self
695 }
696
697 pub fn column_max_width(&mut self, max_width: usize) -> &mut Self {
700 if let Some(column) = self.columns.last_mut() {
701 column.max_width = Some(max_width);
702 }
703 self
704 }
705
706 pub fn column_style(&mut self, style: Style) -> &mut Self {
709 if let Some(column) = self.columns.last_mut() {
710 column.style = style;
711 }
712 self
713 }
714
715 pub fn column_header_style(&mut self, style: Style) -> &mut Self {
719 if let Some(column) = self.columns.last_mut() {
720 column.header_content_style = Some(style);
721 }
722 self
723 }
724
725 pub fn column_header_fill(&mut self, style: Style) -> &mut Self {
729 if let Some(column) = self.columns.last_mut() {
730 column.header_fill = Some(style);
731 }
732 self
733 }
734
735 pub fn column_overflow(&mut self, overflow: Overflow) -> &mut Self {
738 if let Some(column) = self.columns.last_mut() {
739 column.overflow = overflow;
740 }
741 self
742 }
743
744 pub fn column_highlight(&mut self, highlight: bool) -> &mut Self {
747 if let Some(column) = self.columns.last_mut() {
748 column.highlight = Some(highlight);
749 }
750 self
751 }
752
753 pub fn column_no_wrap(&mut self) -> &mut Self {
756 if let Some(column) = self.columns.last_mut() {
757 column.no_wrap = true;
758 }
759 self
760 }
761
762 pub fn add_row(&mut self, cells: &[&str]) -> &mut Self {
766 self.push_row(
767 cells
768 .iter()
769 .map(|s| Cell::Markup((*s).to_string()))
770 .collect(),
771 None,
772 false,
773 )
774 }
775
776 pub fn add_row_with(
780 &mut self,
781 cells: Vec<Cell>,
782 style: Option<StyleType>,
783 end_section: bool,
784 ) -> &mut Self {
785 self.push_row(cells, style, end_section)
786 }
787
788 fn push_row(
791 &mut self,
792 cells: Vec<Cell>,
793 style: Option<StyleType>,
794 end_section: bool,
795 ) -> &mut Self {
796 while self.columns.len() < cells.len() {
797 let mut column = Column::new(Cell::Markup(String::new()), Justify::Left);
798 column.highlight = Some(self.highlight);
799 self.columns.push(column);
800 }
801 self.rows.push(Row {
802 cells,
803 style,
804 end_section,
805 });
806 self
807 }
808
809 pub fn add_row_text(&mut self, cells: Vec<Text>) -> &mut Self {
813 self.push_row(cells.into_iter().map(Cell::Text).collect(), None, false)
814 }
815
816 pub fn add_row_cells(&mut self, cells: Vec<Cell>) -> &mut Self {
818 self.push_row(cells, None, false)
819 }
820
821 fn extra_width(&self) -> usize {
824 if self.no_box {
825 0
826 } else {
827 (if self.show_edge { 2 } else { 0 }) + self.columns.len().saturating_sub(1)
828 }
829 }
830
831 fn padding_width(&self, index: usize) -> usize {
835 let (_, mut pad_right, _, mut pad_left) = self.padding;
836 if self.collapse_padding {
837 pad_left = 0;
838 }
839 if !self.pad_edge {
840 if index == 0 {
841 pad_left = 0;
842 }
843 if index + 1 == self.columns.len() {
844 pad_right = 0;
845 }
846 }
847 pad_left + pad_right
848 }
849
850 fn measure_padded_cell(
854 &self,
855 console: &Console,
856 options: &ConsoleOptions,
857 cell: &Cell,
858 (left, right): (usize, usize),
859 ) -> Measurement {
860 let max_width = options.max_width;
861 if max_width < 1 {
862 return Measurement::new(0, 0);
863 }
864 let (top, pr, bottom, pl) = self.padding;
865 if top == 0 && pr == 0 && bottom == 0 && pl == 0 {
866 return cell.measure_cell(console, options);
867 }
868 let extra_width = left + right;
869 let width = if max_width < extra_width + 1 {
870 Measurement::new(max_width, max_width)
871 } else {
872 let inner = cell.measure_cell(console, options);
873 Measurement::new(inner.minimum + extra_width, inner.maximum + extra_width)
874 .with_maximum(max_width)
875 };
876 let width = width.normalize().with_maximum(max_width);
878 if width.maximum < 1 {
879 Measurement::new(0, 0)
880 } else {
881 width.normalize()
882 }
883 }
884
885 fn measure_column(
890 &self,
891 console: &Console,
892 options: &ConsoleOptions,
893 index: usize,
894 ) -> Measurement {
895 let max_width = options.max_width;
896 if max_width < 1 {
897 return Measurement::new(0, 0);
898 }
899 let column = &self.columns[index];
900 let padding_width = self.padding_width(index);
901 if let Some(width) = column.width {
902 return Measurement::new(width + padding_width, width + padding_width)
904 .with_maximum(max_width);
905 }
906 let padding = self.cell_padding(index, self.columns.len());
909 let empty = Cell::Markup(String::new());
910 let header = self.show_header.then_some(&column.header);
911 let body = self
912 .rows
913 .iter()
914 .map(|row| row.cells.get(index).unwrap_or(&empty));
915 let footer = self.show_footer.then_some(&column.footer);
916 let mut measured = false;
917 let (mut minimum, mut maximum) = (0, 0);
918 for cell in header.into_iter().chain(body).chain(footer) {
919 let width = self.measure_padded_cell(console, options, cell, padding);
920 minimum = minimum.max(width.minimum);
921 maximum = maximum.max(width.maximum);
922 measured = true;
923 }
924 let measurement = if measured {
925 Measurement::new(minimum, maximum)
926 } else {
927 Measurement::new(1, max_width)
928 }
929 .with_maximum(max_width);
930 measurement.clamp(
931 column.min_width.map(|width| width + padding_width),
932 column.max_width.map(|width| width + padding_width),
933 )
934 }
935
936 fn column_widths(
940 &self,
941 console: &Console,
942 options: &ConsoleOptions,
943 available: usize,
944 ) -> Vec<usize> {
945 let options = &options.update_width(available);
946 let max_width = available as i64;
947 let extra_width = self.extra_width() as i64;
948 let maximums: Vec<i64> = (0..self.columns.len())
951 .map(|index| self.measure_column(console, options, index).maximum as i64)
952 .collect();
953 let mut widths: Vec<i64> = maximums.iter().map(|&width| width.max(1)).collect();
954
955 if self.is_expand() {
959 let ratios: Vec<i64> = self
960 .columns
961 .iter()
962 .filter(|c| c.ratio.is_some())
963 .map(|c| i64::try_from(c.ratio.unwrap()).unwrap_or(i64::MAX))
964 .collect();
965 if ratios.iter().any(|&r| r > 0) {
966 let fixed_widths: Vec<i64> = maximums
967 .iter()
968 .zip(&self.columns)
969 .map(|(&w, c)| if c.ratio.is_some() { 0 } else { w })
970 .collect();
971 let flex_minimum: Vec<i64> = self
972 .columns
973 .iter()
974 .enumerate()
975 .filter(|(_, c)| c.ratio.is_some())
976 .map(|(index, c)| (c.width.unwrap_or(1) + self.padding_width(index)) as i64)
977 .collect();
978 let flexible_width = max_width - fixed_widths.iter().sum::<i64>();
979 let flex_widths = ratio_distribute(flexible_width, &ratios, Some(&flex_minimum));
980 let mut iter_flex = flex_widths.into_iter();
981 for (index, column) in self.columns.iter().enumerate() {
982 if column.ratio.is_some() {
983 widths[index] = fixed_widths[index] + iter_flex.next().unwrap_or(0);
984 }
985 }
986 }
987 }
988
989 let mut table_width: i64 = widths.iter().sum();
990 if table_width > max_width {
991 let wrapable: Vec<bool> = self
994 .columns
995 .iter()
996 .map(|c| c.width.is_none() && !c.no_wrap)
997 .collect();
998 widths = collapse_widths(widths, &wrapable, max_width);
999 table_width = widths.iter().sum();
1000 if table_width > max_width {
1003 let excess = table_width - max_width;
1004 let ratios = vec![1i64; widths.len()];
1005 widths = ratio_reduce(excess, &ratios, &widths, &widths);
1006 table_width = widths.iter().sum();
1007 }
1008 widths = widths
1013 .iter()
1014 .enumerate()
1015 .map(|(index, &width)| {
1016 self.measure_column(
1017 console,
1018 &options.update_width(width.max(0) as usize),
1019 index,
1020 )
1021 .maximum as i64
1022 })
1023 .collect();
1024 }
1025
1026 let min_width = self.min_width.map(|width| width as i64);
1030 if (table_width < max_width && self.is_expand())
1031 || min_width.is_some_and(|min_width| table_width < min_width - extra_width)
1032 {
1033 let target = match min_width {
1034 None => max_width,
1035 Some(min_width) => (min_width - extra_width).min(max_width),
1036 };
1037 if widths.iter().sum::<i64>() > 0 {
1038 let pad = ratio_distribute(target - table_width, &widths, None);
1039 for (width, extra) in widths.iter_mut().zip(pad) {
1040 *width += extra;
1041 }
1042 }
1043 }
1044 widths.into_iter().map(|w| w.max(0) as usize).collect()
1045 }
1046
1047 fn cell_padding_fitted(&self, index: usize, ncols: usize, rendered: usize) -> (usize, usize) {
1057 let (mut pl, mut pr) = self.cell_padding(index, ncols);
1058 while pl + pr > rendered {
1059 if pr > pl {
1060 pr -= 1;
1061 } else if pl > 0 {
1062 pl -= 1;
1063 } else {
1064 break;
1065 }
1066 }
1067 (pl, pr)
1068 }
1069
1070 fn cell_style(&self, console: &Console, index: usize, kind: &RowKind) -> Style {
1075 let column = self.columns.get(index);
1076 match kind {
1077 RowKind::Header => {
1078 let base = console.theme().get_style_or_null(&self.header_style);
1079 match column.and_then(|c| c.header_fill.as_ref()) {
1080 Some(fill) => base.combine(fill),
1081 None => base,
1082 }
1083 }
1084 RowKind::Footer => {
1085 let base = console.theme().get_style_or_null(&self.footer_style);
1086 match column.and_then(|c| c.footer_fill.as_ref()) {
1087 Some(fill) => base.combine(fill),
1088 None => base,
1089 }
1090 }
1091 RowKind::Body(row_style) => column
1092 .map(|c| c.style.clone())
1093 .unwrap_or_default()
1094 .combine(row_style),
1095 }
1096 }
1097
1098 fn vertical_padding(&self, first_row: bool, last_row: bool) -> (usize, usize) {
1107 let (mut top, _, mut bottom, _) = self.padding;
1108 if self.collapse_padding && !last_row {
1109 bottom = top.saturating_sub(bottom);
1110 }
1111 if !self.pad_edge {
1112 if first_row {
1113 top = 0;
1114 }
1115 if last_row {
1116 bottom = 0;
1117 }
1118 }
1119 (top, bottom)
1120 }
1121
1122 fn pad_cell_lines(
1123 &self,
1124 lines: Vec<Vec<Segment>>,
1125 width: usize,
1126 (cpl, cpr): (usize, usize),
1127 (pt, pb): (usize, usize),
1128 style: &Style,
1129 ) -> Vec<Vec<Segment>> {
1130 let cell_fill = Some(style.clone());
1131 let cell_width = cpl + width + cpr;
1132 if cell_width == 0 {
1136 return Vec::new();
1137 }
1138 let blank = || vec![Segment::new(" ".repeat(cell_width), cell_fill.clone())];
1139 let mut padded_lines: Vec<Vec<Segment>> = Vec::new();
1140 for _ in 0..pt {
1141 padded_lines.push(blank());
1142 }
1143 for line in &lines {
1144 let mut row = Vec::new();
1145 if cpl > 0 {
1146 row.push(Segment::new(" ".repeat(cpl), cell_fill.clone()));
1147 }
1148 row.extend(Segment::adjust_line_length(line, width, cell_fill.clone()));
1150 if cpr > 0 {
1151 row.push(Segment::new(" ".repeat(cpr), cell_fill.clone()));
1152 }
1153 padded_lines.push(row);
1154 }
1155 for _ in 0..pb {
1156 padded_lines.push(blank());
1157 }
1158 padded_lines
1159 }
1160
1161 #[allow(clippy::too_many_arguments)]
1163 fn render_row(
1164 &self,
1165 console: &Console,
1166 options: &ConsoleOptions,
1167 cells: &[Cell],
1168 rendered_widths: &[usize],
1169 kind: &RowKind,
1170 (first_row, last_row): (bool, bool),
1171 edges: Option<(Segment, Segment, Segment)>,
1172 ) -> Vec<Vec<Segment>> {
1173 let is_header = matches!(kind, RowKind::Header);
1174 let vertical = self.vertical_padding(first_row, last_row);
1177 let ncols = self.columns.len();
1178 let paddings: Vec<(usize, usize)> = (0..ncols)
1181 .map(|index| {
1182 let rendered = rendered_widths.get(index).copied().unwrap_or(0);
1183 self.cell_padding_fitted(index, ncols, rendered)
1184 })
1185 .collect();
1186 let content_widths: Vec<usize> = rendered_widths
1187 .iter()
1188 .zip(&paddings)
1189 .map(|(w, (pl, pr))| w.saturating_sub(pl + pr))
1190 .collect();
1191
1192 let mut cell_lines: Vec<Vec<Vec<Segment>>> = Vec::with_capacity(ncols);
1194 let mut height = 1;
1195 for (index, width) in content_widths.iter().enumerate() {
1196 let style = self.cell_style(console, index, kind);
1197 let column = self.columns.get(index);
1198 let mut text = match cells.get(index) {
1199 Some(Cell::Text(text)) => text.clone(),
1200 Some(Cell::Markup(markup)) => console.render_str(
1203 markup,
1204 Some(column.and_then(|c| c.highlight).unwrap_or(self.highlight)),
1205 ),
1206 None => Text::default(),
1207 Some(Cell::Renderable(renderable)) => {
1208 let mut cell_options = options.update_width(*width);
1212 cell_options.highlight =
1213 Some(column.and_then(|c| c.highlight).unwrap_or(self.highlight));
1214 cell_options.justify = column.map_or(Justify::Left, |c| c.justify);
1215 cell_options.no_wrap = Some(column.is_some_and(|c| c.no_wrap));
1216 cell_options.overflow = Some(column.map_or(Overflow::Ellipsis, |c| c.overflow));
1217 let lines = if *width == 0 {
1218 Vec::new()
1219 } else {
1220 console.render_lines_styled(
1221 renderable.as_ref(),
1222 &cell_options,
1223 Some(&style),
1224 true,
1225 )
1226 };
1227 cell_lines.push(self.pad_cell_lines(
1228 lines,
1229 *width,
1230 paddings[index],
1231 vertical,
1232 &style,
1233 ));
1234 height = height.max(cell_lines.last().map_or(0, Vec::len));
1235 continue;
1236 }
1237 };
1238 let justify = text
1243 .get_justify_option()
1244 .unwrap_or_else(|| column.map(|c| c.justify).unwrap_or(Justify::Left));
1245 let overflow = text
1246 .get_overflow()
1247 .unwrap_or_else(|| column.map_or(Overflow::Ellipsis, |c| c.overflow));
1248 let no_wrap = text
1249 .get_no_wrap()
1250 .unwrap_or_else(|| column.map(|c| c.no_wrap).unwrap_or(false));
1251 if is_header {
1254 if let Some(span) = column.and_then(|c| c.header_content_style.clone()) {
1255 let len = text.plain().len();
1256 text.stylize(span, 0, len);
1257 }
1258 }
1259 let tab_size = text.console_tab_size(console);
1271 let mut lines: Vec<Vec<Segment>> = if *width == 0 {
1272 Vec::new()
1273 } else {
1274 text.render_lines_wrapped_tabs(
1275 console.theme(),
1276 &Style::new(),
1277 Some(*width),
1278 justify,
1279 overflow,
1280 no_wrap,
1281 tab_size,
1282 )
1283 .iter()
1284 .map(|line| Segment::apply_style(line, &style))
1285 .collect()
1286 };
1287 if lines.is_empty() && *width > 0 {
1288 lines.push(Vec::new());
1289 }
1290 let padded_lines =
1291 self.pad_cell_lines(lines, *width, paddings[index], vertical, &style);
1292 height = height.max(padded_lines.len());
1293 cell_lines.push(padded_lines);
1294 }
1295
1296 let row_height = cell_lines.iter().map(Vec::len).max().unwrap_or(0);
1302 for (index, lines) in cell_lines.iter_mut().enumerate() {
1303 let vertical = if is_header {
1307 crate::align::VerticalAlign::Bottom
1308 } else if matches!(kind, RowKind::Footer) {
1309 crate::align::VerticalAlign::Top
1310 } else {
1311 match cells.get(index) {
1312 Some(Cell::Renderable(renderable)) => renderable.vertical(),
1313 _ => None,
1314 }
1315 .unwrap_or_else(|| {
1316 self.columns
1317 .get(index)
1318 .map_or(crate::align::VerticalAlign::Top, |c| c.vertical)
1319 })
1320 };
1321 let (cpl, cpr) = paddings[index];
1322 let blank = " ".repeat(cpl + content_widths[index] + cpr);
1323 let filler = vec![Segment::new(
1324 blank.clone(),
1325 Some(self.cell_style(console, index, kind)),
1326 )];
1327 let missing = row_height.saturating_sub(lines.len());
1328 let top = match vertical {
1329 crate::align::VerticalAlign::Top => 0,
1330 crate::align::VerticalAlign::Middle => missing / 2,
1331 crate::align::VerticalAlign::Bottom => missing,
1332 };
1333 lines.splice(0..0, std::iter::repeat_n(filler.clone(), top));
1334 lines.extend(std::iter::repeat_n(filler, missing - top));
1335 while lines.len() < height {
1336 lines.push(vec![Segment::new(blank.clone(), None)]);
1337 }
1338 }
1339
1340 let last = ncols.saturating_sub(1);
1341 let mut rows_out: Vec<Vec<Segment>> = Vec::with_capacity(height);
1342 #[allow(clippy::needless_range_loop)]
1345 for r in 0..height {
1346 let mut row = Vec::new();
1347 if let (Some((edge_left, _, _)), true) = (&edges, self.show_edge) {
1348 row.push(edge_left.clone());
1349 }
1350 for (c, column_lines) in cell_lines.iter().enumerate() {
1351 row.extend(column_lines[r].clone());
1352 let Some((_, divider, edge_right)) = &edges else {
1353 continue;
1354 };
1355 if c != last {
1356 row.push(divider.clone());
1357 } else if self.show_edge {
1358 row.push(edge_right.clone());
1359 }
1360 }
1361 rows_out.push(row);
1362 }
1363 rows_out
1364 }
1365}
1366
1367impl LineRenderable for Table {
1368 fn try_for_each_line<E>(
1376 &self,
1377 console: &Console,
1378 options: &ConsoleOptions,
1379 mut emit: impl FnMut(Vec<Segment>) -> Result<(), E>,
1380 ) -> Result<(), E> {
1381 if self.columns.is_empty() {
1382 return emit(vec![Segment::new("", None)]);
1383 }
1384 let box_set = self.box_set.substitute(
1387 console.legacy_windows(),
1388 self.safe_box.unwrap_or_else(|| console.safe_box()),
1389 console.ascii_only(),
1390 );
1391 let box_set = if self.show_header {
1392 box_set
1393 } else {
1394 box_set.get_plain_headed_box()
1395 };
1396 let max_width = self.width.unwrap_or(options.max_width);
1398 let extra_width = self.extra_width();
1399 let available = max_width.saturating_sub(extra_width);
1400
1401 let rendered_widths = self.column_widths(console, options, available);
1402 let border_style = self.style.combine(&self.border_style);
1403 let border = Some(border_style.clone());
1404
1405 let table_width: usize = rendered_widths.iter().sum::<usize>() + extra_width;
1407
1408 if let Some(title) = &self.title {
1409 let style = self
1410 .title_style
1411 .clone()
1412 .unwrap_or_else(|| StyleType::Name("table.title".to_string()));
1413 for line in render_annotation(
1414 console,
1415 options,
1416 title,
1417 &style,
1418 self.title_justify,
1419 table_width,
1420 ) {
1421 emit(line)?;
1422 }
1423 }
1424
1425 let edge = self.show_edge;
1426 let boxed = !self.no_box;
1427 if boxed && edge {
1428 emit(vec![Segment::new(
1429 box_set.get_top(&rendered_widths, edge),
1430 border.clone(),
1431 )])?;
1432 }
1433
1434 let segments = |(left, vertical, right): (char, char, char)| {
1437 (
1438 Segment::new(left.to_string(), border.clone()),
1439 Segment::new(vertical.to_string(), border.clone()),
1440 Segment::new(right.to_string(), border.clone()),
1441 )
1442 };
1443 let head_edges = segments((box_set.head_left, box_set.head_vertical, box_set.head_right));
1444 let mid_edges = segments((box_set.mid_left, box_set.mid_vertical, box_set.mid_right));
1445 let foot_edges = segments((box_set.foot_left, box_set.foot_vertical, box_set.foot_right));
1446
1447 let mut row_list: Vec<(RowKind, &[Cell], Option<&Row>)> = Vec::new();
1449 let header_cells: Vec<Cell> = self.columns.iter().map(|c| c.header.clone()).collect();
1450 let footer_cells: Vec<Cell> = self.columns.iter().map(|c| c.footer.clone()).collect();
1451 if self.show_header {
1452 row_list.push((RowKind::Header, &header_cells, None));
1453 }
1454 for (index, row) in self.rows.iter().enumerate() {
1455 row_list.push((
1456 RowKind::Body(self.get_row_style(console, index)),
1457 &row.cells,
1458 Some(row),
1459 ));
1460 }
1461 if self.show_footer {
1462 row_list.push((RowKind::Footer, &footer_cells, None));
1463 }
1464 let total = row_list.len();
1465 for (index, (kind, cells, row)) in row_list.iter().enumerate() {
1466 let first = index == 0;
1467 let last = index + 1 == total;
1468 let header_row = first && self.show_header;
1469 if boxed && last && self.show_footer {
1470 emit(vec![Segment::new(
1471 box_set.get_row(&rendered_widths, RowLevel::Foot, edge),
1472 border.clone(),
1473 )])?;
1474 }
1475 let edges = boxed.then(|| {
1476 let (left, divider, right) = if first {
1477 head_edges.clone()
1478 } else if last {
1479 foot_edges.clone()
1480 } else {
1481 mid_edges.clone()
1482 };
1483 let divider = if divider.text.trim().is_empty() {
1485 let background = match kind {
1486 RowKind::Body(row_style) => {
1487 Style::from_color(None, row_style.bgcolor().cloned())
1488 }
1489 _ => Style::new(),
1490 };
1491 Segment::new(
1492 divider.text.clone(),
1493 Some(background.combine(&border_style)),
1494 )
1495 } else {
1496 divider
1497 };
1498 (left, divider, right)
1499 });
1500 for line in self.render_row(
1501 console,
1502 options,
1503 cells,
1504 &rendered_widths,
1505 kind,
1506 (first, last),
1507 edges,
1508 ) {
1509 emit(line)?;
1510 }
1511 if boxed && header_row {
1512 emit(vec![Segment::new(
1513 box_set.get_row(&rendered_widths, RowLevel::Head, edge),
1514 border.clone(),
1515 )])?;
1516 }
1517 let end_section = row.is_some_and(|row| row.end_section);
1518 if boxed
1519 && (self.show_lines || self.leading > 0 || end_section)
1520 && !last
1521 && !(self.show_footer && index + 2 >= total)
1522 && !header_row
1523 {
1524 let line = if self.leading > 0 {
1525 box_set
1527 .get_row(&rendered_widths, RowLevel::Mid, edge)
1528 .repeat(self.leading)
1529 } else {
1530 box_set.get_row(&rendered_widths, RowLevel::Row, edge)
1531 };
1532 emit(vec![Segment::new(line, border.clone())])?;
1533 }
1534 }
1535
1536 if boxed && edge {
1537 emit(vec![Segment::new(
1538 box_set.get_bottom(&rendered_widths, edge),
1539 border.clone(),
1540 )])?;
1541 }
1542
1543 if let Some(caption) = &self.caption {
1544 let style = self
1545 .caption_style
1546 .clone()
1547 .unwrap_or_else(|| StyleType::Name("table.caption".to_string()));
1548 for line in render_annotation(
1549 console,
1550 options,
1551 caption,
1552 &style,
1553 self.caption_justify,
1554 table_width,
1555 ) {
1556 emit(line)?;
1557 }
1558 }
1559
1560 Ok(())
1561 }
1562}
1563
1564impl crate::protocol::OwnedTableRows for Table {
1565 fn extend_owned_rows(&mut self, rows: Vec<Vec<String>>) -> &mut Self {
1566 for row in rows {
1567 self.push_row(row.into_iter().map(Cell::Markup).collect(), None, false);
1568 }
1569 self
1570 }
1571}
1572
1573impl Renderable for Table {
1574 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
1578 let max_width = self.width.unwrap_or(options.max_width);
1579 if self.columns.is_empty() {
1580 let width = usize::from(!self.no_box && self.show_edge);
1584 return Measurement::new(width, self.width.unwrap_or(width))
1585 .clamp(self.min_width, None);
1586 }
1587 let extra_width = self.extra_width();
1588 let columns_width: usize = self
1589 .column_widths(console, options, max_width.saturating_sub(extra_width))
1590 .iter()
1591 .sum();
1592 let options = options.update_width(columns_width);
1593 let (minimum, maximum) = (0..self.columns.len())
1594 .map(|index| self.measure_column(console, &options, index))
1595 .fold((0, 0), |(minimum, maximum), width| {
1596 (minimum + width.minimum, maximum + width.maximum)
1597 });
1598 let maximum = self.width.unwrap_or(maximum + extra_width);
1599 Measurement::new(minimum + extra_width, maximum).clamp(self.min_width, None)
1600 }
1601
1602 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
1603 let mut segments = Vec::new();
1604 let mut first = true;
1605 let result: Result<(), std::convert::Infallible> =
1606 self.try_for_each_line(console, options, |line| {
1607 if !first {
1608 segments.push(Segment::line());
1609 }
1610 first = false;
1611 segments.extend(line);
1612 Ok(())
1613 });
1614 match result {
1615 Ok(()) => segments,
1616 Err(never) => match never {},
1617 }
1618 }
1619}
1620
1621fn render_annotation(
1626 console: &Console,
1627 options: &ConsoleOptions,
1628 annotation: &Cell,
1629 style: &StyleType,
1630 justify: Justify,
1631 width: usize,
1632) -> Vec<Vec<Segment>> {
1633 let text = match annotation {
1634 Cell::Markup(markup) => {
1635 if markup.is_empty() {
1636 return Vec::new();
1637 }
1638 let mut text = console.render_str(markup, Some(false));
1639 text.set_base_style(style.clone());
1640 text
1641 }
1642 Cell::Text(text) => {
1643 if text.plain().is_empty() {
1644 return Vec::new();
1645 }
1646 text.clone()
1647 }
1648 Cell::Renderable(_) => return Vec::new(),
1649 };
1650 if width == 0 {
1651 return Vec::new();
1652 }
1653 let justify = text.get_justify_option().unwrap_or(justify);
1654 let overflow = text
1655 .get_overflow()
1656 .or(options.overflow)
1657 .unwrap_or(Overflow::Fold);
1658 let no_wrap = text.get_no_wrap().or(options.no_wrap).unwrap_or(false);
1659 let tab_size = text.console_tab_size(console);
1660 text.render_lines_wrapped_tabs(
1661 console.theme(),
1662 console.base_style(),
1663 Some(width),
1664 justify,
1665 overflow,
1666 no_wrap,
1667 tab_size,
1668 )
1669}
1670
1671fn round_half_even(value: f64) -> i64 {
1673 let floor = value.floor();
1674 let diff = value - floor;
1675 if (diff - 0.5).abs() < 1e-9 {
1676 let f = floor as i64;
1677 if f % 2 == 0 {
1678 f
1679 } else {
1680 f + 1
1681 }
1682 } else {
1683 value.round() as i64
1684 }
1685}
1686
1687fn ratio_reduce(total: i64, ratios: &[i64], maximums: &[i64], values: &[i64]) -> Vec<i64> {
1690 let ratios: Vec<i64> = ratios
1691 .iter()
1692 .zip(maximums)
1693 .map(|(&r, &m)| if m != 0 { r } else { 0 })
1694 .collect();
1695 let mut total_ratio: i128 = ratios.iter().map(|&r| i128::from(r)).sum();
1696 if total_ratio == 0 {
1697 return values.to_vec();
1698 }
1699 let mut total_remaining = total;
1700 let mut result = Vec::with_capacity(values.len());
1701 for ((&ratio, &maximum), &value) in ratios.iter().zip(maximums).zip(values) {
1702 if ratio != 0 && total_ratio > 0 {
1703 let distributed = maximum.min(round_half_even(
1704 ratio as f64 * total_remaining as f64 / total_ratio as f64,
1705 ));
1706 result.push(value - distributed);
1707 total_remaining -= distributed;
1708 total_ratio -= i128::from(ratio);
1709 } else {
1710 result.push(value);
1711 }
1712 }
1713 result
1714}
1715
1716fn ratio_distribute(total: i64, ratios: &[i64], minimums: Option<&[i64]>) -> Vec<i64> {
1720 let ratios: Vec<i64> = match minimums {
1722 Some(mins) => ratios
1723 .iter()
1724 .zip(mins)
1725 .map(|(&r, &m)| if m != 0 { r } else { 0 })
1726 .collect(),
1727 None => ratios.to_vec(),
1728 };
1729 let mut total_ratio: i128 = ratios.iter().map(|&r| i128::from(r)).sum();
1732 let mut total_remaining = i128::from(total);
1733 let mut result = Vec::with_capacity(ratios.len());
1734 for (index, &ratio) in ratios.iter().enumerate() {
1735 let ratio = i128::from(ratio);
1736 let minimum = i128::from(minimums.map_or(0, |m| m[index]));
1737 let distributed = if total_ratio > 0 {
1738 let numerator = ratio * total_remaining;
1741 let ceil_div = (numerator + total_ratio - 1) / total_ratio;
1742 minimum.max(ceil_div)
1743 } else {
1744 total_remaining
1745 };
1746 result.push(i64::try_from(distributed).unwrap_or(if distributed < 0 {
1747 i64::MIN
1748 } else {
1749 i64::MAX
1750 }));
1751 total_ratio -= ratio;
1752 total_remaining -= distributed;
1753 }
1754 result
1755}
1756
1757fn collapse_widths(mut widths: Vec<i64>, wrapable: &[bool], max_width: i64) -> Vec<i64> {
1760 let mut total_width: i64 = widths.iter().sum();
1761 let mut excess_width = total_width - max_width;
1762 if wrapable.iter().any(|&w| w) {
1763 while total_width != 0 && excess_width > 0 {
1764 let max_column = widths
1765 .iter()
1766 .zip(wrapable)
1767 .filter(|(_, &w)| w)
1768 .map(|(&x, _)| x)
1769 .max()
1770 .unwrap_or(0);
1771 let second_max_column = widths
1772 .iter()
1773 .zip(wrapable)
1774 .map(|(&x, &w)| if w && x != max_column { x } else { 0 })
1775 .max()
1776 .unwrap_or(0);
1777 let column_difference = max_column - second_max_column;
1778 let ratios: Vec<i64> = widths
1779 .iter()
1780 .zip(wrapable)
1781 .map(|(&x, &w)| i64::from(x == max_column && w))
1782 .collect();
1783 if !ratios.iter().any(|&r| r != 0) || column_difference == 0 {
1784 break;
1785 }
1786 let max_reduce = vec![excess_width.min(column_difference); widths.len()];
1787 widths = ratio_reduce(excess_width, &ratios, &max_reduce, &widths);
1788 total_width = widths.iter().sum();
1789 excess_width = total_width - max_width;
1790 }
1791 }
1792 widths
1793}
1794
1795#[cfg(test)]
1796mod tests {
1797
1798 #[test]
1799 fn a_huge_column_ratio_does_not_overflow() {
1800 let console = crate::Console::builder()
1803 .width(40)
1804 .color_system(None)
1805 .build();
1806 let mut table = Table::new().expand(true);
1807 table.add_column("a").column_ratio(usize::MAX);
1808 table.add_column("b").column_ratio(1);
1809 table.add_row(&["x", "y"]);
1810 assert_eq!(
1811 console.render_to_string(&table) + "\n",
1812 "┏━━━┳━━━┓\n┃ a ┃ b ┃\n┡━━━╇━━━┩\n│ x │ y │\n└───┴───┘\n"
1813 );
1814 }
1815
1816 use super::*;
1817 use crate::color::ColorSystem;
1818 use crate::r#box::SQUARE;
1819
1820 fn console() -> Console {
1821 Console::builder()
1822 .force_terminal(true)
1823 .color_system(Some(ColorSystem::Truecolor))
1824 .width(40)
1825 .no_color(false)
1826 .build()
1827 }
1828
1829 #[test]
1830 fn owned_rows_preserve_measurement_styles_and_missing_cells() {
1831 use crate::protocol::OwnedTableRows;
1832 for width in [1, 12, 40, 80] {
1833 let console = Console::builder().width(width).force_terminal(true).build();
1834 let build = || {
1835 let mut table = Table::new()
1836 .title("Rows")
1837 .caption("owned or borrowed")
1838 .show_lines(true);
1839 table.add_column("Name");
1840 table.add_column_justify("Value", Justify::Right);
1841 table
1842 };
1843 let mut borrowed = build();
1844 let mut owned = build();
1845 for row in [
1846 vec!["漢字\n🙂", "123"],
1847 vec!["short"],
1848 vec!["extra", "4", "ignored"],
1849 ] {
1850 borrowed.add_row(&row);
1851 owned.extend_owned_rows(vec![row.into_iter().map(str::to_owned).collect()]);
1852 }
1853 assert_eq!(
1854 console.render_to_string(&borrowed),
1855 console.render_to_string(&owned)
1856 );
1857 }
1858 }
1859
1860 #[test]
1861 fn simple_square_table() {
1862 let mut table = Table::new().box_set(SQUARE);
1863 table.add_column("Name");
1864 table.add_column("Age");
1865 table.add_row(&["Alice", "30"]);
1866 table.add_row(&["Bob", "7"]);
1867 let out = console().render_export(&table);
1868 let expected = concat!(
1869 "┌───────┬─────┐\n",
1870 "│\x1b[1m \x1b[0m\x1b[1mName \x1b[0m\x1b[1m \x1b[0m│\x1b[1m \x1b[0m\x1b[1mAge\x1b[0m\x1b[1m \x1b[0m│\n",
1871 "├───────┼─────┤\n",
1872 "│ Alice │ 30 │\n",
1873 "│ Bob │ 7 │\n",
1874 "└───────┴─────┘\n",
1875 );
1876 assert_eq!(out, expected);
1877 }
1878
1879 #[test]
1880 fn streamed_lines_match_styled_table_output() {
1881 let mut table = Table::new().box_set(SQUARE);
1882 table.add_column("Name");
1883 table.add_column("Age");
1884 table.add_row(&["Alice", "30"]);
1885 table.add_row(&["Bob", "7"]);
1886 let console = console();
1887 let mut streamed = String::new();
1888 table
1889 .try_for_each_line(&console, &console.options(), |line| {
1890 assert!(line.iter().all(|segment| !segment.text.contains('\n')));
1891 streamed.push_str(&console.segments_to_string(&line));
1892 streamed.push('\n');
1893 Ok::<_, std::convert::Infallible>(())
1894 })
1895 .unwrap();
1896 assert_eq!(streamed, console.render_export(&table));
1899 assert_eq!(streamed.lines().count(), 6);
1900 }
1901
1902 #[test]
1903 fn streamed_lines_stop_at_the_first_writer_error() {
1904 let mut table = Table::new()
1905 .box_set(SQUARE)
1906 .title("People")
1907 .caption("End")
1908 .show_lines(true);
1909 table.add_column("Name");
1910 table.add_row(&["Alice\nBob"]);
1911 table.add_row(&["Carol"]);
1912 let console = console();
1913 let mut visits = 0;
1914 let result = table.try_for_each_line(&console, &console.options(), |_| {
1915 visits += 1;
1916 if visits == 5 {
1917 Err("writer failed")
1918 } else {
1919 Ok(())
1920 }
1921 });
1922 assert_eq!(result, Err("writer failed"));
1923 assert_eq!(visits, 5);
1924 }
1925
1926 #[test]
1931 fn a_column_narrower_than_its_padding_stays_inside_the_border() {
1932 for ncols in [20usize, 29, 40] {
1933 let mut table = Table::new().box_set(SQUARE);
1934 for i in 0..ncols {
1935 table.add_column(format!("c{i}"));
1936 }
1937 let row: Vec<String> = (0..ncols).map(|i| i.to_string()).collect();
1938 table.add_row(&row.iter().map(String::as_str).collect::<Vec<_>>());
1939 let console = Console::builder().width(80).color_system(None).build();
1940 let out = console.render_to_string(&table);
1941 let rows: Vec<&str> = out.lines().filter(|l| !l.trim().is_empty()).collect();
1942 let widths: Vec<usize> = rows.iter().map(|r| r.chars().count()).collect();
1943 assert!(
1944 widths.iter().all(|w| *w == widths[0]),
1945 "{ncols} columns produced ragged rows: {widths:?}"
1946 );
1947 for (index, row) in rows.iter().enumerate() {
1948 let last = row.chars().last().expect("non-empty row");
1949 assert!(
1950 !last.is_whitespace(),
1951 "{ncols} columns: row {index} lost its right border: {row:?}"
1952 );
1953 }
1954 }
1955 }
1956
1957 #[test]
1962 fn a_multi_line_cell_is_measured_by_its_widest_line() {
1963 let mut table = Table::new().box_set(SQUARE);
1964 table.add_column("name");
1965 table.add_column("bio");
1966 table.add_row(&["Alice", "line one\nline two is much longer"]);
1967 table.add_row(&["Bob", "short"]);
1968 let console = Console::builder().width(60).color_system(None).build();
1969 let out = console.render_to_string(&table);
1970 let top = out.lines().next().expect("a top border");
1971 let width = top.chars().count();
1972 assert!(
1974 width < 40,
1975 "the multi-line cell was measured as the sum of its lines: {width} wide"
1976 );
1977 assert!(
1978 out.contains("line two is much longer"),
1979 "content lost: {out:?}"
1980 );
1981 }
1982}