1use crate::align::{AlignMethod, VerticalAlignMethod};
5use crate::box_drawing::{get_safe_box, BoxStyle, BOX_HEAVY_HEAD};
6use crate::console::{ConsoleOptions, OverflowMethod, RenderResult, Renderable};
7use crate::segment::Segment;
8use crate::style::Style;
9use std::collections::HashSet;
10use unicode_width::UnicodeWidthStr;
11
12#[derive(Debug, Clone)]
18pub struct Cell {
19 pub content: String,
21 pub style: Option<Style>,
23 pub colspan: usize,
25 pub rowspan: usize,
27}
28
29impl Cell {
30 pub fn new(content: impl Into<String>) -> Self {
32 Cell {
33 content: content.into(),
34 style: None,
35 colspan: 1,
36 rowspan: 1,
37 }
38 }
39
40 pub fn style(mut self, s: Style) -> Self { self.style = Some(s); self }
42 pub fn colspan(mut self, c: usize) -> Self { self.colspan = c; self }
44 pub fn rowspan(mut self, r: usize) -> Self { self.rowspan = r; self }
46}
47
48impl From<String> for Cell {
49 fn from(s: String) -> Self { Cell::new(s) }
50}
51
52impl From<&str> for Cell {
53 fn from(s: &str) -> Self { Cell::new(s) }
54}
55
56#[derive(Debug, Clone)]
62pub struct Column {
63 pub header: String,
65 pub footer: String,
67 pub header_style: Style,
69 pub footer_style: Style,
71 pub style: Style,
73 pub justify: AlignMethod,
75 pub vertical: VerticalAlignMethod,
77 pub overflow: OverflowMethod,
79 pub width: Option<usize>,
81 pub min_width: Option<usize>,
83 pub max_width: Option<usize>,
85 pub ratio: Option<usize>,
87 pub colspan: usize,
89}
90
91impl Column {
92 pub fn new(header: impl Into<String>) -> Self {
94 Self {
95 header: header.into(),
96 footer: String::new(),
97 header_style: Style::new().bold(true),
98 footer_style: Style::new(),
99 style: Style::new(),
100 justify: AlignMethod::Left,
101 vertical: VerticalAlignMethod::Top,
102 overflow: OverflowMethod::Ellipsis,
103 width: None,
104 min_width: None,
105 max_width: None,
106 ratio: None,
107 colspan: 1,
108 }
109 }
110
111 pub fn justify(mut self, j: AlignMethod) -> Self { self.justify = j; self }
113 pub fn width(mut self, w: usize) -> Self { self.width = Some(w); self }
115 pub fn min_width(mut self, w: usize) -> Self { self.min_width = Some(w); self }
117 pub fn max_width(mut self, w: usize) -> Self { self.max_width = Some(w); self }
119 pub fn style(mut self, s: Style) -> Self { self.style = s; self }
121 pub fn header_style(mut self, s: Style) -> Self { self.header_style = s; self }
123 pub fn ratio(mut self, r: usize) -> Self { self.ratio = Some(r); self }
125 pub fn overflow(mut self, o: OverflowMethod) -> Self { self.overflow = o; self }
127}
128
129#[derive(Debug, Clone)]
135pub struct Table {
136 columns: Vec<Column>,
137 rows: Vec<Vec<Cell>>,
138 pub title: Option<String>,
140 pub caption: Option<String>,
142 pub box_style: BoxStyle,
144 pub show_header: bool,
146 pub show_footer: bool,
148 pub show_edge: bool,
150 pub show_lines: bool,
152 pub padding: (usize, usize, usize, usize),
154 pub collapse_padding: bool,
156 pub style: Style,
158 pub border_style: Style,
160 pub title_style: Style,
162 pub caption_style: Style,
164 pub title_justify: AlignMethod,
166 pub caption_justify: AlignMethod,
168 pub highlight: bool,
170 pub width: Option<usize>,
172 pub row_styles: Vec<Style>,
174 pub leading: usize,
176 pub rowspans: Vec<usize>,
178 pub section_rows: HashSet<usize>,
180}
181
182impl Table {
183 pub fn new() -> Self {
185 Self {
186 columns: Vec::new(),
187 rows: Vec::new(),
188 title: None,
189 caption: None,
190 box_style: BOX_HEAVY_HEAD.clone(),
191 show_header: true,
192 show_footer: false,
193 show_edge: true,
194 show_lines: false,
195 padding: (0, 1, 0, 1),
196 collapse_padding: false,
197 style: Style::new(),
198 border_style: Style::new(),
199 title_style: Style::new().bold(true),
200 caption_style: Style::new().dim(true),
201 title_justify: AlignMethod::Center,
202 caption_justify: AlignMethod::Center,
203 highlight: false,
204 width: None,
205 row_styles: Vec::new(),
206 leading: 0,
207 rowspans: Vec::new(),
208 section_rows: HashSet::new(),
209 }
210 }
211
212 pub fn add_column(&mut self, column: Column) {
214 self.columns.push(column);
215 }
216
217 pub fn add_row(&mut self, row: Vec<Cell>) {
219 self.rows.push(row);
220 }
221
222 pub fn add_row_str(&mut self, row: Vec<String>) {
224 let cells: Vec<Cell> = row.into_iter().map(Cell::new).collect();
225 self.rows.push(cells);
226 }
227
228 pub fn column(mut self, col: Column) -> Self { self.add_column(col); self }
230
231 pub fn row(mut self, row: Vec<Cell>) -> Self { self.add_row(row); self }
233
234 pub fn row_str(mut self, row: Vec<String>) -> Self { self.add_row_str(row); self }
236
237 pub fn title(mut self, t: impl Into<String>) -> Self { self.title = Some(t.into()); self }
239
240 pub fn caption(mut self, t: impl Into<String>) -> Self { self.caption = Some(t.into()); self }
242
243 pub fn box_style(mut self, bs: BoxStyle) -> Self { self.box_style = bs; self }
245
246 pub fn border_style(mut self, s: Style) -> Self { self.border_style = s; self }
248
249 pub fn hide_header(mut self) -> Self { self.show_header = false; self }
251
252 pub fn show_lines(mut self) -> Self { self.show_lines = true; self }
254
255 pub fn leading(mut self, l: usize) -> Self { self.leading = l; self }
257
258 pub fn grid() -> Self {
261 Self {
262 columns: Vec::new(),
263 rows: Vec::new(),
264 title: None,
265 caption: None,
266 box_style: crate::box_drawing::BOX_SIMPLE.clone(),
267 show_header: false,
268 show_footer: false,
269 show_edge: false,
270 show_lines: false,
271 padding: (0, 1, 0, 1),
272 collapse_padding: false,
273 style: Style::new(),
274 border_style: Style::new(),
275 title_style: Style::new().bold(true),
276 caption_style: Style::new().dim(true),
277 title_justify: AlignMethod::Center,
278 caption_justify: AlignMethod::Center,
279 highlight: false,
280 width: None,
281 row_styles: Vec::new(),
282 leading: 0,
283 rowspans: Vec::new(),
284 section_rows: HashSet::new(),
285 }
286 }
287
288 pub fn add_section(&mut self) {
291 self.section_rows.insert(self.rows.len());
292 }
293
294 pub fn row_count(&self) -> usize { self.rows.len() }
296}
297
298impl Renderable for Table {
299 fn render(&self, options: &ConsoleOptions) -> RenderResult {
300 if self.columns.is_empty() {
301 return RenderResult::new();
302 }
303
304 let box_style = get_safe_box(&self.box_style, options.ascii_only);
305 let available_width = self.width.unwrap_or(options.max_width);
306 let col_count = self.columns.len();
307
308 let col_widths = self.calculate_column_widths(available_width);
310
311 let mut lines: Vec<Vec<Segment>> = Vec::new();
312 let b = &box_style;
313
314 let bs = |ch: char| -> Segment {
316 let ansi = self.border_style.to_ansi();
317 let reset = if ansi.is_empty() { "" } else { "\x1b[0m" };
318 Segment::new(format!("{ansi}{ch}{reset}"))
319 };
320
321 if let Some(ref title) = self.title {
323 let _tw = UnicodeWidthStr::width(title.as_str());
324 let centered = self.title_justify.align_text(title, available_width.saturating_sub(2));
325 lines.push(vec![bs(b.top_left), Segment::new(¢ered[1..centered.len()-1]), bs(b.top_right), Segment::line()]);
326 }
327
328 if self.show_edge {
330 let mut top_line = vec![bs(b.top_left)];
331 for (i, w) in col_widths.iter().enumerate() {
332 top_line.push(Segment::new(b.top.to_string().repeat(*w)));
333 if i < col_count - 1 {
334 top_line.push(bs(b.top_divider));
335 }
336 }
337 top_line.push(bs(b.top_right));
338 top_line.push(Segment::line());
339 lines.push(top_line);
340 }
341
342 if self.show_header && self.columns.iter().any(|c| !c.header.is_empty()) {
344 let (pt, _pr, _pb, _pl) = self.padding;
346 for _ in 0..pt {
347 lines.push(self.render_row_line(&col_widths, &[], &b, available_width, false));
348 }
349
350 let header_cells: Vec<String> = self.columns.iter()
351 .map(|c| c.header.clone())
352 .collect();
353 lines.push(self.render_cell_line(&col_widths, &header_cells, &b, true));
354
355 let mut sep = vec![bs(b.head_row_left)];
357 for (i, w) in col_widths.iter().enumerate() {
358 sep.push(Segment::new(b.head_row_horizontal.to_string().repeat(*w)));
359 if i < col_count - 1 {
360 sep.push(bs(b.head_row_cross));
361 }
362 }
363 sep.push(bs(b.head_row_right));
364 sep.push(Segment::line());
365 lines.push(sep);
366 }
367
368 let mut rowspan_remaining: Vec<usize> = vec![0; col_count];
370 for (row_idx, row) in self.rows.iter().enumerate() {
371 if self.section_rows.contains(&row_idx) {
373 let mut sep = vec![bs(b.head_row_left)];
374 for (i, w) in col_widths.iter().enumerate() {
375 sep.push(Segment::new(b.head_row_horizontal.to_string().repeat(*w)));
376 if i < col_count - 1 {
377 sep.push(bs(b.head_row_cross));
378 }
379 }
380 sep.push(bs(b.head_row_right));
381 sep.push(Segment::line());
382 lines.push(sep);
383 }
384
385 if row_idx > 0 {
387 for _ in 0..self.leading {
388 lines.push(self.render_row_line(&col_widths, &[], &b, available_width, false));
389 }
390 }
391
392 let (pt, _pr, _pb, _pl) = self.padding;
393 for _ in 0..pt {
394 lines.push(self.render_row_line(&col_widths, &[], &b, available_width, false));
395 }
396
397 let _style = if row_idx < self.row_styles.len() {
398 Some(&self.row_styles[row_idx])
399 } else if self.row_styles.len() == 2 {
400 Some(&self.row_styles[row_idx % 2])
401 } else {
402 None
403 };
404
405 lines.push(self.render_cell_line_with_rowspan(
406 &col_widths, row, &b, false, &mut rowspan_remaining,
407 ));
408
409 if self.show_lines && row_idx < self.rows.len() - 1 {
411 let mut sep = vec![bs(b.row_left)];
412 for (i, w) in col_widths.iter().enumerate() {
413 sep.push(Segment::new(b.row_horizontal.to_string().repeat(*w)));
414 if i < col_count - 1 {
415 sep.push(bs(b.row_cross));
416 }
417 }
418 sep.push(bs(b.row_right));
419 sep.push(Segment::line());
420 lines.push(sep);
421 }
422 }
423
424 if self.show_footer && self.columns.iter().any(|c| !c.footer.is_empty()) {
426 let mut sep = vec![bs(b.foot_row_left)];
427 for (i, w) in col_widths.iter().enumerate() {
428 sep.push(Segment::new(b.foot_row_horizontal.to_string().repeat(*w)));
429 if i < col_count - 1 {
430 sep.push(bs(b.foot_row_cross));
431 }
432 }
433 sep.push(bs(b.foot_row_right));
434 sep.push(Segment::line());
435 lines.push(sep);
436
437 let footer_cells: Vec<String> = self.columns.iter()
438 .map(|c| c.footer.clone())
439 .collect();
440 lines.push(self.render_cell_line(&col_widths, &footer_cells, &b, false));
441 }
442
443 if self.show_edge {
445 let mut bot_line = vec![bs(b.bottom_left)];
446 for (i, w) in col_widths.iter().enumerate() {
447 bot_line.push(Segment::new(b.bottom.to_string().repeat(*w)));
448 if i < col_count - 1 {
449 bot_line.push(bs(b.bottom_divider));
450 }
451 }
452 bot_line.push(bs(b.bottom_right));
453 bot_line.push(Segment::line());
454 lines.push(bot_line);
455 }
456
457 if let Some(ref caption) = self.caption {
459 let centered = self.caption_justify.align_text(caption, available_width.saturating_sub(2));
460 lines.push(vec![Segment::new(¢ered), Segment::line()]);
461 }
462
463 RenderResult { lines, items: Vec::new() }
464 }
465}
466
467impl Table {
468 fn calculate_column_widths(&self, available: usize) -> Vec<usize> {
469 let col_count = self.columns.len();
470 let total_pad = col_count.saturating_sub(1) + 2; let content_width = available.saturating_sub(total_pad);
472
473 let mut widths: Vec<usize> = vec![0; col_count];
475 let mut flex_cols: Vec<usize> = Vec::new();
476 let mut used = 0usize;
477
478 for (i, col) in self.columns.iter().enumerate() {
479 if let Some(w) = col.width {
480 widths[i] = w;
481 used += w;
482 } else {
483 flex_cols.push(i);
484 }
485 }
486
487 if flex_cols.is_empty() {
488 return widths;
489 }
490
491 let remaining = content_width.saturating_sub(used);
492 let _flex_count = flex_cols.len();
493
494 let total_ratio: usize = flex_cols.iter()
496 .map(|&i| self.columns[i].ratio.unwrap_or(1))
497 .sum();
498
499 for &i in &flex_cols {
500 let col = &self.columns[i];
501 let ratio = col.ratio.unwrap_or(1);
502 let mut w = (remaining * ratio) / total_ratio;
503 if let Some(min_w) = col.min_width {
504 w = w.max(min_w);
505 }
506 if let Some(max_w) = col.max_width {
507 w = w.min(max_w);
508 }
509 w = w.max(3); widths[i] = w;
511 }
512
513 let total: usize = widths.iter().sum();
515 if total < content_width && !flex_cols.is_empty() {
516 let extra = content_width - total;
517 widths[flex_cols[flex_cols.len() - 1]] += extra;
518 }
519
520 widths
521 }
522
523 fn render_cell_line(
524 &self,
525 widths: &[usize],
526 values: &[String],
527 b: &BoxStyle,
528 is_header: bool,
529 ) -> Vec<Segment> {
530 let mut line = Vec::new();
531 let bs = |ch: char| -> Segment {
532 let ansi = self.border_style.to_ansi();
533 let reset = if ansi.is_empty() { "" } else { "\x1b[0m" };
534 Segment::new(format!("{ansi}{ch}{reset}"))
535 };
536
537 line.push(bs(b.mid_vertical));
538
539 for (i, w) in widths.iter().enumerate() {
540 let val = values.get(i).map(|s| s.as_str()).unwrap_or("");
541 let col = self.columns.get(i);
542 let justify = col.map(|c| c.justify).unwrap_or(AlignMethod::Left);
543 let (_pt, pr, _pb, pl) = self.padding;
544
545 line.push(Segment::new(" ".repeat(pl)));
547
548 let disp = justify.align_text(val, w.saturating_sub(pl + pr));
550 let disp_trunc = if UnicodeWidthStr::width(disp.as_str()) > *w {
552 let mut truncated = disp.chars().take(
553 w.saturating_sub(1) ).collect::<String>();
555 truncated.push('…');
556 truncated
557 } else {
558 disp
559 };
560
561 if is_header {
563 let header_style = col.map(|c| &c.header_style);
564 if let Some(hs) = header_style {
565 let ansi = hs.to_ansi();
566 let reset = hs.reset_ansi();
567 line.push(Segment::new(format!("{ansi}{disp_trunc}{reset}")));
568 } else {
569 line.push(Segment::new(disp_trunc));
570 }
571 } else {
572 line.push(Segment::new(disp_trunc));
573 }
574
575 line.push(Segment::new(" ".repeat(pr)));
577
578 if i < widths.len() - 1 {
579 line.push(bs(b.mid_vertical));
580 }
581 }
582
583 line.push(bs(b.mid_right));
584 line.push(Segment::line());
585 line
586 }
587
588 fn render_cell_line_with_rowspan(
591 &self,
592 widths: &[usize],
593 cells: &[Cell],
594 b: &BoxStyle,
595 is_header: bool,
596 rowspan_remaining: &mut [usize],
597 ) -> Vec<Segment> {
598 let mut line = Vec::new();
599 let bs = |ch: char| -> Segment {
600 let ansi = self.border_style.to_ansi();
601 let reset = if ansi.is_empty() { "" } else { "\x1b[0m" };
602 Segment::new(format!("{ansi}{ch}{reset}"))
603 };
604
605 line.push(bs(b.mid_vertical));
606
607 let col_count = widths.len();
608 let mut cell_idx = 0;
609 let mut col: usize = 0;
610
611 while col < col_count {
612 if rowspan_remaining[col] > 0 {
614 rowspan_remaining[col] -= 1;
615 let w = widths[col];
617 let (_pt, pr, _pb, pl) = self.padding;
618 line.push(Segment::new(" ".repeat(pl + w + pr)));
619 if col < col_count - 1 {
620 line.push(bs(b.mid_vertical));
621 }
622 col += 1;
623 continue;
624 }
625
626 if cell_idx >= cells.len() {
628 let w = widths[col];
629 let (_pt, pr, _pb, pl) = self.padding;
630 line.push(Segment::new(" ".repeat(pl + w + pr)));
631 if col < col_count - 1 {
632 line.push(bs(b.mid_vertical));
633 }
634 col += 1;
635 continue;
636 }
637
638 let cell = &cells[cell_idx];
639 cell_idx += 1;
640
641 let span_end = (col + cell.colspan).min(col_count);
642 let span_width: usize = widths[col..span_end].iter().sum();
643 let (_pt, pr, _pb, pl) = self.padding;
644 let content_width = span_width.saturating_sub(pl + pr);
645
646 let col_def = self.columns.get(col);
647 let justify = col_def.map(|c| c.justify).unwrap_or(AlignMethod::Left);
648
649 let disp_text = justify.align_text(&cell.content, content_width);
651 let disp_trunc = if UnicodeWidthStr::width(disp_text.as_str()) > content_width {
652 let mut truncated: String = disp_text.chars()
653 .take(content_width.saturating_sub(1))
654 .collect();
655 truncated.push('…');
656 truncated
657 } else {
658 disp_text
659 };
660
661 line.push(Segment::new(" ".repeat(pl)));
663
664 if let Some(ref cell_style) = cell.style {
666 let ansi = cell_style.to_ansi();
667 let reset = if ansi.is_empty() { "" } else { "\x1b[0m" };
668 line.push(Segment::new(format!("{ansi}{disp_trunc}{reset}")));
669 } else if is_header {
670 if let Some(hs) = col_def.map(|c| &c.header_style) {
671 let ansi = hs.to_ansi();
672 let reset = hs.reset_ansi();
673 line.push(Segment::new(format!("{ansi}{disp_trunc}{reset}")));
674 } else {
675 line.push(Segment::new(disp_trunc));
676 }
677 } else {
678 let col_ansi = col_def.map(|c| c.style.to_ansi()).unwrap_or_default();
680 if col_ansi.is_empty() {
681 line.push(Segment::new(disp_trunc));
682 } else {
683 line.push(Segment::new(format!("{col_ansi}{disp_trunc}\x1b[0m")));
684 }
685 }
686
687 line.push(Segment::new(" ".repeat(pr)));
689
690 if cell.rowspan > 1 {
692 for rc in col..span_end {
693 rowspan_remaining[rc] = cell.rowspan - 1;
694 }
695 }
696
697 col = span_end;
698
699 if col < col_count {
701 line.push(bs(b.mid_vertical));
702 }
703 }
704
705 line.push(bs(b.mid_right));
706 line.push(Segment::line());
707 line
708 }
709
710 fn render_row_line(
711 &self,
712 widths: &[usize],
713 _values: &[String],
714 b: &BoxStyle,
715 _available_width: usize,
716 _is_header: bool,
717 ) -> Vec<Segment> {
718 let mut line = Vec::new();
719 let bs = |ch: char| -> Segment {
720 let ansi = self.border_style.to_ansi();
721 let reset = if ansi.is_empty() { "" } else { "\x1b[0m" };
722 Segment::new(format!("{ansi}{ch}{reset}"))
723 };
724
725 line.push(bs(b.mid_vertical));
726 for (i, w) in widths.iter().enumerate() {
727 line.push(Segment::new(" ".repeat(*w)));
728 if i < widths.len() - 1 {
729 line.push(bs(b.mid_vertical));
730 }
731 }
732 line.push(bs(b.mid_right));
733 line.push(Segment::line());
734 line
735 }
736}
737
738impl Default for Table {
739 fn default() -> Self {
740 Self::new()
741 }
742}
743
744#[cfg(test)]
745mod tests {
746 use super::*;
747
748 #[test]
749 fn test_empty_table() {
750 let table = Table::new();
751 let opts = ConsoleOptions::default();
752 let result = table.render(&opts);
753 assert!(result.lines.is_empty());
754 }
755
756 #[test]
757 fn test_table_with_one_column() {
758 let mut table = Table::new();
759 table.add_column(Column::new("Name"));
760 table.add_row_str(vec!["Alice".into()]);
761 table.add_row_str(vec!["Bob".into()]);
762
763 let opts = ConsoleOptions::default();
764 let result = table.render(&opts);
765 let ansi = result.to_ansi();
766 assert!(ansi.contains("Name"));
767 assert!(ansi.contains("Alice"));
768 }
769
770 #[test]
771 fn test_cell_creation() {
772 let cell = Cell::new("hello");
773 assert_eq!(cell.content, "hello");
774 assert_eq!(cell.colspan, 1);
775 assert_eq!(cell.rowspan, 1);
776 assert!(cell.style.is_none());
777
778 let cell2 = Cell::new("world").colspan(2).rowspan(3);
779 assert_eq!(cell2.content, "world");
780 assert_eq!(cell2.colspan, 2);
781 assert_eq!(cell2.rowspan, 3);
782 }
783
784 #[test]
785 fn test_cell_from_string() {
786 let cell: Cell = "test".into();
787 assert_eq!(cell.content, "test");
788 }
789
790 #[test]
791 fn test_column_colspan() {
792 let col = Column::new("Header");
793 assert_eq!(col.colspan, 1);
794 }
795
796 #[test]
797 fn test_add_row_str() {
798 let mut table = Table::new();
799 table.add_column(Column::new("A"));
800 table.add_column(Column::new("B"));
801 table.add_row_str(vec!["x".into(), "y".into()]);
802 assert_eq!(table.row_count(), 1);
803 }
804
805 #[test]
806 fn test_add_section() {
807 let mut table = Table::new();
808 table.add_column(Column::new("A"));
809 table.add_row_str(vec!["r1".into()]);
810 table.add_section();
811 table.add_row_str(vec!["r2".into()]);
812 assert_eq!(table.row_count(), 2);
813 assert!(table.section_rows.contains(&1));
814
815 let opts = ConsoleOptions::default();
816 let result = table.render(&opts);
817 let ansi = result.to_ansi();
818 assert!(ansi.contains("r1"));
819 assert!(ansi.contains("r2"));
820 }
821
822 #[test]
823 fn test_leading() {
824 let table = Table::new()
825 .column(Column::new("X"))
826 .row_str(vec!["a".into()])
827 .row_str(vec!["b".into()])
828 .leading(1);
829 assert_eq!(table.leading, 1);
830 }
831
832 #[test]
833 fn test_cell_rowspan() {
834 let mut table = Table::new();
835 table.add_column(Column::new("A"));
836 table.add_column(Column::new("B"));
837 let cell_a = Cell::new("span").rowspan(2);
838 let cell_b = Cell::new("single");
839 table.add_row(vec![cell_a, cell_b]);
840 table.add_row_str(vec!["row2col2".into()]);
841
842 let opts = ConsoleOptions::default();
843 let result = table.render(&opts);
844 let ansi = result.to_ansi();
845 assert!(ansi.contains("span"));
846 }
847
848 #[test]
849 fn test_cell_colspan() {
850 let mut table = Table::new();
851 table.add_column(Column::new("A"));
852 table.add_column(Column::new("B"));
853 table.add_column(Column::new("C"));
854 let cell = Cell::new("wide").colspan(2);
855 table.add_row(vec![cell, Cell::new("c")]);
856 table.add_row_str(vec!["a".into(), "b".into(), "c".into()]);
857
858 let opts = ConsoleOptions::default();
859 let result = table.render(&opts);
860 let ansi = result.to_ansi();
861 assert!(ansi.contains("wide"));
862 }
863}