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::ratio::{ratio_distribute, ratio_reduce};
32use crate::segment::Segment;
33use crate::style::{Style, StyleType};
34use crate::text::Text;
35
36pub struct Column {
41 header: Cell,
42 footer: Cell,
44 footer_fill: Option<Style>,
47 vertical: crate::align::VerticalAlign,
49 highlight: Option<bool>,
52 justify: Justify,
53 width: Option<usize>,
55 style: Style,
57 header_content_style: Option<Style>,
61 header_fill: Option<Style>,
65 ratio: Option<usize>,
68 min_width: Option<usize>,
70 max_width: Option<usize>,
73 no_wrap: bool,
75 overflow: Overflow,
78}
79
80impl Column {
83 pub fn header(&self) -> &Cell {
85 &self.header
86 }
87
88 pub fn footer(&self) -> &Cell {
91 &self.footer
92 }
93
94 pub fn justify(&self) -> Justify {
96 self.justify
97 }
98
99 pub fn style(&self) -> &Style {
101 &self.style
102 }
103
104 pub fn header_style(&self) -> Option<&Style> {
107 self.header_fill.as_ref()
108 }
109
110 pub fn footer_style(&self) -> Option<&Style> {
113 self.footer_fill.as_ref()
114 }
115}
116
117impl Row {
120 pub fn cells(&self) -> &[Cell] {
123 &self.cells
124 }
125
126 pub fn style(&self) -> Option<&StyleType> {
128 self.style.as_ref()
129 }
130
131 pub fn end_section(&self) -> bool {
133 self.end_section
134 }
135}
136
137impl Column {
138 fn new(header: Cell, justify: Justify) -> Self {
140 Column {
141 header,
142 footer: Cell::Markup(String::new()),
143 footer_fill: None,
144 highlight: None,
145 vertical: crate::align::VerticalAlign::Top,
146 justify,
147 width: None,
148 style: Style::new(),
149 header_content_style: None,
150 header_fill: None,
151 ratio: None,
152 min_width: None,
153 max_width: None,
154 no_wrap: false,
155 overflow: Overflow::Ellipsis,
156 }
157 }
158}
159
160pub struct Row {
165 cells: Vec<Cell>,
166 style: Option<StyleType>,
168 end_section: bool,
170}
171
172#[derive(Clone)]
175enum RowKind {
176 Header,
177 Body(Style),
178 Footer,
179}
180
181#[derive(Clone)]
184pub enum Cell {
185 Text(Text),
188 Markup(String),
193 Renderable(Arc<dyn Renderable + Send + Sync>),
196}
197
198impl From<Text> for Cell {
199 fn from(text: Text) -> Self {
200 Cell::Text(text)
201 }
202}
203
204impl From<&str> for Cell {
211 fn from(text: &str) -> Self {
212 Cell::Markup(text.to_string())
213 }
214}
215
216impl From<String> for Cell {
218 fn from(text: String) -> Self {
219 Cell::Markup(text)
220 }
221}
222
223impl From<&String> for Cell {
225 fn from(text: &String) -> Self {
226 Cell::Markup(text.clone())
227 }
228}
229
230impl Cell {
231 pub(crate) fn to_text(&self, console: &Console, highlight: Option<bool>) -> Option<Text> {
234 match self {
235 Cell::Text(text) => Some(text.clone()),
236 Cell::Markup(markup) => Some(console.render_str(markup, highlight)),
237 Cell::Renderable(_) => None,
238 }
239 }
240
241 pub(crate) fn measure_cell(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
245 match self {
246 Cell::Text(text) => Measurement::get(console, options, text),
247 Cell::Markup(markup) if !markup.contains(['[', ':']) => {
250 if options.max_width < 1 {
251 return Measurement::new(0, 0);
252 }
253 let (minimum, maximum) = crate::text::measure_plain(markup);
254 let width = Measurement::new(minimum, maximum)
255 .normalize()
256 .with_maximum(options.max_width);
257 if width.maximum < 1 {
258 Measurement::new(0, 0)
259 } else {
260 width.normalize()
261 }
262 }
263 Cell::Markup(markup) => {
264 Measurement::get(console, options, &console.render_str(markup, Some(false)))
265 }
266 Cell::Renderable(renderable) => Measurement::get(console, options, renderable.as_ref()),
267 }
268 }
269}
270
271impl Default for Cell {
272 fn default() -> Self {
273 Cell::Text(Text::default())
274 }
275}
276
277#[derive(Clone, Debug)]
280pub struct ColumnOptions {
281 pub justify: Justify,
283 pub width: Option<usize>,
285 pub min_width: Option<usize>,
287 pub max_width: Option<usize>,
289 pub ratio: Option<usize>,
291 pub no_wrap: bool,
293 pub overflow: Overflow,
295 pub style: Style,
297}
298
299impl Default for ColumnOptions {
300 fn default() -> Self {
301 ColumnOptions {
302 justify: Justify::Left,
303 width: None,
304 min_width: None,
305 max_width: None,
306 ratio: None,
307 no_wrap: false,
308 overflow: Overflow::Ellipsis,
309 style: Style::new(),
310 }
311 }
312}
313
314pub struct Table {
316 columns: Vec<Column>,
317 rows: Vec<Row>,
318 box_set: BoxSet,
319 no_box: bool,
321 show_header: bool,
322 show_lines: bool,
323 show_edge: bool,
324 pad_edge: bool,
325 collapse_padding: bool,
326 expand: bool,
327 title: Option<Cell>,
329 caption: Option<Cell>,
330 padding: (usize, usize, usize, usize),
331 header_style: StyleType,
333 footer_style: StyleType,
335 border_style: Style,
336 style: Style,
337 highlight: bool,
339 width: Option<usize>,
341 min_width: Option<usize>,
343 show_footer: bool,
344 leading: usize,
346 row_styles: Vec<StyleType>,
348 title_style: Option<StyleType>,
350 caption_style: Option<StyleType>,
351 title_justify: Justify,
352 caption_justify: Justify,
353 safe_box: Option<bool>,
356}
357
358impl Default for Table {
359 fn default() -> Self {
360 Table {
361 columns: Vec::new(),
362 rows: Vec::new(),
363 box_set: HEAVY_HEAD,
364 no_box: false,
365 show_header: true,
366 show_lines: false,
367 show_edge: true,
368 pad_edge: true,
369 collapse_padding: false,
370 expand: false,
371 title: None,
372 caption: None,
373 padding: (0, 1, 0, 1),
374 header_style: StyleType::Name("table.header".to_string()),
375 footer_style: StyleType::Name("table.footer".to_string()),
376 border_style: Style::new(),
377 style: Style::new(),
378 highlight: false,
379 width: None,
380 min_width: None,
381 show_footer: false,
382 leading: 0,
383 row_styles: Vec::new(),
384 title_style: None,
385 caption_style: None,
386 title_justify: Justify::Center,
387 caption_justify: Justify::Center,
388 safe_box: None,
389 }
390 }
391}
392
393impl Table {
394 pub fn new() -> Self {
395 Table::default()
396 }
397
398 pub fn grid() -> Self {
402 Table {
403 no_box: true,
404 show_header: false,
405 show_edge: false,
406 pad_edge: false,
407 collapse_padding: true,
408 padding: (0, 0, 0, 0),
409 ..Table::default()
410 }
411 }
412
413 pub fn without_box(mut self) -> Self {
415 self.no_box = true;
416 self
417 }
418
419 pub fn padding(mut self, top: usize, right: usize, bottom: usize, left: usize) -> Self {
421 self.padding = (top, right, bottom, left);
422 self
423 }
424
425 pub fn box_set(mut self, box_set: BoxSet) -> Self {
427 self.box_set = box_set;
428 self
429 }
430
431 pub fn border_style(mut self, style: Style) -> Self {
434 self.border_style = style;
435 self
436 }
437
438 pub fn show_header(mut self, show: bool) -> Self {
440 self.show_header = show;
441 self
442 }
443
444 pub fn expand(mut self, expand: bool) -> Self {
446 self.expand = expand;
447 self
448 }
449
450 pub fn show_lines(mut self, show: bool) -> Self {
452 self.show_lines = show;
453 self
454 }
455
456 pub fn show_edge(mut self, show: bool) -> Self {
459 self.show_edge = show;
460 self
461 }
462
463 pub fn pad_edge(mut self, pad: bool) -> Self {
466 self.pad_edge = pad;
467 self
468 }
469
470 pub fn collapse_padding(mut self, collapse: bool) -> Self {
473 self.collapse_padding = collapse;
474 self
475 }
476
477 pub fn style(mut self, style: Style) -> Self {
481 self.style = style;
482 self
483 }
484
485 pub fn highlight(mut self, highlight: bool) -> Self {
489 self.highlight = highlight;
490 self
491 }
492
493 fn cell_padding(&self, index: usize, ncols: usize) -> (usize, usize) {
496 let (_, pr, _, pl) = self.padding;
497 let mut left = if self.collapse_padding && index > 0 {
500 pl.saturating_sub(pr)
501 } else {
502 pl
503 };
504 let mut right = pr;
505 if !self.pad_edge && index == 0 {
507 left = 0;
508 }
509 if !self.pad_edge && index + 1 == ncols {
510 right = 0;
511 }
512 (left, right)
513 }
514
515 pub fn title(mut self, title: impl Into<String>) -> Self {
518 self.title = Some(Cell::Markup(title.into()));
519 self
520 }
521
522 pub fn caption(mut self, caption: impl Into<String>) -> Self {
524 self.caption = Some(Cell::Markup(caption.into()));
525 self
526 }
527
528 pub fn title_text(mut self, title: Text) -> Self {
531 self.title = Some(Cell::Text(title));
532 self
533 }
534
535 pub fn caption_text(mut self, caption: Text) -> Self {
537 self.caption = Some(Cell::Text(caption));
538 self
539 }
540
541 pub fn title_style(mut self, style: impl Into<StyleType>) -> Self {
544 self.title_style = Some(style.into());
545 self
546 }
547
548 pub fn caption_style(mut self, style: impl Into<StyleType>) -> Self {
551 self.caption_style = Some(style.into());
552 self
553 }
554
555 pub fn title_justify(mut self, justify: Justify) -> Self {
557 self.title_justify = justify;
558 self
559 }
560
561 pub fn caption_justify(mut self, justify: Justify) -> Self {
563 self.caption_justify = justify;
564 self
565 }
566
567 pub fn header_style(mut self, style: impl Into<StyleType>) -> Self {
570 self.header_style = style.into();
571 self
572 }
573
574 pub fn footer_style(mut self, style: impl Into<StyleType>) -> Self {
577 self.footer_style = style.into();
578 self
579 }
580
581 pub fn width(mut self, width: Option<usize>) -> Self {
584 self.width = width;
585 self
586 }
587
588 pub fn min_width(mut self, min_width: Option<usize>) -> Self {
590 self.min_width = min_width;
591 self
592 }
593
594 pub fn show_footer(mut self, show: bool) -> Self {
597 self.show_footer = show;
598 self
599 }
600
601 pub fn leading(mut self, leading: usize) -> Self {
604 self.leading = leading;
605 self
606 }
607
608 pub fn row_styles(mut self, styles: Vec<StyleType>) -> Self {
610 self.row_styles = styles;
611 self
612 }
613
614 pub fn safe_box(mut self, safe_box: Option<bool>) -> Self {
617 self.safe_box = safe_box;
618 self
619 }
620
621 pub fn is_expand(&self) -> bool {
624 self.expand || self.width.is_some()
625 }
626
627 pub fn row_count(&self) -> usize {
629 self.rows.len()
630 }
631
632 pub fn column_count(&self) -> usize {
634 self.columns.len()
635 }
636
637 pub fn columns(&self) -> &[Column] {
655 &self.columns
656 }
657
658 pub fn rows(&self) -> &[Row] {
660 &self.rows
661 }
662
663 pub fn get_title(&self) -> Option<&Cell> {
666 self.title.as_ref()
667 }
668
669 pub fn get_caption(&self) -> Option<&Cell> {
672 self.caption.as_ref()
673 }
674
675 pub fn get_show_header(&self) -> bool {
677 self.show_header
678 }
679
680 pub fn get_show_footer(&self) -> bool {
682 self.show_footer
683 }
684
685 pub fn get_show_lines(&self) -> bool {
687 self.show_lines
688 }
689
690 pub fn get_box(&self) -> Option<BoxSet> {
693 (!self.no_box).then_some(self.box_set)
694 }
695
696 pub fn get_header_style(&self) -> &StyleType {
698 &self.header_style
699 }
700
701 pub fn get_footer_style(&self) -> &StyleType {
703 &self.footer_style
704 }
705
706 pub fn get_row_style(&self, console: &Console, index: usize) -> Style {
709 let theme = console.theme();
710 let mut style = Style::new();
711 if !self.row_styles.is_empty() {
712 style = style
713 .combine(&theme.get_style_or_null(&self.row_styles[index % self.row_styles.len()]));
714 }
715 if let Some(row_style) = self.rows.get(index).and_then(|row| row.style.as_ref()) {
716 style = style.combine(&theme.get_style_or_null(row_style));
717 }
718 style
719 }
720
721 pub fn add_section(&mut self) -> &mut Self {
724 if let Some(row) = self.rows.last_mut() {
725 row.end_section = true;
726 }
727 self
728 }
729
730 pub fn add_column(&mut self, header: impl Into<String>) -> &mut Self {
733 self.add_column_justify(header, Justify::Left)
734 }
735
736 pub fn add_column_justify(&mut self, header: impl Into<String>, justify: Justify) -> &mut Self {
739 self.add_column_text(Text::default(), justify);
740 if let Some(column) = self.columns.last_mut() {
741 column.header = Cell::Markup(header.into());
742 }
743 self
744 }
745
746 pub fn add_column_text(&mut self, header: Text, justify: Justify) -> &mut Self {
751 self.columns.push(Column::new(Cell::Text(header), justify));
752 self
753 }
754
755 pub fn add_column_cell(&mut self, header: Cell, justify: Justify) -> &mut Self {
758 self.columns.push(Column::new(header, justify));
759 self
760 }
761
762 pub fn column_footer(&mut self, footer: impl Into<Cell>) -> &mut Self {
765 if let Some(column) = self.columns.last_mut() {
766 column.footer = footer.into();
767 }
768 self
769 }
770
771 pub fn column_footer_fill(&mut self, style: Style) -> &mut Self {
774 if let Some(column) = self.columns.last_mut() {
775 column.footer_fill = Some(style);
776 }
777 self
778 }
779
780 pub fn add_column_with(&mut self, header: Text, options: ColumnOptions) -> &mut Self {
783 self.columns.push(Column {
784 width: options.width,
785 style: options.style,
786 ratio: options.ratio,
787 min_width: options.min_width,
788 max_width: options.max_width,
789 no_wrap: options.no_wrap,
790 overflow: options.overflow,
791 ..Column::new(Cell::Text(header), options.justify)
792 });
793 self
794 }
795
796 pub fn column_vertical(&mut self, vertical: crate::align::VerticalAlign) -> &mut Self {
801 if let Some(column) = self.columns.last_mut() {
802 column.vertical = vertical;
803 }
804 self
805 }
806
807 pub fn column_width(&mut self, width: usize) -> &mut Self {
811 if let Some(column) = self.columns.last_mut() {
812 column.width = Some(width);
813 }
814 self
815 }
816
817 pub fn column_ratio(&mut self, ratio: usize) -> &mut Self {
821 if let Some(column) = self.columns.last_mut() {
822 column.ratio = Some(ratio);
823 }
824 self
825 }
826
827 pub fn column_min_width(&mut self, min_width: usize) -> &mut Self {
830 if let Some(column) = self.columns.last_mut() {
831 column.min_width = Some(min_width);
832 }
833 self
834 }
835
836 pub fn column_max_width(&mut self, max_width: usize) -> &mut Self {
839 if let Some(column) = self.columns.last_mut() {
840 column.max_width = Some(max_width);
841 }
842 self
843 }
844
845 pub fn column_style(&mut self, style: Style) -> &mut Self {
848 if let Some(column) = self.columns.last_mut() {
849 column.style = style;
850 }
851 self
852 }
853
854 pub fn column_header_style(&mut self, style: Style) -> &mut Self {
858 if let Some(column) = self.columns.last_mut() {
859 column.header_content_style = Some(style);
860 }
861 self
862 }
863
864 pub fn column_header_fill(&mut self, style: Style) -> &mut Self {
868 if let Some(column) = self.columns.last_mut() {
869 column.header_fill = Some(style);
870 }
871 self
872 }
873
874 pub fn column_overflow(&mut self, overflow: Overflow) -> &mut Self {
877 if let Some(column) = self.columns.last_mut() {
878 column.overflow = overflow;
879 }
880 self
881 }
882
883 pub fn column_highlight(&mut self, highlight: bool) -> &mut Self {
886 if let Some(column) = self.columns.last_mut() {
887 column.highlight = Some(highlight);
888 }
889 self
890 }
891
892 pub fn column_no_wrap(&mut self) -> &mut Self {
895 if let Some(column) = self.columns.last_mut() {
896 column.no_wrap = true;
897 }
898 self
899 }
900
901 pub fn add_row(&mut self, cells: &[&str]) -> &mut Self {
907 self.push_row(
908 cells
909 .iter()
910 .map(|s| Cell::Markup((*s).to_string()))
911 .collect(),
912 None,
913 false,
914 )
915 }
916
917 pub fn add_row_with(
921 &mut self,
922 cells: Vec<Cell>,
923 style: Option<StyleType>,
924 end_section: bool,
925 ) -> &mut Self {
926 self.push_row(cells, style, end_section)
927 }
928
929 fn push_row(
932 &mut self,
933 cells: Vec<Cell>,
934 style: Option<StyleType>,
935 end_section: bool,
936 ) -> &mut Self {
937 while self.columns.len() < cells.len() {
938 let mut column = Column::new(Cell::Markup(String::new()), Justify::Left);
939 column.highlight = Some(self.highlight);
940 self.columns.push(column);
941 }
942 self.rows.push(Row {
943 cells,
944 style,
945 end_section,
946 });
947 self
948 }
949
950 pub fn add_row_text(&mut self, cells: Vec<Text>) -> &mut Self {
954 self.push_row(cells.into_iter().map(Cell::Text).collect(), None, false)
955 }
956
957 pub fn add_row_cells(&mut self, cells: Vec<Cell>) -> &mut Self {
959 self.push_row(cells, None, false)
960 }
961
962 fn extra_width(&self) -> usize {
965 if self.no_box {
966 0
967 } else {
968 (if self.show_edge { 2 } else { 0 }) + self.columns.len().saturating_sub(1)
969 }
970 }
971
972 fn padding_width(&self, index: usize) -> usize {
976 let (_, mut pad_right, _, mut pad_left) = self.padding;
977 if self.collapse_padding {
978 pad_left = 0;
979 }
980 if !self.pad_edge {
981 if index == 0 {
982 pad_left = 0;
983 }
984 if index + 1 == self.columns.len() {
985 pad_right = 0;
986 }
987 }
988 pad_left + pad_right
989 }
990
991 fn measure_padded_cell(
995 &self,
996 console: &Console,
997 options: &ConsoleOptions,
998 cell: &Cell,
999 (left, right): (usize, usize),
1000 ) -> Measurement {
1001 let max_width = options.max_width;
1002 if max_width < 1 {
1003 return Measurement::new(0, 0);
1004 }
1005 let (top, pr, bottom, pl) = self.padding;
1006 if top == 0 && pr == 0 && bottom == 0 && pl == 0 {
1007 return cell.measure_cell(console, options);
1008 }
1009 let extra_width = left + right;
1010 let width = if max_width < extra_width + 1 {
1011 Measurement::new(max_width, max_width)
1012 } else {
1013 let inner = cell.measure_cell(console, options);
1014 Measurement::new(inner.minimum + extra_width, inner.maximum + extra_width)
1015 .with_maximum(max_width)
1016 };
1017 let width = width.normalize().with_maximum(max_width);
1019 if width.maximum < 1 {
1020 Measurement::new(0, 0)
1021 } else {
1022 width.normalize()
1023 }
1024 }
1025
1026 fn measure_column(
1031 &self,
1032 console: &Console,
1033 options: &ConsoleOptions,
1034 index: usize,
1035 ) -> Measurement {
1036 let max_width = options.max_width;
1037 if max_width < 1 {
1038 return Measurement::new(0, 0);
1039 }
1040 let column = &self.columns[index];
1041 let padding_width = self.padding_width(index);
1042 if let Some(width) = column.width {
1043 return Measurement::new(width + padding_width, width + padding_width)
1045 .with_maximum(max_width);
1046 }
1047 let padding = self.cell_padding(index, self.columns.len());
1050 let empty = Cell::Markup(String::new());
1051 let header = self.show_header.then_some(&column.header);
1052 let body = self
1053 .rows
1054 .iter()
1055 .map(|row| row.cells.get(index).unwrap_or(&empty));
1056 let footer = self.show_footer.then_some(&column.footer);
1057 let mut measured = false;
1058 let (mut minimum, mut maximum) = (0, 0);
1059 for cell in header.into_iter().chain(body).chain(footer) {
1060 let width = self.measure_padded_cell(console, options, cell, padding);
1061 minimum = minimum.max(width.minimum);
1062 maximum = maximum.max(width.maximum);
1063 measured = true;
1064 }
1065 let measurement = if measured {
1066 Measurement::new(minimum, maximum)
1067 } else {
1068 Measurement::new(1, max_width)
1069 }
1070 .with_maximum(max_width);
1071 measurement.clamp(
1072 column.min_width.map(|width| width + padding_width),
1073 column.max_width.map(|width| width + padding_width),
1074 )
1075 }
1076
1077 fn column_widths(
1081 &self,
1082 console: &Console,
1083 options: &ConsoleOptions,
1084 available: usize,
1085 ) -> Vec<usize> {
1086 let options = &options.update_width(available);
1087 let max_width = available as i64;
1088 let extra_width = self.extra_width() as i64;
1089 let maximums: Vec<i64> = (0..self.columns.len())
1092 .map(|index| self.measure_column(console, options, index).maximum as i64)
1093 .collect();
1094 let mut widths: Vec<i64> = maximums.iter().map(|&width| width.max(1)).collect();
1095
1096 if self.is_expand() {
1100 let ratios: Vec<i64> = self
1101 .columns
1102 .iter()
1103 .filter(|c| c.ratio.is_some())
1104 .map(|c| i64::try_from(c.ratio.unwrap()).unwrap_or(i64::MAX))
1105 .collect();
1106 if ratios.iter().any(|&r| r > 0) {
1107 let fixed_widths: Vec<i64> = maximums
1108 .iter()
1109 .zip(&self.columns)
1110 .map(|(&w, c)| if c.ratio.is_some() { 0 } else { w })
1111 .collect();
1112 let flex_minimum: Vec<i64> = self
1113 .columns
1114 .iter()
1115 .enumerate()
1116 .filter(|(_, c)| c.ratio.is_some())
1117 .map(|(index, c)| (c.width.unwrap_or(1) + self.padding_width(index)) as i64)
1118 .collect();
1119 let flexible_width = max_width - fixed_widths.iter().sum::<i64>();
1120 let flex_widths = ratio_distribute(flexible_width, &ratios, Some(&flex_minimum));
1121 let mut iter_flex = flex_widths.into_iter();
1122 for (index, column) in self.columns.iter().enumerate() {
1123 if column.ratio.is_some() {
1124 widths[index] = fixed_widths[index] + iter_flex.next().unwrap_or(0);
1125 }
1126 }
1127 }
1128 }
1129
1130 let mut table_width: i64 = widths.iter().sum();
1131 if table_width > max_width {
1132 let wrapable: Vec<bool> = self
1135 .columns
1136 .iter()
1137 .map(|c| c.width.is_none() && !c.no_wrap)
1138 .collect();
1139 widths = collapse_widths(widths, &wrapable, max_width);
1140 table_width = widths.iter().sum();
1141 if table_width > max_width {
1144 let excess = table_width - max_width;
1145 let ratios = vec![1i64; widths.len()];
1146 widths = ratio_reduce(excess, &ratios, &widths, &widths);
1147 table_width = widths.iter().sum();
1148 }
1149 widths = widths
1154 .iter()
1155 .enumerate()
1156 .map(|(index, &width)| {
1157 self.measure_column(
1158 console,
1159 &options.update_width(width.max(0) as usize),
1160 index,
1161 )
1162 .maximum as i64
1163 })
1164 .collect();
1165 }
1166
1167 let min_width = self.min_width.map(|width| width as i64);
1171 if (table_width < max_width && self.is_expand())
1172 || min_width.is_some_and(|min_width| table_width < min_width - extra_width)
1173 {
1174 let target = match min_width {
1175 None => max_width,
1176 Some(min_width) => (min_width - extra_width).min(max_width),
1177 };
1178 if widths.iter().sum::<i64>() > 0 {
1179 let pad = ratio_distribute(target - table_width, &widths, None);
1180 for (width, extra) in widths.iter_mut().zip(pad) {
1181 *width += extra;
1182 }
1183 }
1184 }
1185 widths.into_iter().map(|w| w.max(0) as usize).collect()
1186 }
1187
1188 fn cell_padding_fitted(&self, index: usize, ncols: usize, rendered: usize) -> (usize, usize) {
1198 let (mut pl, mut pr) = self.cell_padding(index, ncols);
1199 while pl + pr > rendered {
1200 if pr > pl {
1201 pr -= 1;
1202 } else if pl > 0 {
1203 pl -= 1;
1204 } else {
1205 break;
1206 }
1207 }
1208 (pl, pr)
1209 }
1210
1211 fn cell_style(&self, console: &Console, index: usize, kind: &RowKind) -> Style {
1216 let column = self.columns.get(index);
1217 match kind {
1218 RowKind::Header => {
1219 let base = console.theme().get_style_or_null(&self.header_style);
1220 match column.and_then(|c| c.header_fill.as_ref()) {
1221 Some(fill) => base.combine(fill),
1222 None => base,
1223 }
1224 }
1225 RowKind::Footer => {
1226 let base = console.theme().get_style_or_null(&self.footer_style);
1227 match column.and_then(|c| c.footer_fill.as_ref()) {
1228 Some(fill) => base.combine(fill),
1229 None => base,
1230 }
1231 }
1232 RowKind::Body(row_style) => column
1233 .map(|c| c.style.clone())
1234 .unwrap_or_default()
1235 .combine(row_style),
1236 }
1237 }
1238
1239 fn vertical_padding(&self, first_row: bool, last_row: bool) -> (usize, usize) {
1248 let (mut top, _, mut bottom, _) = self.padding;
1249 if self.collapse_padding && !last_row {
1250 bottom = top.saturating_sub(bottom);
1251 }
1252 if !self.pad_edge {
1253 if first_row {
1254 top = 0;
1255 }
1256 if last_row {
1257 bottom = 0;
1258 }
1259 }
1260 (top, bottom)
1261 }
1262
1263 fn pad_cell_lines(
1264 &self,
1265 lines: Vec<Vec<Segment>>,
1266 width: usize,
1267 (cpl, cpr): (usize, usize),
1268 (pt, pb): (usize, usize),
1269 style: &Style,
1270 ) -> Vec<Vec<Segment>> {
1271 let cell_fill = Some(style.clone());
1272 let cell_width = cpl + width + cpr;
1273 if cell_width == 0 {
1277 return Vec::new();
1278 }
1279 let blank = || vec![Segment::new(" ".repeat(cell_width), cell_fill.clone())];
1280 let mut padded_lines: Vec<Vec<Segment>> = Vec::new();
1281 for _ in 0..pt {
1282 padded_lines.push(blank());
1283 }
1284 for line in &lines {
1285 let mut row = Vec::new();
1286 if cpl > 0 {
1287 row.push(Segment::new(" ".repeat(cpl), cell_fill.clone()));
1288 }
1289 row.extend(Segment::adjust_line_length(line, width, cell_fill.clone()));
1291 if cpr > 0 {
1292 row.push(Segment::new(" ".repeat(cpr), cell_fill.clone()));
1293 }
1294 padded_lines.push(row);
1295 }
1296 for _ in 0..pb {
1297 padded_lines.push(blank());
1298 }
1299 padded_lines
1300 }
1301
1302 #[allow(clippy::too_many_arguments)]
1304 fn render_row(
1305 &self,
1306 console: &Console,
1307 options: &ConsoleOptions,
1308 cells: &[Cell],
1309 rendered_widths: &[usize],
1310 kind: &RowKind,
1311 (first_row, last_row): (bool, bool),
1312 edges: Option<(Segment, Segment, Segment)>,
1313 row_index: usize,
1314 ) -> Vec<Vec<Segment>> {
1315 let is_header = matches!(kind, RowKind::Header);
1316 let vertical = self.vertical_padding(first_row, last_row);
1319 let ncols = self.columns.len();
1320 let paddings: Vec<(usize, usize)> = (0..ncols)
1323 .map(|index| {
1324 let rendered = rendered_widths.get(index).copied().unwrap_or(0);
1325 self.cell_padding_fitted(index, ncols, rendered)
1326 })
1327 .collect();
1328 let content_widths: Vec<usize> = rendered_widths
1329 .iter()
1330 .zip(&paddings)
1331 .map(|(w, (pl, pr))| w.saturating_sub(pl + pr))
1332 .collect();
1333
1334 let mut cell_lines: Vec<Vec<Vec<Segment>>> = Vec::with_capacity(ncols);
1336 let mut height = 1;
1337 let mut cell_regions = Vec::new();
1339 for (index, width) in content_widths.iter().enumerate() {
1340 let region = crate::protocol::enter_region(console, || {
1341 use crate::protocol::{RegionInfo, RegionRole};
1342 RegionInfo::new(match kind {
1343 RowKind::Header => RegionRole::TableHeader { column: index },
1344 RowKind::Footer => RegionRole::TableFooter { column: index },
1345 RowKind::Body(_) => RegionRole::TableCell {
1346 row: row_index,
1347 column: index,
1348 },
1349 })
1350 });
1351 if let Some(guard) = ®ion {
1353 cell_regions.push((index, guard.id()));
1354 }
1355 let style = self.cell_style(console, index, kind);
1356 let column = self.columns.get(index);
1357 let mut text = match cells.get(index) {
1358 Some(Cell::Text(text)) => text.clone(),
1359 Some(Cell::Markup(markup)) => console.render_str(
1362 markup,
1363 Some(column.and_then(|c| c.highlight).unwrap_or(self.highlight)),
1364 ),
1365 None => Text::default(),
1366 Some(Cell::Renderable(renderable)) => {
1367 let mut cell_options = options.update_width(*width);
1371 cell_options.highlight =
1372 Some(column.and_then(|c| c.highlight).unwrap_or(self.highlight));
1373 cell_options.justify = column.map_or(Justify::Left, |c| c.justify);
1374 cell_options.no_wrap = Some(column.is_some_and(|c| c.no_wrap));
1375 cell_options.overflow = Some(column.map_or(Overflow::Ellipsis, |c| c.overflow));
1376 let lines = if *width == 0 {
1377 Vec::new()
1378 } else {
1379 console.render_lines_styled(
1380 renderable.as_ref(),
1381 &cell_options,
1382 Some(&style),
1383 true,
1384 )
1385 };
1386 cell_lines.push(self.pad_cell_lines(
1387 lines,
1388 *width,
1389 paddings[index],
1390 vertical,
1391 &style,
1392 ));
1393 height = height.max(cell_lines.last().map_or(0, Vec::len));
1394 continue;
1395 }
1396 };
1397 let justify = text
1402 .get_justify_option()
1403 .unwrap_or_else(|| column.map(|c| c.justify).unwrap_or(Justify::Left));
1404 let overflow = text
1405 .get_overflow()
1406 .unwrap_or_else(|| column.map_or(Overflow::Ellipsis, |c| c.overflow));
1407 let no_wrap = text
1408 .get_no_wrap()
1409 .unwrap_or_else(|| column.map(|c| c.no_wrap).unwrap_or(false));
1410 if is_header {
1413 if let Some(span) = column.and_then(|c| c.header_content_style.clone()) {
1414 let len = text.plain().len();
1415 text.stylize(span, 0, len);
1416 }
1417 }
1418 let tab_size = text.console_tab_size(console);
1430 let mut lines: Vec<Vec<Segment>> = if *width == 0 {
1431 Vec::new()
1432 } else {
1433 text.render_lines_wrapped_tabs(
1434 console.theme(),
1435 &Style::new(),
1436 Some(*width),
1437 justify,
1438 overflow,
1439 no_wrap,
1440 tab_size,
1441 )
1442 .iter()
1443 .map(|line| Segment::apply_style(line, &style))
1444 .collect()
1445 };
1446 if lines.is_empty() && *width > 0 {
1447 lines.push(Vec::new());
1448 }
1449 let padded_lines =
1450 self.pad_cell_lines(lines, *width, paddings[index], vertical, &style);
1451 height = height.max(padded_lines.len());
1452 cell_lines.push(padded_lines);
1453 }
1454
1455 let row_height = cell_lines.iter().map(Vec::len).max().unwrap_or(0);
1461 for (index, lines) in cell_lines.iter_mut().enumerate() {
1462 let vertical = if is_header {
1466 crate::align::VerticalAlign::Bottom
1467 } else if matches!(kind, RowKind::Footer) {
1468 crate::align::VerticalAlign::Top
1469 } else {
1470 match cells.get(index) {
1471 Some(Cell::Renderable(renderable)) => renderable.vertical(),
1472 _ => None,
1473 }
1474 .unwrap_or_else(|| {
1475 self.columns
1476 .get(index)
1477 .map_or(crate::align::VerticalAlign::Top, |c| c.vertical)
1478 })
1479 };
1480 let (cpl, cpr) = paddings[index];
1481 let blank = " ".repeat(cpl + content_widths[index] + cpr);
1482 let filler = vec![Segment::new(
1483 blank.clone(),
1484 Some(self.cell_style(console, index, kind)),
1485 )];
1486 let missing = row_height.saturating_sub(lines.len());
1487 let top = match vertical {
1488 crate::align::VerticalAlign::Top => 0,
1489 crate::align::VerticalAlign::Middle => missing / 2,
1490 crate::align::VerticalAlign::Bottom => missing,
1491 };
1492 lines.splice(0..0, std::iter::repeat_n(filler.clone(), top));
1493 lines.extend(std::iter::repeat_n(filler, missing - top));
1494 while lines.len() < height {
1495 lines.push(vec![Segment::new(blank.clone(), None)]);
1496 }
1497 if let Some((_, id)) = cell_regions.iter().find(|(cell, _)| *cell == index) {
1498 for line in lines.iter_mut() {
1499 crate::protocol::tag_region(line, *id);
1500 }
1501 }
1502 }
1503
1504 let last = ncols.saturating_sub(1);
1505 let mut rows_out: Vec<Vec<Segment>> = Vec::with_capacity(height);
1506 #[allow(clippy::needless_range_loop)]
1509 for r in 0..height {
1510 let mut row = Vec::new();
1511 if let (Some((edge_left, _, _)), true) = (&edges, self.show_edge) {
1512 row.push(edge_left.clone());
1513 }
1514 for (c, column_lines) in cell_lines.iter().enumerate() {
1515 row.extend(column_lines[r].clone());
1516 let Some((_, divider, edge_right)) = &edges else {
1517 continue;
1518 };
1519 if c != last {
1520 row.push(divider.clone());
1521 } else if self.show_edge {
1522 row.push(edge_right.clone());
1523 }
1524 }
1525 rows_out.push(row);
1526 }
1527 rows_out
1528 }
1529}
1530
1531impl LineRenderable for Table {
1532 fn try_for_each_line<E>(
1540 &self,
1541 console: &Console,
1542 options: &ConsoleOptions,
1543 emit: impl FnMut(Vec<Segment>) -> Result<(), E>,
1544 ) -> Result<(), E> {
1545 let region = crate::protocol::enter_region(console, || {
1547 let info = crate::protocol::RegionInfo::new(crate::protocol::RegionRole::Table);
1548 match &self.title {
1549 Some(title) => info.label(annotation_plain(console, title)),
1550 None => info,
1551 }
1552 });
1553 let mut emit = {
1554 let mut emit = emit;
1555 let region = region.as_ref().map(|guard| guard.id());
1556 move |mut line: Vec<Segment>| {
1557 if let Some(id) = region {
1558 crate::protocol::tag_region(&mut line, id);
1559 }
1560 emit(line)
1561 }
1562 };
1563 if self.columns.is_empty() {
1564 return emit(vec![Segment::new("", None)]);
1565 }
1566 let box_set = self
1569 .box_set
1570 .substitute_for(options, self.safe_box.unwrap_or_else(|| console.safe_box()));
1571 let box_set = if self.show_header {
1572 box_set
1573 } else {
1574 box_set.get_plain_headed_box()
1575 };
1576 let max_width = self.width.unwrap_or(options.max_width);
1578 let extra_width = self.extra_width();
1579 let available = max_width.saturating_sub(extra_width);
1580
1581 let rendered_widths = self.column_widths(console, options, available);
1582 let border_style = self.style.combine(&self.border_style);
1583 let border = Some(border_style.clone());
1584
1585 let table_width: usize = rendered_widths.iter().sum::<usize>() + extra_width;
1587
1588 if let Some(title) = &self.title {
1589 let style = self
1590 .title_style
1591 .clone()
1592 .unwrap_or_else(|| StyleType::Name("table.title".to_string()));
1593 for line in render_annotation(
1594 console,
1595 options,
1596 title,
1597 &style,
1598 self.title_justify,
1599 table_width,
1600 ) {
1601 emit(line)?;
1602 }
1603 }
1604
1605 let edge = self.show_edge;
1606 let boxed = !self.no_box;
1607 if boxed && edge {
1608 emit(vec![Segment::new(
1609 box_set.get_top(&rendered_widths, edge),
1610 border.clone(),
1611 )])?;
1612 }
1613
1614 let segments = |(left, vertical, right): (char, char, char)| {
1617 (
1618 Segment::new(left.to_string(), border.clone()),
1619 Segment::new(vertical.to_string(), border.clone()),
1620 Segment::new(right.to_string(), border.clone()),
1621 )
1622 };
1623 let head_edges = segments((box_set.head_left, box_set.head_vertical, box_set.head_right));
1624 let mid_edges = segments((box_set.mid_left, box_set.mid_vertical, box_set.mid_right));
1625 let foot_edges = segments((box_set.foot_left, box_set.foot_vertical, box_set.foot_right));
1626
1627 let mut row_list: Vec<(RowKind, &[Cell], Option<&Row>)> = Vec::new();
1629 let header_cells: Vec<Cell> = self.columns.iter().map(|c| c.header.clone()).collect();
1630 let footer_cells: Vec<Cell> = self.columns.iter().map(|c| c.footer.clone()).collect();
1631 if self.show_header {
1632 row_list.push((RowKind::Header, &header_cells, None));
1633 }
1634 for (index, row) in self.rows.iter().enumerate() {
1635 row_list.push((
1636 RowKind::Body(self.get_row_style(console, index)),
1637 &row.cells,
1638 Some(row),
1639 ));
1640 }
1641 if self.show_footer {
1642 row_list.push((RowKind::Footer, &footer_cells, None));
1643 }
1644 let total = row_list.len();
1645 for (index, (kind, cells, row)) in row_list.iter().enumerate() {
1646 let first = index == 0;
1647 let last = index + 1 == total;
1648 let header_row = first && self.show_header;
1649 if boxed && last && self.show_footer {
1650 emit(vec![Segment::new(
1651 box_set.get_row(&rendered_widths, RowLevel::Foot, edge),
1652 border.clone(),
1653 )])?;
1654 }
1655 let edges = boxed.then(|| {
1656 let (left, divider, right) = if first {
1657 head_edges.clone()
1658 } else if last {
1659 foot_edges.clone()
1660 } else {
1661 mid_edges.clone()
1662 };
1663 let divider = if divider.text.trim().is_empty() {
1665 let background = match kind {
1666 RowKind::Body(row_style) => {
1667 Style::from_color(None, row_style.bgcolor().cloned())
1668 }
1669 _ => Style::new(),
1670 };
1671 Segment::new(
1672 divider.text.clone(),
1673 Some(background.combine(&border_style)),
1674 )
1675 } else {
1676 divider
1677 };
1678 (left, divider, right)
1679 });
1680 for line in self.render_row(
1681 console,
1682 options,
1683 cells,
1684 &rendered_widths,
1685 kind,
1686 (first, last),
1687 edges,
1688 index - usize::from(self.show_header && !matches!(kind, RowKind::Header)),
1689 ) {
1690 emit(line)?;
1691 }
1692 if boxed && header_row {
1693 emit(vec![Segment::new(
1694 box_set.get_row(&rendered_widths, RowLevel::Head, edge),
1695 border.clone(),
1696 )])?;
1697 }
1698 let end_section = row.is_some_and(|row| row.end_section);
1699 if boxed
1700 && (self.show_lines || self.leading > 0 || end_section)
1701 && !last
1702 && !(self.show_footer && index + 2 >= total)
1703 && !header_row
1704 {
1705 let line = if self.leading > 0 {
1706 box_set
1708 .get_row(&rendered_widths, RowLevel::Mid, edge)
1709 .repeat(self.leading)
1710 } else {
1711 box_set.get_row(&rendered_widths, RowLevel::Row, edge)
1712 };
1713 emit(vec![Segment::new(line, border.clone())])?;
1714 }
1715 }
1716
1717 if boxed && edge {
1718 emit(vec![Segment::new(
1719 box_set.get_bottom(&rendered_widths, edge),
1720 border.clone(),
1721 )])?;
1722 }
1723
1724 if let Some(caption) = &self.caption {
1725 let style = self
1726 .caption_style
1727 .clone()
1728 .unwrap_or_else(|| StyleType::Name("table.caption".to_string()));
1729 for line in render_annotation(
1730 console,
1731 options,
1732 caption,
1733 &style,
1734 self.caption_justify,
1735 table_width,
1736 ) {
1737 emit(line)?;
1738 }
1739 }
1740
1741 Ok(())
1742 }
1743}
1744
1745impl crate::protocol::OwnedTableRows for Table {
1746 fn extend_owned_rows(&mut self, rows: Vec<Vec<String>>) -> &mut Self {
1747 for row in rows {
1748 self.push_row(row.into_iter().map(Cell::Markup).collect(), None, false);
1749 }
1750 self
1751 }
1752}
1753
1754impl Renderable for Table {
1755 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
1759 let max_width = self.width.unwrap_or(options.max_width);
1760 if self.columns.is_empty() {
1761 let width = usize::from(!self.no_box && self.show_edge);
1765 return Measurement::new(width, self.width.unwrap_or(width))
1766 .clamp(self.min_width, None);
1767 }
1768 let extra_width = self.extra_width();
1769 let columns_width: usize = self
1770 .column_widths(console, options, max_width.saturating_sub(extra_width))
1771 .iter()
1772 .sum();
1773 let options = options.update_width(columns_width);
1774 let (minimum, maximum) = (0..self.columns.len())
1775 .map(|index| self.measure_column(console, &options, index))
1776 .fold((0, 0), |(minimum, maximum), width| {
1777 (minimum + width.minimum, maximum + width.maximum)
1778 });
1779 let maximum = self.width.unwrap_or(maximum + extra_width);
1780 Measurement::new(minimum + extra_width, maximum).clamp(self.min_width, None)
1781 }
1782
1783 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
1784 let mut segments = Vec::new();
1785 let mut first = true;
1786 let result: Result<(), std::convert::Infallible> =
1787 self.try_for_each_line(console, options, |line| {
1788 if !first {
1789 segments.push(Segment::line());
1790 }
1791 first = false;
1792 segments.extend(line);
1793 Ok(())
1794 });
1795 match result {
1796 Ok(()) => segments,
1797 Err(never) => match never {},
1798 }
1799 }
1800}
1801
1802fn annotation_plain(console: &Console, annotation: &Cell) -> String {
1808 match annotation {
1809 Cell::Markup(markup) => console.render_str(markup, Some(false)).plain().to_string(),
1810 Cell::Text(text) => text.plain().to_string(),
1811 Cell::Renderable(_) => String::new(),
1812 }
1813}
1814
1815fn render_annotation(
1816 console: &Console,
1817 options: &ConsoleOptions,
1818 annotation: &Cell,
1819 style: &StyleType,
1820 justify: Justify,
1821 width: usize,
1822) -> Vec<Vec<Segment>> {
1823 let text = match annotation {
1824 Cell::Markup(markup) => {
1825 if markup.is_empty() {
1826 return Vec::new();
1827 }
1828 let mut text = console.render_str(markup, Some(false));
1829 text.set_base_style(style.clone());
1830 text
1831 }
1832 Cell::Text(text) => {
1833 if text.plain().is_empty() {
1834 return Vec::new();
1835 }
1836 text.clone()
1837 }
1838 Cell::Renderable(_) => return Vec::new(),
1839 };
1840 if width == 0 {
1841 return Vec::new();
1842 }
1843 let justify = text.get_justify_option().unwrap_or(justify);
1844 let overflow = text
1845 .get_overflow()
1846 .or(options.overflow)
1847 .unwrap_or(Overflow::Fold);
1848 let no_wrap = text.get_no_wrap().or(options.no_wrap).unwrap_or(false);
1849 let tab_size = text.console_tab_size(console);
1850 text.render_lines_wrapped_tabs(
1851 console.theme(),
1852 console.base_style(),
1853 Some(width),
1854 justify,
1855 overflow,
1856 no_wrap,
1857 tab_size,
1858 )
1859}
1860
1861fn collapse_widths(mut widths: Vec<i64>, wrapable: &[bool], max_width: i64) -> Vec<i64> {
1864 let mut total_width: i64 = widths.iter().sum();
1865 let mut excess_width = total_width - max_width;
1866 if wrapable.iter().any(|&w| w) {
1867 while total_width != 0 && excess_width > 0 {
1868 let max_column = widths
1869 .iter()
1870 .zip(wrapable)
1871 .filter(|(_, &w)| w)
1872 .map(|(&x, _)| x)
1873 .max()
1874 .unwrap_or(0);
1875 let second_max_column = widths
1876 .iter()
1877 .zip(wrapable)
1878 .map(|(&x, &w)| if w && x != max_column { x } else { 0 })
1879 .max()
1880 .unwrap_or(0);
1881 let column_difference = max_column - second_max_column;
1882 let ratios: Vec<i64> = widths
1883 .iter()
1884 .zip(wrapable)
1885 .map(|(&x, &w)| i64::from(x == max_column && w))
1886 .collect();
1887 if !ratios.iter().any(|&r| r != 0) || column_difference == 0 {
1888 break;
1889 }
1890 let max_reduce = vec![excess_width.min(column_difference); widths.len()];
1891 widths = ratio_reduce(excess_width, &ratios, &max_reduce, &widths);
1892 total_width = widths.iter().sum();
1893 excess_width = total_width - max_width;
1894 }
1895 }
1896 widths
1897}
1898
1899#[cfg(test)]
1900mod tests {
1901
1902 #[test]
1903 fn a_huge_column_ratio_does_not_overflow() {
1904 let console = crate::Console::builder()
1907 .width(40)
1908 .color_system(None)
1909 .build();
1910 let mut table = Table::new().expand(true);
1911 table.add_column("a").column_ratio(usize::MAX);
1912 table.add_column("b").column_ratio(1);
1913 table.add_row(&["x", "y"]);
1914 assert_eq!(
1915 console.render_to_string(&table) + "\n",
1916 "┏━━━┳━━━┓\n┃ a ┃ b ┃\n┡━━━╇━━━┩\n│ x │ y │\n└───┴───┘\n"
1917 );
1918 }
1919
1920 use super::*;
1921 use crate::color::ColorSystem;
1922 use crate::r#box::SQUARE;
1923
1924 #[test]
1925 fn text_cells_are_never_markup() {
1926 let console = Console::builder().width(80).color_system(None).build();
1927 let data = ["[bold]model[/]", "col[/b]", ":sparkles:"];
1929 let mut plain = Table::new();
1930 plain.add_column("a").add_column("b").add_column("c");
1931 plain.add_row_text(data.iter().map(|s| Text::new(*s)).collect());
1932 let rendered = console.render_to_string(&plain);
1933 for cell in data {
1934 assert!(rendered.contains(cell), "{cell}: {rendered}");
1935 }
1936 let mut cells = Table::new();
1938 cells.add_column("a").add_column("b").add_column("c");
1939 cells.add_row_cells(vec![
1940 Cell::from(Text::new("[bold]model[/]")),
1941 Cell::Markup("[bold]model[/]".into()),
1942 Cell::from("[bold]model[/]"),
1943 ]);
1944 let rendered = console.render_to_string(&cells);
1945 assert_eq!(rendered.matches("[bold]model[/]").count(), 1, "{rendered}");
1946 assert_eq!(rendered.matches("model").count(), 3, "{rendered}");
1947 }
1948
1949 fn console() -> Console {
1950 Console::builder()
1951 .force_terminal(true)
1952 .color_system(Some(ColorSystem::Truecolor))
1953 .width(40)
1954 .no_color(false)
1955 .build()
1956 }
1957
1958 #[test]
1959 fn owned_rows_preserve_measurement_styles_and_missing_cells() {
1960 use crate::protocol::OwnedTableRows;
1961 for width in [1, 12, 40, 80] {
1962 let console = Console::builder().width(width).force_terminal(true).build();
1963 let build = || {
1964 let mut table = Table::new()
1965 .title("Rows")
1966 .caption("owned or borrowed")
1967 .show_lines(true);
1968 table.add_column("Name");
1969 table.add_column_justify("Value", Justify::Right);
1970 table
1971 };
1972 let mut borrowed = build();
1973 let mut owned = build();
1974 for row in [
1975 vec!["漢字\n🙂", "123"],
1976 vec!["short"],
1977 vec!["extra", "4", "ignored"],
1978 ] {
1979 borrowed.add_row(&row);
1980 owned.extend_owned_rows(vec![row.into_iter().map(str::to_owned).collect()]);
1981 }
1982 assert_eq!(
1983 console.render_to_string(&borrowed),
1984 console.render_to_string(&owned)
1985 );
1986 }
1987 }
1988
1989 #[test]
1990 fn simple_square_table() {
1991 let mut table = Table::new().box_set(SQUARE);
1992 table.add_column("Name");
1993 table.add_column("Age");
1994 table.add_row(&["Alice", "30"]);
1995 table.add_row(&["Bob", "7"]);
1996 let out = console().render_export(&table);
1997 let expected = concat!(
1998 "┌───────┬─────┐\n",
1999 "│\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",
2000 "├───────┼─────┤\n",
2001 "│ Alice │ 30 │\n",
2002 "│ Bob │ 7 │\n",
2003 "└───────┴─────┘\n",
2004 );
2005 assert_eq!(out, expected);
2006 }
2007
2008 #[test]
2009 fn streamed_lines_match_styled_table_output() {
2010 let mut table = Table::new().box_set(SQUARE);
2011 table.add_column("Name");
2012 table.add_column("Age");
2013 table.add_row(&["Alice", "30"]);
2014 table.add_row(&["Bob", "7"]);
2015 let console = console();
2016 let mut streamed = String::new();
2017 table
2018 .try_for_each_line(&console, &console.options(), |line| {
2019 assert!(line.iter().all(|segment| !segment.text.contains('\n')));
2020 streamed.push_str(&console.segments_to_string(&line));
2021 streamed.push('\n');
2022 Ok::<_, std::convert::Infallible>(())
2023 })
2024 .unwrap();
2025 assert_eq!(streamed, console.render_export(&table));
2028 assert_eq!(streamed.lines().count(), 6);
2029 }
2030
2031 #[test]
2032 fn streamed_lines_stop_at_the_first_writer_error() {
2033 let mut table = Table::new()
2034 .box_set(SQUARE)
2035 .title("People")
2036 .caption("End")
2037 .show_lines(true);
2038 table.add_column("Name");
2039 table.add_row(&["Alice\nBob"]);
2040 table.add_row(&["Carol"]);
2041 let console = console();
2042 let mut visits = 0;
2043 let result = table.try_for_each_line(&console, &console.options(), |_| {
2044 visits += 1;
2045 if visits == 5 {
2046 Err("writer failed")
2047 } else {
2048 Ok(())
2049 }
2050 });
2051 assert_eq!(result, Err("writer failed"));
2052 assert_eq!(visits, 5);
2053 }
2054
2055 #[test]
2060 fn a_column_narrower_than_its_padding_stays_inside_the_border() {
2061 for ncols in [20usize, 29, 40] {
2062 let mut table = Table::new().box_set(SQUARE);
2063 for i in 0..ncols {
2064 table.add_column(format!("c{i}"));
2065 }
2066 let row: Vec<String> = (0..ncols).map(|i| i.to_string()).collect();
2067 table.add_row(&row.iter().map(String::as_str).collect::<Vec<_>>());
2068 let console = Console::builder().width(80).color_system(None).build();
2069 let out = console.render_to_string(&table);
2070 let rows: Vec<&str> = out.lines().filter(|l| !l.trim().is_empty()).collect();
2071 let widths: Vec<usize> = rows.iter().map(|r| r.chars().count()).collect();
2072 assert!(
2073 widths.iter().all(|w| *w == widths[0]),
2074 "{ncols} columns produced ragged rows: {widths:?}"
2075 );
2076 for (index, row) in rows.iter().enumerate() {
2077 let last = row.chars().last().expect("non-empty row");
2078 assert!(
2079 !last.is_whitespace(),
2080 "{ncols} columns: row {index} lost its right border: {row:?}"
2081 );
2082 }
2083 }
2084 }
2085
2086 #[test]
2091 fn a_multi_line_cell_is_measured_by_its_widest_line() {
2092 let mut table = Table::new().box_set(SQUARE);
2093 table.add_column("name");
2094 table.add_column("bio");
2095 table.add_row(&["Alice", "line one\nline two is much longer"]);
2096 table.add_row(&["Bob", "short"]);
2097 let console = Console::builder().width(60).color_system(None).build();
2098 let out = console.render_to_string(&table);
2099 let top = out.lines().next().expect("a top border");
2100 let width = top.chars().count();
2101 assert!(
2103 width < 40,
2104 "the multi-line cell was measured as the sum of its lines: {width} wide"
2105 );
2106 assert!(
2107 out.contains("line two is much longer"),
2108 "content lost: {out:?}"
2109 );
2110 }
2111}