// TODO: Stop building `tabled -e` when it's clear we are out of terminal // TODO: Stop building `tabled` when it's clear we are out of terminal // NOTE: TODO the above we could expose something like [`WidthCtrl`] in which case we could also laverage the width list build right away. // currently it seems like we do recacalculate it for `table -e`? use std::{cmp::min, collections::HashMap}; use nu_ansi_term::Style; use nu_color_config::TextStyle; use nu_protocol::{TableIndent, TrimStrategy}; use tabled::{ builder::Builder, grid::{ ansi::ANSIBuf, config::{ AlignmentHorizontal, ColoredConfig, Entity, Indent, Position, Sides, SpannedConfig, }, dimension::{CompleteDimensionVecRecords, SpannedGridDimension}, records::{ vec_records::{Text, VecRecords}, ExactRecords, Records, }, }, settings::{ formatting::AlignmentStrategy, object::{Columns, Rows}, themes::ColumnNames, width::Truncate, Alignment, CellOption, Color, Padding, TableOption, Width, }, Table, }; use crate::{convert_style, is_color_empty, table_theme::TableTheme}; const EMPTY_COLUMN_TEXT: &str = "..."; const EMPTY_COLUMN_TEXT_WIDTH: usize = 3; pub type NuRecords = VecRecords; pub type NuRecordsValue = Text; /// NuTable is a table rendering implementation. #[derive(Debug, Clone)] pub struct NuTable { data: NuRecords, styles: Styles, alignments: Alignments, config: TableConfig, } impl NuTable { /// Creates an empty [`NuTable`] instance. pub fn new(count_rows: usize, count_columns: usize) -> Self { Self { data: VecRecords::new(vec![vec![Text::default(); count_columns]; count_rows]), styles: Styles::default(), alignments: Alignments { data: AlignmentHorizontal::Left, index: AlignmentHorizontal::Right, header: AlignmentHorizontal::Center, columns: HashMap::default(), cells: HashMap::default(), }, config: TableConfig { theme: TableTheme::basic(), trim: TrimStrategy::truncate(None), structure: TableStructure::new(false, false, false), indent: TableIndent::new(1, 1), header_on_border: false, expand: false, border_color: None, }, } } /// Return amount of rows. pub fn count_rows(&self) -> usize { self.data.count_rows() } /// Return amount of columns. pub fn count_columns(&self) -> usize { self.data.count_columns() } pub fn insert(&mut self, pos: Position, text: String) { self.data[pos.0][pos.1] = Text::new(text); } pub fn insert_row(&mut self, index: usize, row: Vec) { let data = &mut self.data[index]; for (col, text) in row.into_iter().enumerate() { data[col] = Text::new(text); } } pub fn set_row(&mut self, index: usize, row: Vec) { assert_eq!(self.data[index].len(), row.len()); self.data[index] = row; } pub fn set_column_style(&mut self, column: usize, style: TextStyle) { if let Some(style) = style.color_style { let style = convert_style(style); self.styles.columns.insert(column, style); } let alignment = convert_alignment(style.alignment); if alignment != self.alignments.data { self.alignments.columns.insert(column, alignment); } } pub fn insert_style(&mut self, pos: Position, style: TextStyle) { if let Some(style) = style.color_style { let style = convert_style(style); self.styles.cells.insert(pos, style); } let alignment = convert_alignment(style.alignment); if alignment != self.alignments.data { self.alignments.cells.insert(pos, alignment); } } pub fn set_header_style(&mut self, style: TextStyle) { if let Some(style) = style.color_style { let style = convert_style(style); self.styles.header = style; } self.alignments.header = convert_alignment(style.alignment); } pub fn set_index_style(&mut self, style: TextStyle) { if let Some(style) = style.color_style { let style = convert_style(style); self.styles.index = style; } self.alignments.index = convert_alignment(style.alignment); } pub fn set_data_style(&mut self, style: TextStyle) { if let Some(style) = style.color_style { let style = convert_style(style); self.styles.data = style; } self.alignments.data = convert_alignment(style.alignment); } pub fn set_indent(&mut self, indent: TableIndent) { self.config.indent = indent; } pub fn set_theme(&mut self, theme: TableTheme) { self.config.theme = theme; } pub fn set_structure(&mut self, index: bool, header: bool, footer: bool) { self.config.structure = TableStructure::new(index, header, footer); } pub fn set_border_header(&mut self, on: bool) { self.config.header_on_border = on; } pub fn set_trim(&mut self, strategy: TrimStrategy) { self.config.trim = strategy; } pub fn set_strategy(&mut self, expand: bool) { self.config.expand = expand; } pub fn set_border_color(&mut self, color: Style) { self.config.border_color = (!color.is_plain()).then_some(color); } pub fn get_records_mut(&mut self) -> &mut NuRecords { &mut self.data } /// Converts a table to a String. /// /// It returns None in case where table cannot be fit to a terminal width. pub fn draw(self, termwidth: usize) -> Option { build_table(self, termwidth) } /// Return a total table width. pub fn total_width(&self) -> usize { let config = create_config(&self.config.theme, false, None); let pad = indent_sum(self.config.indent); let widths = build_width(&self.data, pad); get_total_width2(&widths, &config) } } impl From>>> for NuTable { fn from(value: Vec>>) -> Self { let mut nutable = Self::new(0, 0); nutable.data = VecRecords::new(value); nutable } } type Alignments = CellConfiguration; type Styles = CellConfiguration; #[derive(Debug, Default, Clone)] struct CellConfiguration { data: Value, index: Value, header: Value, columns: HashMap, cells: HashMap, } #[derive(Debug, Clone)] pub struct TableConfig { theme: TableTheme, trim: TrimStrategy, border_color: Option