1use crate::align::{AlignMethod, VerticalAlignMethod};
46use crate::box_drawing::{get_safe_box, BoxStyle, BOX_HEAVY_HEAD};
47use crate::console::{ConsoleOptions, OverflowMethod, RenderResult, Renderable};
48use crate::segment::Segment;
49use crate::style::Style;
50use std::collections::HashSet;
51use unicode_width::UnicodeWidthStr;
52
53#[derive(Debug, Clone)]
59pub struct Cell {
60 pub content: String,
62 pub style: Option<Style>,
64 pub colspan: usize,
66 pub rowspan: usize,
68}
69
70impl Cell {
71 pub fn new(content: impl Into<String>) -> Self {
73 Cell {
74 content: content.into(),
75 style: None,
76 colspan: 1,
77 rowspan: 1,
78 }
79 }
80
81 pub fn style(mut self, s: Style) -> Self { self.style = Some(s); self }
83 pub fn colspan(mut self, c: usize) -> Self { self.colspan = c; self }
85 pub fn rowspan(mut self, r: usize) -> Self { self.rowspan = r; self }
87}
88
89impl From<String> for Cell {
90 fn from(s: String) -> Self { Cell::new(s) }
91}
92
93impl From<&str> for Cell {
94 fn from(s: &str) -> Self { Cell::new(s) }
95}
96
97#[derive(Debug, Clone)]
103pub struct Column {
104 pub header: String,
106 pub footer: String,
108 pub header_style: Style,
110 pub footer_style: Style,
112 pub style: Style,
114 pub justify: AlignMethod,
116 pub vertical: VerticalAlignMethod,
118 pub overflow: OverflowMethod,
120 pub width: Option<usize>,
122 pub min_width: Option<usize>,
124 pub max_width: Option<usize>,
126 pub ratio: Option<usize>,
128 pub colspan: usize,
130}
131
132impl Column {
133 pub fn new(header: impl Into<String>) -> Self {
135 Self {
136 header: header.into(),
137 footer: String::new(),
138 header_style: Style::new().bold(true),
139 footer_style: Style::new(),
140 style: Style::new(),
141 justify: AlignMethod::Left,
142 vertical: VerticalAlignMethod::Top,
143 overflow: OverflowMethod::Ellipsis,
144 width: None,
145 min_width: None,
146 max_width: None,
147 ratio: None,
148 colspan: 1,
149 }
150 }
151
152 pub fn justify(mut self, j: AlignMethod) -> Self { self.justify = j; self }
154 pub fn width(mut self, w: usize) -> Self { self.width = Some(w); self }
156 pub fn min_width(mut self, w: usize) -> Self { self.min_width = Some(w); self }
158 pub fn max_width(mut self, w: usize) -> Self { self.max_width = Some(w); self }
160 pub fn style(mut self, s: Style) -> Self { self.style = s; self }
162 pub fn header_style(mut self, s: Style) -> Self { self.header_style = s; self }
164 pub fn ratio(mut self, r: usize) -> Self { self.ratio = Some(r); self }
166 pub fn overflow(mut self, o: OverflowMethod) -> Self { self.overflow = o; self }
168}
169
170#[derive(Debug, Clone)]
176pub struct Table {
177 columns: Vec<Column>,
178 rows: Vec<Vec<Cell>>,
179 pub title: Option<String>,
181 pub caption: Option<String>,
183 pub box_style: BoxStyle,
185 pub show_header: bool,
187 pub show_footer: bool,
189 pub show_edge: bool,
191 pub show_lines: bool,
193 pub padding: (usize, usize, usize, usize),
195 pub collapse_padding: bool,
197 pub style: Style,
199 pub border_style: Style,
201 pub title_style: Style,
203 pub caption_style: Style,
205 pub title_justify: AlignMethod,
207 pub caption_justify: AlignMethod,
209 pub highlight: bool,
211 pub width: Option<usize>,
213 pub row_styles: Vec<Style>,
215 pub leading: usize,
217 pub rowspans: Vec<usize>,
219 pub section_rows: HashSet<usize>,
221}
222
223impl Table {
224 pub fn new() -> Self {
226 Self {
227 columns: Vec::new(),
228 rows: Vec::new(),
229 title: None,
230 caption: None,
231 box_style: BOX_HEAVY_HEAD.clone(),
232 show_header: true,
233 show_footer: false,
234 show_edge: true,
235 show_lines: false,
236 padding: (0, 1, 0, 1),
237 collapse_padding: false,
238 style: Style::new(),
239 border_style: Style::new(),
240 title_style: Style::new().bold(true),
241 caption_style: Style::new().dim(true),
242 title_justify: AlignMethod::Center,
243 caption_justify: AlignMethod::Center,
244 highlight: false,
245 width: None,
246 row_styles: Vec::new(),
247 leading: 0,
248 rowspans: Vec::new(),
249 section_rows: HashSet::new(),
250 }
251 }
252
253 pub fn add_column(&mut self, column: Column) {
267 self.columns.push(column);
268 }
269
270 pub fn add_row(&mut self, row: Vec<Cell>) {
283 self.rows.push(row);
284 }
285
286 pub fn add_row_str(&mut self, row: Vec<String>) {
299 let cells: Vec<Cell> = row.into_iter().map(Cell::new).collect();
300 self.rows.push(cells);
301 }
302
303 pub fn column(mut self, col: Column) -> Self { self.add_column(col); self }
305
306 pub fn row(mut self, row: Vec<Cell>) -> Self { self.add_row(row); self }
308
309 pub fn row_str(mut self, row: Vec<String>) -> Self { self.add_row_str(row); self }
311
312 pub fn title(mut self, t: impl Into<String>) -> Self { self.title = Some(t.into()); self }
314
315 pub fn caption(mut self, t: impl Into<String>) -> Self { self.caption = Some(t.into()); self }
317
318 pub fn box_style(mut self, bs: BoxStyle) -> Self { self.box_style = bs; self }
320
321 pub fn border_style(mut self, s: Style) -> Self { self.border_style = s; self }
323
324 pub fn hide_header(mut self) -> Self { self.show_header = false; self }
326
327 pub fn show_lines(mut self) -> Self { self.show_lines = true; self }
329
330 pub fn leading(mut self, l: usize) -> Self { self.leading = l; self }
332
333 pub fn grid() -> Self {
336 Self {
337 columns: Vec::new(),
338 rows: Vec::new(),
339 title: None,
340 caption: None,
341 box_style: crate::box_drawing::BOX_SIMPLE.clone(),
342 show_header: false,
343 show_footer: false,
344 show_edge: false,
345 show_lines: false,
346 padding: (0, 1, 0, 1),
347 collapse_padding: false,
348 style: Style::new(),
349 border_style: Style::new(),
350 title_style: Style::new().bold(true),
351 caption_style: Style::new().dim(true),
352 title_justify: AlignMethod::Center,
353 caption_justify: AlignMethod::Center,
354 highlight: false,
355 width: None,
356 row_styles: Vec::new(),
357 leading: 0,
358 rowspans: Vec::new(),
359 section_rows: HashSet::new(),
360 }
361 }
362
363 pub fn add_section(&mut self) {
366 self.section_rows.insert(self.rows.len());
367 }
368
369 pub fn row_count(&self) -> usize { self.rows.len() }
371}
372
373impl Renderable for Table {
374 fn render(&self, options: &ConsoleOptions) -> RenderResult {
375 if self.columns.is_empty() {
376 return RenderResult::new();
377 }
378
379 let box_style = get_safe_box(&self.box_style, options.ascii_only);
380 let available_width = self.width.unwrap_or(options.max_width);
381 let col_count = self.columns.len();
382
383 let col_widths = self.calculate_column_widths(available_width);
385
386 let mut lines: Vec<Vec<Segment>> = Vec::new();
387 let b = &box_style;
388
389 let bs = |ch: char| -> Segment {
391 let ansi = self.border_style.to_ansi();
392 let reset = if ansi.is_empty() { "" } else { "\x1b[0m" };
393 Segment::new(format!("{ansi}{ch}{reset}"))
394 };
395
396 if let Some(ref title) = self.title {
398 let _tw = UnicodeWidthStr::width(title.as_str());
399 let centered = self.title_justify.align_text(title, available_width.saturating_sub(2));
400 lines.push(vec![bs(b.top_left), Segment::new(¢ered[1..centered.len()-1]), bs(b.top_right), Segment::line()]);
401 }
402
403 if self.show_edge {
405 let mut top_line = vec![bs(b.top_left)];
406 for (i, w) in col_widths.iter().enumerate() {
407 top_line.push(Segment::new(b.top.to_string().repeat(*w)));
408 if i < col_count - 1 {
409 top_line.push(bs(b.top_divider));
410 }
411 }
412 top_line.push(bs(b.top_right));
413 top_line.push(Segment::line());
414 lines.push(top_line);
415 }
416
417 if self.show_header && self.columns.iter().any(|c| !c.header.is_empty()) {
419 let (pt, _pr, _pb, _pl) = self.padding;
421 for _ in 0..pt {
422 lines.push(self.render_row_line(&col_widths, &[], &b, available_width, false));
423 }
424
425 let header_cells: Vec<String> = self.columns.iter()
426 .map(|c| c.header.clone())
427 .collect();
428 lines.push(self.render_cell_line(&col_widths, &header_cells, &b, true));
429
430 let mut sep = vec![bs(b.head_row_left)];
432 for (i, w) in col_widths.iter().enumerate() {
433 sep.push(Segment::new(b.head_row_horizontal.to_string().repeat(*w)));
434 if i < col_count - 1 {
435 sep.push(bs(b.head_row_cross));
436 }
437 }
438 sep.push(bs(b.head_row_right));
439 sep.push(Segment::line());
440 lines.push(sep);
441 }
442
443 let mut rowspan_remaining: Vec<usize> = vec![0; col_count];
445 for (row_idx, row) in self.rows.iter().enumerate() {
446 if self.section_rows.contains(&row_idx) {
448 let mut sep = vec![bs(b.head_row_left)];
449 for (i, w) in col_widths.iter().enumerate() {
450 sep.push(Segment::new(b.head_row_horizontal.to_string().repeat(*w)));
451 if i < col_count - 1 {
452 sep.push(bs(b.head_row_cross));
453 }
454 }
455 sep.push(bs(b.head_row_right));
456 sep.push(Segment::line());
457 lines.push(sep);
458 }
459
460 if row_idx > 0 {
462 for _ in 0..self.leading {
463 lines.push(self.render_row_line(&col_widths, &[], &b, available_width, false));
464 }
465 }
466
467 let (pt, _pr, _pb, _pl) = self.padding;
468 for _ in 0..pt {
469 lines.push(self.render_row_line(&col_widths, &[], &b, available_width, false));
470 }
471
472 let _style = if row_idx < self.row_styles.len() {
473 Some(&self.row_styles[row_idx])
474 } else if self.row_styles.len() == 2 {
475 Some(&self.row_styles[row_idx % 2])
476 } else {
477 None
478 };
479
480 lines.push(self.render_cell_line_with_rowspan(
481 &col_widths, row, &b, false, &mut rowspan_remaining,
482 ));
483
484 if self.show_lines && row_idx < self.rows.len() - 1 {
486 let mut sep = vec![bs(b.row_left)];
487 for (i, w) in col_widths.iter().enumerate() {
488 sep.push(Segment::new(b.row_horizontal.to_string().repeat(*w)));
489 if i < col_count - 1 {
490 sep.push(bs(b.row_cross));
491 }
492 }
493 sep.push(bs(b.row_right));
494 sep.push(Segment::line());
495 lines.push(sep);
496 }
497 }
498
499 if self.show_footer && self.columns.iter().any(|c| !c.footer.is_empty()) {
501 let mut sep = vec![bs(b.foot_row_left)];
502 for (i, w) in col_widths.iter().enumerate() {
503 sep.push(Segment::new(b.foot_row_horizontal.to_string().repeat(*w)));
504 if i < col_count - 1 {
505 sep.push(bs(b.foot_row_cross));
506 }
507 }
508 sep.push(bs(b.foot_row_right));
509 sep.push(Segment::line());
510 lines.push(sep);
511
512 let footer_cells: Vec<String> = self.columns.iter()
513 .map(|c| c.footer.clone())
514 .collect();
515 lines.push(self.render_cell_line(&col_widths, &footer_cells, &b, false));
516 }
517
518 if self.show_edge {
520 let mut bot_line = vec![bs(b.bottom_left)];
521 for (i, w) in col_widths.iter().enumerate() {
522 bot_line.push(Segment::new(b.bottom.to_string().repeat(*w)));
523 if i < col_count - 1 {
524 bot_line.push(bs(b.bottom_divider));
525 }
526 }
527 bot_line.push(bs(b.bottom_right));
528 bot_line.push(Segment::line());
529 lines.push(bot_line);
530 }
531
532 if let Some(ref caption) = self.caption {
534 let centered = self.caption_justify.align_text(caption, available_width.saturating_sub(2));
535 lines.push(vec![Segment::new(¢ered), Segment::line()]);
536 }
537
538 RenderResult { lines, items: Vec::new() }
539 }
540}
541
542impl Table {
543 fn calculate_column_widths(&self, available: usize) -> Vec<usize> {
544 let col_count = self.columns.len();
545 let total_pad = col_count.saturating_sub(1) + 2; let content_width = available.saturating_sub(total_pad);
547
548 let mut widths: Vec<usize> = vec![0; col_count];
550 let mut flex_cols: Vec<usize> = Vec::new();
551 let mut used = 0usize;
552
553 for (i, col) in self.columns.iter().enumerate() {
554 if let Some(w) = col.width {
555 widths[i] = w;
556 used += w;
557 } else {
558 flex_cols.push(i);
559 }
560 }
561
562 if flex_cols.is_empty() {
563 return widths;
564 }
565
566 let remaining = content_width.saturating_sub(used);
567 let _flex_count = flex_cols.len();
568
569 let total_ratio: usize = flex_cols.iter()
571 .map(|&i| self.columns[i].ratio.unwrap_or(1))
572 .sum();
573
574 for &i in &flex_cols {
575 let col = &self.columns[i];
576 let ratio = col.ratio.unwrap_or(1);
577 let mut w = (remaining * ratio) / total_ratio;
578 if let Some(min_w) = col.min_width {
579 w = w.max(min_w);
580 }
581 if let Some(max_w) = col.max_width {
582 w = w.min(max_w);
583 }
584 w = w.max(3); widths[i] = w;
586 }
587
588 let total: usize = widths.iter().sum();
590 if total < content_width && !flex_cols.is_empty() {
591 let extra = content_width - total;
592 widths[flex_cols[flex_cols.len() - 1]] += extra;
593 }
594
595 widths
596 }
597
598 fn render_cell_line(
599 &self,
600 widths: &[usize],
601 values: &[String],
602 b: &BoxStyle,
603 is_header: bool,
604 ) -> Vec<Segment> {
605 let mut line = Vec::new();
606 let bs = |ch: char| -> Segment {
607 let ansi = self.border_style.to_ansi();
608 let reset = if ansi.is_empty() { "" } else { "\x1b[0m" };
609 Segment::new(format!("{ansi}{ch}{reset}"))
610 };
611
612 line.push(bs(b.mid_vertical));
613
614 for (i, w) in widths.iter().enumerate() {
615 let val = values.get(i).map(|s| s.as_str()).unwrap_or("");
616 let col = self.columns.get(i);
617 let justify = col.map(|c| c.justify).unwrap_or(AlignMethod::Left);
618 let (_pt, pr, _pb, pl) = self.padding;
619
620 line.push(Segment::new(" ".repeat(pl)));
622
623 let disp = justify.align_text(val, w.saturating_sub(pl + pr));
625 let disp_trunc = if UnicodeWidthStr::width(disp.as_str()) > *w {
627 let mut truncated = disp.chars().take(
628 w.saturating_sub(1) ).collect::<String>();
630 truncated.push('…');
631 truncated
632 } else {
633 disp
634 };
635
636 if is_header {
638 let header_style = col.map(|c| &c.header_style);
639 if let Some(hs) = header_style {
640 let ansi = hs.to_ansi();
641 let reset = hs.reset_ansi();
642 line.push(Segment::new(format!("{ansi}{disp_trunc}{reset}")));
643 } else {
644 line.push(Segment::new(disp_trunc));
645 }
646 } else {
647 line.push(Segment::new(disp_trunc));
648 }
649
650 line.push(Segment::new(" ".repeat(pr)));
652
653 if i < widths.len() - 1 {
654 line.push(bs(b.mid_vertical));
655 }
656 }
657
658 line.push(bs(b.mid_right));
659 line.push(Segment::line());
660 line
661 }
662
663 fn render_cell_line_with_rowspan(
666 &self,
667 widths: &[usize],
668 cells: &[Cell],
669 b: &BoxStyle,
670 is_header: bool,
671 rowspan_remaining: &mut [usize],
672 ) -> Vec<Segment> {
673 let mut line = Vec::new();
674 let bs = |ch: char| -> Segment {
675 let ansi = self.border_style.to_ansi();
676 let reset = if ansi.is_empty() { "" } else { "\x1b[0m" };
677 Segment::new(format!("{ansi}{ch}{reset}"))
678 };
679
680 line.push(bs(b.mid_vertical));
681
682 let col_count = widths.len();
683 let mut cell_idx = 0;
684 let mut col: usize = 0;
685
686 while col < col_count {
687 if rowspan_remaining[col] > 0 {
689 rowspan_remaining[col] -= 1;
690 let w = widths[col];
692 let (_pt, pr, _pb, pl) = self.padding;
693 line.push(Segment::new(" ".repeat(pl + w + pr)));
694 if col < col_count - 1 {
695 line.push(bs(b.mid_vertical));
696 }
697 col += 1;
698 continue;
699 }
700
701 if cell_idx >= cells.len() {
703 let w = widths[col];
704 let (_pt, pr, _pb, pl) = self.padding;
705 line.push(Segment::new(" ".repeat(pl + w + pr)));
706 if col < col_count - 1 {
707 line.push(bs(b.mid_vertical));
708 }
709 col += 1;
710 continue;
711 }
712
713 let cell = &cells[cell_idx];
714 cell_idx += 1;
715
716 let span_end = (col + cell.colspan).min(col_count);
717 let span_width: usize = widths[col..span_end].iter().sum();
718 let (_pt, pr, _pb, pl) = self.padding;
719 let content_width = span_width.saturating_sub(pl + pr);
720
721 let col_def = self.columns.get(col);
722 let justify = col_def.map(|c| c.justify).unwrap_or(AlignMethod::Left);
723
724 let disp_text = justify.align_text(&cell.content, content_width);
726 let disp_trunc = if UnicodeWidthStr::width(disp_text.as_str()) > content_width {
727 let mut truncated: String = disp_text.chars()
728 .take(content_width.saturating_sub(1))
729 .collect();
730 truncated.push('…');
731 truncated
732 } else {
733 disp_text
734 };
735
736 line.push(Segment::new(" ".repeat(pl)));
738
739 if let Some(ref cell_style) = cell.style {
741 let ansi = cell_style.to_ansi();
742 let reset = if ansi.is_empty() { "" } else { "\x1b[0m" };
743 line.push(Segment::new(format!("{ansi}{disp_trunc}{reset}")));
744 } else if is_header {
745 if let Some(hs) = col_def.map(|c| &c.header_style) {
746 let ansi = hs.to_ansi();
747 let reset = hs.reset_ansi();
748 line.push(Segment::new(format!("{ansi}{disp_trunc}{reset}")));
749 } else {
750 line.push(Segment::new(disp_trunc));
751 }
752 } else {
753 let col_ansi = col_def.map(|c| c.style.to_ansi()).unwrap_or_default();
755 if col_ansi.is_empty() {
756 line.push(Segment::new(disp_trunc));
757 } else {
758 line.push(Segment::new(format!("{col_ansi}{disp_trunc}\x1b[0m")));
759 }
760 }
761
762 line.push(Segment::new(" ".repeat(pr)));
764
765 if cell.rowspan > 1 {
767 for rc in col..span_end {
768 rowspan_remaining[rc] = cell.rowspan - 1;
769 }
770 }
771
772 col = span_end;
773
774 if col < col_count {
776 line.push(bs(b.mid_vertical));
777 }
778 }
779
780 line.push(bs(b.mid_right));
781 line.push(Segment::line());
782 line
783 }
784
785 fn render_row_line(
786 &self,
787 widths: &[usize],
788 _values: &[String],
789 b: &BoxStyle,
790 _available_width: usize,
791 _is_header: bool,
792 ) -> Vec<Segment> {
793 let mut line = Vec::new();
794 let bs = |ch: char| -> Segment {
795 let ansi = self.border_style.to_ansi();
796 let reset = if ansi.is_empty() { "" } else { "\x1b[0m" };
797 Segment::new(format!("{ansi}{ch}{reset}"))
798 };
799
800 line.push(bs(b.mid_vertical));
801 for (i, w) in widths.iter().enumerate() {
802 line.push(Segment::new(" ".repeat(*w)));
803 if i < widths.len() - 1 {
804 line.push(bs(b.mid_vertical));
805 }
806 }
807 line.push(bs(b.mid_right));
808 line.push(Segment::line());
809 line
810 }
811}
812
813impl Default for Table {
814 fn default() -> Self {
815 Self::new()
816 }
817}
818
819#[cfg(test)]
820mod tests {
821 use super::*;
822
823 #[test]
824 fn test_empty_table() {
825 let table = Table::new();
826 let opts = ConsoleOptions::default();
827 let result = table.render(&opts);
828 assert!(result.lines.is_empty());
829 }
830
831 #[test]
832 fn test_table_with_one_column() {
833 let mut table = Table::new();
834 table.add_column(Column::new("Name"));
835 table.add_row_str(vec!["Alice".into()]);
836 table.add_row_str(vec!["Bob".into()]);
837
838 let opts = ConsoleOptions::default();
839 let result = table.render(&opts);
840 let ansi = result.to_ansi();
841 assert!(ansi.contains("Name"));
842 assert!(ansi.contains("Alice"));
843 }
844
845 #[test]
846 fn test_cell_creation() {
847 let cell = Cell::new("hello");
848 assert_eq!(cell.content, "hello");
849 assert_eq!(cell.colspan, 1);
850 assert_eq!(cell.rowspan, 1);
851 assert!(cell.style.is_none());
852
853 let cell2 = Cell::new("world").colspan(2).rowspan(3);
854 assert_eq!(cell2.content, "world");
855 assert_eq!(cell2.colspan, 2);
856 assert_eq!(cell2.rowspan, 3);
857 }
858
859 #[test]
860 fn test_cell_from_string() {
861 let cell: Cell = "test".into();
862 assert_eq!(cell.content, "test");
863 }
864
865 #[test]
866 fn test_column_colspan() {
867 let col = Column::new("Header");
868 assert_eq!(col.colspan, 1);
869 }
870
871 #[test]
872 fn test_add_row_str() {
873 let mut table = Table::new();
874 table.add_column(Column::new("A"));
875 table.add_column(Column::new("B"));
876 table.add_row_str(vec!["x".into(), "y".into()]);
877 assert_eq!(table.row_count(), 1);
878 }
879
880 #[test]
881 fn test_add_section() {
882 let mut table = Table::new();
883 table.add_column(Column::new("A"));
884 table.add_row_str(vec!["r1".into()]);
885 table.add_section();
886 table.add_row_str(vec!["r2".into()]);
887 assert_eq!(table.row_count(), 2);
888 assert!(table.section_rows.contains(&1));
889
890 let opts = ConsoleOptions::default();
891 let result = table.render(&opts);
892 let ansi = result.to_ansi();
893 assert!(ansi.contains("r1"));
894 assert!(ansi.contains("r2"));
895 }
896
897 #[test]
898 fn test_leading() {
899 let table = Table::new()
900 .column(Column::new("X"))
901 .row_str(vec!["a".into()])
902 .row_str(vec!["b".into()])
903 .leading(1);
904 assert_eq!(table.leading, 1);
905 }
906
907 #[test]
908 fn test_cell_rowspan() {
909 let mut table = Table::new();
910 table.add_column(Column::new("A"));
911 table.add_column(Column::new("B"));
912 let cell_a = Cell::new("span").rowspan(2);
913 let cell_b = Cell::new("single");
914 table.add_row(vec![cell_a, cell_b]);
915 table.add_row_str(vec!["row2col2".into()]);
916
917 let opts = ConsoleOptions::default();
918 let result = table.render(&opts);
919 let ansi = result.to_ansi();
920 assert!(ansi.contains("span"));
921 }
922
923 #[test]
924 fn test_cell_colspan() {
925 let mut table = Table::new();
926 table.add_column(Column::new("A"));
927 table.add_column(Column::new("B"));
928 table.add_column(Column::new("C"));
929 let cell = Cell::new("wide").colspan(2);
930 table.add_row(vec![cell, Cell::new("c")]);
931 table.add_row_str(vec!["a".into(), "b".into(), "c".into()]);
932
933 let opts = ConsoleOptions::default();
934 let result = table.render(&opts);
935 let ansi = result.to_ansi();
936 assert!(ansi.contains("wide"));
937 }
938}