diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 50db949ced..80a6e938e4 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -11,8 +11,8 @@ use nu_protocol::{ ByteStream, Config, DataSource, ListStream, PipelineMetadata, Signals, TableMode, ValueIterator, }; use nu_table::{ - common::create_nu_table_config, CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, - StringResult, TableOpts, TableOutput, + common::create_nu_table_config, CollapsedTable, ExpandedTable, JustTable, NuRecordsValue, + NuTable, StringResult, TableOpts, TableOutput, }; use nu_utils::get_ls_colors; use std::{ @@ -522,14 +522,13 @@ fn handle_record( } } - let indent = (config.table.padding.left, config.table.padding.right); let opts = TableOpts::new( &config, styles, input.engine_state.signals(), span, cfg.term_width, - indent, + config.table.padding, cfg.theme, cfg.index.unwrap_or(0), cfg.index.is_none(), @@ -826,7 +825,7 @@ impl PagingTableCreator { self.engine_state.signals(), self.head, self.cfg.term_width, - (cfg.table.padding.left, cfg.table.padding.right), + cfg.table.padding, self.cfg.theme, self.cfg.index.unwrap_or(0) + self.row_offset, self.cfg.index.is_none(), @@ -1084,11 +1083,11 @@ fn create_empty_placeholder( return String::new(); } - let cell = NuTableCell::new(format!("empty {}", value_type_name)); + let cell = NuRecordsValue::new(format!("empty {}", value_type_name)); let data = vec![vec![cell]]; let mut table = NuTable::from(data); table.set_data_style(TextStyle::default().dimmed()); - let out = TableOutput::new(table, false, false, 1); + let out = TableOutput::from_table(table, false, false); let style_computer = &StyleComputer::from_config(engine_state, stack); let config = create_nu_table_config(&config, style_computer, &out, false, TableMode::default()); diff --git a/crates/nu-explore/src/nu_common/table.rs b/crates/nu-explore/src/nu_common/table.rs index 79a97ed9ca..1757efc148 100644 --- a/crates/nu-explore/src/nu_common/table.rs +++ b/crates/nu-explore/src/nu_common/table.rs @@ -36,7 +36,7 @@ fn try_build_map( signals, Span::unknown(), usize::MAX, - (config.table.padding.left, config.table.padding.right), + config.table.padding, config.table.mode, 0, false, @@ -63,7 +63,7 @@ fn try_build_list( signals, Span::unknown(), usize::MAX, - (config.table.padding.left, config.table.padding.right), + config.table.padding, config.table.mode, 0, false, diff --git a/crates/nu-protocol/src/config/mod.rs b/crates/nu-protocol/src/config/mod.rs index c66c41e455..5f53810315 100644 --- a/crates/nu-protocol/src/config/mod.rs +++ b/crates/nu-protocol/src/config/mod.rs @@ -21,7 +21,7 @@ pub use plugin_gc::{PluginGcConfig, PluginGcConfigs}; pub use reedline::{CursorShapeConfig, EditBindings, NuCursorShape, ParsedKeybinding, ParsedMenu}; pub use rm::RmConfig; pub use shell_integration::ShellIntegrationConfig; -pub use table::{FooterMode, TableConfig, TableIndexMode, TableMode, TrimStrategy}; +pub use table::{FooterMode, TableConfig, TableIndent, TableIndexMode, TableMode, TrimStrategy}; mod completions; mod datetime_format; diff --git a/crates/nu-protocol/src/config/table.rs b/crates/nu-protocol/src/config/table.rs index e88f4ea88f..5c6aaf07d6 100644 --- a/crates/nu-protocol/src/config/table.rs +++ b/crates/nu-protocol/src/config/table.rs @@ -277,6 +277,12 @@ pub struct TableIndent { pub right: usize, } +impl TableIndent { + pub fn new(left: usize, right: usize) -> Self { + Self { left, right } + } +} + impl IntoValue for TableIndent { fn into_value(self, span: Span) -> Value { record! { diff --git a/crates/nu-table/src/common.rs b/crates/nu-table/src/common.rs index 13c8f84fbc..96976be4bc 100644 --- a/crates/nu-table/src/common.rs +++ b/crates/nu-table/src/common.rs @@ -54,7 +54,7 @@ pub fn nu_value_to_string_colored(val: &Value, cfg: &Config, style: &StyleComput pub fn nu_value_to_string(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText { let float_precision = cfg.float_precision as usize; let text = val.to_abbreviated_string(cfg); - make_styled_string(style, text, Some(val), float_precision) + make_styled_value(text, val, float_precision, style) } pub fn nu_value_to_string_clean(val: &Value, cfg: &Config, style_comp: &StyleComputer) -> NuText { @@ -66,7 +66,11 @@ pub fn nu_value_to_string_clean(val: &Value, cfg: &Config, style_comp: &StyleCom } pub fn error_sign(style_computer: &StyleComputer) -> (String, TextStyle) { - make_styled_string(style_computer, String::from("❎"), None, 0) + // Though holes are not the same as null, the closure for "empty" is passed a null anyway. + + let text = String::from("❎"); + let style = style_computer.compute("empty", &Value::nothing(Span::unknown())); + (text, TextStyle::with_style(Alignment::Center, style)) } pub fn wrap_text(text: &str, width: usize, config: &Config) -> String { @@ -122,36 +126,23 @@ pub fn get_empty_style(style_computer: &StyleComputer) -> NuText { ) } -fn make_styled_string( - style_computer: &StyleComputer, +fn make_styled_value( text: String, - value: Option<&Value>, // None represents table holes. + value: &Value, float_precision: usize, + style_computer: &StyleComputer, ) -> NuText { match value { - Some(value) => { - match value { - Value::Float { .. } => { - // set dynamic precision from config - let precise_number = match convert_with_precision(&text, float_precision) { - Ok(num) => num, - Err(e) => e.to_string(), - }; - (precise_number, style_computer.style_primitive(value)) - } - _ => (text, style_computer.style_primitive(value)), - } - } - None => { - // Though holes are not the same as null, the closure for "empty" is passed a null anyway. - ( - text, - TextStyle::with_style( - Alignment::Center, - style_computer.compute("empty", &Value::nothing(Span::unknown())), - ), - ) + Value::Float { .. } => { + // set dynamic precision from config + let precise_number = match convert_with_precision(&text, float_precision) { + Ok(num) => num, + Err(e) => e.to_string(), + }; + + (precise_number, style_computer.style_primitive(value)) } + _ => (text, style_computer.style_primitive(value)), } } @@ -220,3 +211,10 @@ fn need_footer(config: &Config, count_records: u64) -> bool { } } } + +pub fn check_value(value: &Value) -> Result<(), ShellError> { + match value { + Value::Error { error, .. } => Err(*error.clone()), + _ => Ok(()), + } +} diff --git a/crates/nu-table/src/lib.rs b/crates/nu-table/src/lib.rs index 182585f193..cfb91d5034 100644 --- a/crates/nu-table/src/lib.rs +++ b/crates/nu-table/src/lib.rs @@ -9,7 +9,7 @@ pub mod common; pub use common::{StringResult, TableResult}; pub use nu_color_config::TextStyle; -pub use table::{NuTable, NuTableCell, NuTableConfig}; +pub use table::{NuRecordsValue, NuTable, NuTableConfig}; pub use table_theme::TableTheme; pub use types::{CollapsedTable, ExpandedTable, JustTable, TableOpts, TableOutput}; pub use unstructured_table::UnstructuredTable; diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index fbb7c2d7a5..4e1b5a11bf 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -29,7 +29,10 @@ use tabled::{ Table, }; -use crate::{convert_style, table_theme::TableTheme}; +use crate::{convert_style, is_color_empty, table_theme::TableTheme}; + +pub type NuRecords = VecRecords; +pub type NuRecordsValue = Text; /// NuTable is a table rendering implementation. #[derive(Debug, Clone)] @@ -40,27 +43,18 @@ pub struct NuTable { indent: (usize, usize), } -pub type NuRecords = VecRecords; -pub type NuTableCell = Text; - #[derive(Debug, Default, Clone)] -struct Styles { - data: Color, - index: Color, - header: Color, - columns: HashMap, - cells: HashMap, +struct TableConfig { + data: Value, + index: Value, + header: Value, + columns: HashMap, + cells: HashMap, } -// todo: generic -#[derive(Debug, Clone)] -struct Alignments { - data: AlignmentHorizontal, - index: AlignmentHorizontal, - header: AlignmentHorizontal, - columns: HashMap, - cells: HashMap, -} +type Alignments = TableConfig; + +type Styles = TableConfig; impl NuTable { /// Creates an empty [`NuTable`] instance. @@ -93,6 +87,14 @@ impl NuTable { 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_column_style(&mut self, column: usize, style: TextStyle) { if let Some(style) = style.color_style { let style = convert_style(style); @@ -411,8 +413,8 @@ impl TableOption> for return; } - let need_expantion = self.cfg.expand && self.width_max > total_width; - if need_expantion { + let need_expansion = self.cfg.expand && self.width_max > total_width; + if need_expansion { let opt = (SetDimensions(self.width), Width::increase(self.width_max)); TableOption::, _, _>::change(opt, rec, cfg, dim); return; @@ -1242,7 +1244,3 @@ fn strip_color_from_row(row: usize) -> ModifyList Modify::new(Rows::single(row)).with(Format::content(foo)) } - -fn is_color_empty(c: &Color) -> bool { - c.get_prefix().is_empty() && c.get_suffix().is_empty() -} diff --git a/crates/nu-table/src/table_theme.rs b/crates/nu-table/src/table_theme.rs index 80ffff1a23..5c9fb2e06f 100644 --- a/crates/nu-table/src/table_theme.rs +++ b/crates/nu-table/src/table_theme.rs @@ -10,7 +10,7 @@ pub struct TableTheme { } impl TableTheme { - pub fn new(base: impl Into, full: impl Into) -> Self { + fn new(base: impl Into, full: impl Into) -> Self { Self { base: base.into(), full: full.into(), @@ -160,26 +160,6 @@ impl TableTheme { Self::new(Style::blank(), Style::blank()) } - // pub fn has_top(&self) -> bool { - // self.theme.borders_has_top() - // } - - // pub fn has_left(&self) -> bool { - // self.theme.borders_has_left() - // } - - // pub fn has_right(&self) -> bool { - // self.theme.borders_has_right() - // } - - // pub fn has_inner(&self) -> bool { - // self.has_inner - // } - - // pub fn has_horizontals(&self) -> bool { - // self.full_theme.get_borders().has_horizontal() - // } - pub fn as_full(&self) -> &Theme { &self.full } diff --git a/crates/nu-table/src/types/collapse.rs b/crates/nu-table/src/types/collapse.rs index 0012c70a35..731d42c946 100644 --- a/crates/nu-table/src/types/collapse.rs +++ b/crates/nu-table/src/types/collapse.rs @@ -3,46 +3,36 @@ use crate::{ StringResult, TableOpts, UnstructuredTable, }; use nu_color_config::StyleComputer; -use nu_protocol::{Config, Record, TableMode, Value}; +use nu_protocol::{Config, Record, Value}; use nu_utils::SharedCow; pub struct CollapsedTable; impl CollapsedTable { pub fn build(value: Value, opts: TableOpts<'_>) -> StringResult { - collapsed_table( - value, - opts.config, - opts.width, - opts.style_computer, - opts.mode, - ) + collapsed_table(value, opts) } } -fn collapsed_table( - mut value: Value, - config: &Config, - term_width: usize, - style_computer: &StyleComputer, - mode: TableMode, -) -> StringResult { - colorize_value(&mut value, config, style_computer); +fn collapsed_table(mut value: Value, opts: TableOpts<'_>) -> StringResult { + colorize_value(&mut value, opts.config, opts.style_computer); - let theme = load_theme(mode); - let mut table = UnstructuredTable::new(value, config); - let is_empty = table.truncate(&theme, term_width); + let mut table = UnstructuredTable::new(value, opts.config); + + let theme = load_theme(opts.mode); + let is_empty = table.truncate(&theme, opts.width); if is_empty { return Ok(None); } - let indent = (config.table.padding.left, config.table.padding.right); - let table = table.draw(style_computer, &theme, indent); + let table = table.draw(&theme, opts.config.table.padding, opts.style_computer); Ok(Some(table)) } fn colorize_value(value: &mut Value, config: &Config, style_computer: &StyleComputer) { + // todo: Remove recursion? + match value { Value::Record { ref mut val, .. } => { let style = get_index_style(style_computer); diff --git a/crates/nu-table/src/types/expanded.rs b/crates/nu-table/src/types/expanded.rs index d5cb201f40..b88484d4fa 100644 --- a/crates/nu-table/src/types/expanded.rs +++ b/crates/nu-table/src/types/expanded.rs @@ -1,12 +1,12 @@ use crate::{ common::{ - create_nu_table_config, error_sign, get_header_style, get_index_style, load_theme, - nu_value_to_string, nu_value_to_string_clean, nu_value_to_string_colored, wrap_text, - NuText, StringResult, TableResult, INDEX_COLUMN_NAME, + check_value, create_nu_table_config, error_sign, get_header_style, get_index_style, + load_theme, nu_value_to_string, nu_value_to_string_clean, nu_value_to_string_colored, + wrap_text, NuText, StringResult, TableResult, INDEX_COLUMN_NAME, }, string_width, types::has_index, - NuTable, NuTableCell, TableOpts, TableOutput, + NuRecordsValue, NuTable, TableOpts, TableOutput, }; use nu_color_config::{Alignment, StyleComputer, TextStyle}; use nu_engine::column::get_columns; @@ -133,15 +133,12 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { if with_index { if with_header { - data[0].push(NuTableCell::exact(String::from("#"), 1, vec![])); + data[0].push(NuRecordsValue::exact(String::from("#"), 1, vec![])); } for (row, item) in input.iter().enumerate() { cfg.opts.signals.check(cfg.opts.span)?; - - if let Value::Error { error, .. } = item { - return Err(*error.clone()); - } + check_value(item)?; let index = row + row_offset; let text = item @@ -152,7 +149,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { .unwrap_or_else(|| index.to_string()); let row = row + with_header as usize; - let value = NuTableCell::new(text); + let value = NuRecordsValue::new(text); data[row].push(value); } @@ -177,10 +174,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { for (row, item) in input.iter().enumerate() { cfg.opts.signals.check(cfg.opts.span)?; - - if let Value::Error { error, .. } = item { - return Err(*error.clone()); - } + check_value(item)?; let inner_cfg = update_config(cfg.clone(), available_width); let mut cell = expanded_table_entry2(item, inner_cfg); @@ -195,7 +189,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { cell.text = wrap_text(&cell.text, available_width, cfg.opts.config); } - let value = NuTableCell::new(cell.text); + let value = NuRecordsValue::new(cell.text); data[row].push(value); data_styles.insert((row, with_index as usize), cell.style); @@ -203,7 +197,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { } let mut table = NuTable::from(data); - table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1); + table.set_indent(cfg.opts.indent.left, cfg.opts.indent.right); table.set_index_style(get_index_style(cfg.opts.style_computer)); set_data_styles(&mut table, data_styles); @@ -266,10 +260,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { for (row, item) in input.iter().enumerate() { cfg.opts.signals.check(cfg.opts.span)?; - - if let Value::Error { error, .. } = item { - return Err(*error.clone()); - } + check_value(item)?; let inner_cfg = update_config(cfg.clone(), available); let mut cell = expanded_table_entry(item, header.as_str(), inner_cfg); @@ -285,14 +276,14 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { column_width = max(column_width, value_width); - let value = NuTableCell::new(cell.text); + let value = NuRecordsValue::new(cell.text); data[row + 1].push(value); data_styles.insert((row + 1, col + with_index as usize), cell.style); column_rows = column_rows.saturating_add(cell.size); } - let head_cell = NuTableCell::new(header); + let head_cell = NuRecordsValue::new(header); data[0].push(head_cell); if column_width > available { @@ -354,7 +345,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { let is_last_column = widths.len() == count_columns; if !is_last_column { - let shift = NuTableCell::exact(String::from("..."), 3, vec![]); + let shift = NuRecordsValue::exact(String::from("..."), 3, vec![]); for row in &mut data { row.push(shift.clone()); } @@ -366,7 +357,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { let mut table = NuTable::from(data); table.set_index_style(get_index_style(cfg.opts.style_computer)); table.set_header_style(get_header_style(cfg.opts.style_computer)); - table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1); + table.set_indent(cfg.opts.indent.left, cfg.opts.indent.right); set_data_styles(&mut table, data_styles); Ok(Some(TableOutput::new(table, true, with_index, rows_count))) @@ -410,8 +401,8 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult { key.insert(0, '\n'); } - let key = NuTableCell::new(key); - let val = NuTableCell::new(cell.text); + let key = NuRecordsValue::new(key); + let val = NuRecordsValue::new(cell.text); let row = vec![key, val]; data.push(row); @@ -421,7 +412,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult { let mut table = NuTable::from(data); table.set_index_style(get_key_style(&cfg)); - table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1); + table.set_indent(cfg.opts.indent.left, cfg.opts.indent.right); let out = TableOutput::new(table, false, true, count_rows); diff --git a/crates/nu-table/src/types/general.rs b/crates/nu-table/src/types/general.rs index e748b6eff2..f4ab8482c3 100644 --- a/crates/nu-table/src/types/general.rs +++ b/crates/nu-table/src/types/general.rs @@ -2,10 +2,10 @@ use super::has_index; use crate::{ clean_charset, colorize_space, common::{ - create_nu_table_config, get_empty_style, get_header_style, get_index_style, + check_value, create_nu_table_config, get_empty_style, get_header_style, get_index_style, get_value_style, nu_value_to_string_colored, NuText, INDEX_COLUMN_NAME, }, - NuTable, NuTableCell, StringResult, TableOpts, TableOutput, TableResult, + NuRecordsValue, NuTable, StringResult, TableOpts, TableOutput, TableResult, }; use nu_color_config::TextStyle; use nu_engine::column::get_columns; @@ -15,7 +15,7 @@ pub struct JustTable; impl JustTable { pub fn table(input: &[Value], opts: TableOpts<'_>) -> StringResult { - create_table(input, opts) + list_table(input, opts) } pub fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult { @@ -23,32 +23,35 @@ impl JustTable { } } -fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result, ShellError> { - match table(input, &opts)? { - Some(mut out) => { - let left = opts.config.table.padding.left; - let right = opts.config.table.padding.right; - out.table.set_indent(left, right); +fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result, ShellError> { + let mut out = match create_table(input, &opts)? { + Some(out) => out, + None => return Ok(None), + }; - colorize_space(out.table.get_records_mut(), opts.style_computer); + out.table.set_indent( + opts.config.table.padding.left, + opts.config.table.padding.right, + ); - let table_config = - create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode); - Ok(out.table.draw(table_config, opts.width)) - } - None => Ok(None), - } + colorize_space(out.table.get_records_mut(), opts.style_computer); + + let config = create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode); + let table = out.table.draw(config, opts.width); + + Ok(table) } fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult { let mut data = vec![Vec::with_capacity(2); record.len()]; + for ((column, value), row) in record.iter().zip(data.iter_mut()) { opts.signals.check(opts.span)?; - let value = nu_value_to_string_colored(value, opts.config, opts.style_computer); + let key = NuRecordsValue::new(column.to_string()); - let key = NuTableCell::new(column.to_string()); - let value = NuTableCell::new(value); + let value = nu_value_to_string_colored(value, opts.config, opts.style_computer); + let value = NuRecordsValue::new(value); row.push(key); row.push(value); @@ -56,14 +59,12 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult { let mut table = NuTable::from(data); table.set_index_style(TextStyle::default_field()); - let count_rows = table.count_rows(); - - let mut out = TableOutput::new(table, false, true, count_rows); - - let left = opts.config.table.padding.left; - let right = opts.config.table.padding.right; - out.table.set_indent(left, right); + table.set_indent( + opts.config.table.padding.left, + opts.config.table.padding.right, + ); + let out = TableOutput::from_table(table, false, true); let table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode); let table = out.table.draw(table_config, opts.width); @@ -71,29 +72,33 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult { Ok(table) } -fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult { +fn create_table(input: &[Value], opts: &TableOpts<'_>) -> TableResult { if input.is_empty() { return Ok(None); } - let mut headers = get_columns(input); + let headers = get_columns(input); let with_index = has_index(opts, &headers); + let with_header = !headers.is_empty(); let row_offset = opts.index_offset; - let with_header = !headers.is_empty(); - if !with_header { - let table = to_table_with_no_header(input, with_index, row_offset, opts)?; - let table = table.map(|table| { - let count_rows = table.count_rows(); - TableOutput::new(table, false, with_index, count_rows) - }); - return Ok(table); - } + let table = match (with_header, with_index) { + (true, true) => create_table_with_header_and_index(input, headers, row_offset, opts)?, + (true, false) => create_table_with_header(input, headers, opts)?, + (false, true) => create_table_with_no_header_and_index(input, row_offset, opts)?, + (false, false) => create_table_with_no_header(input, opts)?, + }; - if with_header && with_index { - headers.insert(0, "#".into()); - } + let table = table.map(|table| TableOutput::from_table(table, with_header, with_index)); + Ok(table) +} + +fn create_table_with_header( + input: &[Value], + headers: Vec, + opts: &TableOpts<'_>, +) -> Result, ShellError> { // The header with the INDEX is removed from the table headers since // it is added to the natural table index let headers: Vec<_> = headers @@ -101,80 +106,113 @@ fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult { .filter(|header| header != INDEX_COLUMN_NAME) .collect(); - let table = to_table_with_header(input, &headers, with_index, row_offset, opts)?; - let table = table.map(|table| { - let count_rows = table.count_rows(); - TableOutput::new(table, true, with_index, count_rows) - }); - - Ok(table) -} - -fn to_table_with_header( - input: &[Value], - headers: &[String], - with_index: bool, - row_offset: usize, - opts: &TableOpts<'_>, -) -> Result, ShellError> { let count_rows = input.len() + 1; let count_columns = headers.len(); let mut table = NuTable::new(count_rows, count_columns); + table.set_header_style(get_header_style(opts.style_computer)); table.set_index_style(get_index_style(opts.style_computer)); - for (i, text) in headers.iter().enumerate() { - table.insert((0, i), text.to_owned()); - } + table.insert_row(0, headers.clone()); for (row, item) in input.iter().enumerate() { opts.signals.check(opts.span)?; + check_value(item)?; - if let Value::Error { error, .. } = item { - return Err(*error.clone()); - } - - if with_index { - let text = get_table_row_index(item, opts.config, row, row_offset); - table.insert((row + 1, 0), text); - } - - let skip_index = usize::from(with_index); - for (col, header) in headers.iter().enumerate().skip(skip_index) { + for (col, header) in headers.iter().enumerate() { let (text, style) = get_string_value_with_header(item, header, opts); - table.insert((row + 1, col), text); - table.insert_style((row + 1, col), style); + let pos = (row + 1, col); + table.insert(pos, text); + table.insert_style(pos, style); } } Ok(Some(table)) } -fn to_table_with_no_header( +fn create_table_with_header_and_index( input: &[Value], - with_index: bool, + headers: Vec, row_offset: usize, opts: &TableOpts<'_>, ) -> Result, ShellError> { - let mut table = NuTable::new(input.len(), with_index as usize + 1); + // The header with the INDEX is removed from the table headers since + // it is added to the natural table index + let mut headers: Vec<_> = headers + .into_iter() + .filter(|header| header != INDEX_COLUMN_NAME) + .collect(); + + headers.insert(0, "#".into()); + + let count_rows = input.len() + 1; + let count_columns = headers.len(); + let mut table = NuTable::new(count_rows, count_columns); + + table.set_header_style(get_header_style(opts.style_computer)); + table.set_index_style(get_index_style(opts.style_computer)); + + table.insert_row(0, headers.clone()); + + for (row, item) in input.iter().enumerate() { + opts.signals.check(opts.span)?; + check_value(item)?; + + let text = get_table_row_index(item, opts.config, row, row_offset); + table.insert((row + 1, 0), text); + + for (col, header) in headers.iter().enumerate().skip(1) { + let (text, style) = get_string_value_with_header(item, header, opts); + + let pos = (row + 1, col); + table.insert(pos, text); + table.insert_style(pos, style); + } + } + + Ok(Some(table)) +} + +fn create_table_with_no_header( + input: &[Value], + opts: &TableOpts<'_>, +) -> Result, ShellError> { + let mut table = NuTable::new(input.len(), 1); table.set_index_style(get_index_style(opts.style_computer)); for (row, item) in input.iter().enumerate() { opts.signals.check(opts.span)?; - - if let Value::Error { error, .. } = item { - return Err(*error.clone()); - } - - if with_index { - let text = get_table_row_index(item, opts.config, row, row_offset); - table.insert((row, 0), text); - } + check_value(item)?; let (text, style) = get_string_value(item, opts); - let pos = (row, with_index as usize); + let pos = (row, 0); + table.insert(pos, text); + table.insert_style(pos, style); + } + + Ok(Some(table)) +} + +fn create_table_with_no_header_and_index( + input: &[Value], + row_offset: usize, + opts: &TableOpts<'_>, +) -> Result, ShellError> { + let mut table = NuTable::new(input.len(), 1 + 1); + table.set_index_style(get_index_style(opts.style_computer)); + + for (row, item) in input.iter().enumerate() { + opts.signals.check(opts.span)?; + check_value(item)?; + + let text = get_table_row_index(item, opts.config, row, row_offset); + table.insert((row, 0), text); + + let (text, style) = get_string_value(item, opts); + + let pos = (row, 1); table.insert(pos, text); table.insert_style(pos, style); } @@ -194,8 +232,9 @@ fn get_string_value_with_header(item: &Value, header: &str, opts: &TableOpts) -> fn get_string_value(item: &Value, opts: &TableOpts) -> NuText { let (mut text, style) = get_value_style(item, opts.config, opts.style_computer); - let is_string_value = matches!(item, Value::String { .. }); - if is_string_value { + + let is_string = matches!(item, Value::String { .. }); + if is_string { text = clean_charset(&text); } diff --git a/crates/nu-table/src/types/mod.rs b/crates/nu-table/src/types/mod.rs index 829c87ed9e..d1e5c1a500 100644 --- a/crates/nu-table/src/types/mod.rs +++ b/crates/nu-table/src/types/mod.rs @@ -1,5 +1,5 @@ use nu_color_config::StyleComputer; -use nu_protocol::{Config, Signals, Span, TableIndexMode, TableMode}; +use nu_protocol::{Config, Signals, Span, TableIndent, TableIndexMode, TableMode}; use crate::{common::INDEX_COLUMN_NAME, NuTable}; @@ -15,6 +15,8 @@ pub struct TableOutput { pub table: NuTable, pub with_header: bool, pub with_index: bool, + /// The value may be bigger then table.count_rows(), + /// Specifically in case of expanded table we collect the whole structure size here. pub count_rows: usize, } @@ -27,6 +29,10 @@ impl TableOutput { count_rows, } } + pub fn from_table(table: NuTable, with_header: bool, with_index: bool) -> Self { + let count_rows = table.count_rows(); + Self::new(table, with_header, with_index, count_rows) + } } #[derive(Debug, Clone)] @@ -36,7 +42,7 @@ pub struct TableOpts<'a> { style_computer: &'a StyleComputer<'a>, span: Span, width: usize, - indent: (usize, usize), + indent: TableIndent, mode: TableMode, index_offset: usize, index_remove: bool, @@ -50,7 +56,7 @@ impl<'a> TableOpts<'a> { signals: &'a Signals, span: Span, width: usize, - indent: (usize, usize), + indent: TableIndent, mode: TableMode, index_offset: usize, index_remove: bool, diff --git a/crates/nu-table/src/unstructured_table.rs b/crates/nu-table/src/unstructured_table.rs index b55fea9c58..21c9b84f87 100644 --- a/crates/nu-table/src/unstructured_table.rs +++ b/crates/nu-table/src/unstructured_table.rs @@ -1,6 +1,6 @@ -use crate::{string_width, string_wrap, TableTheme}; use nu_color_config::StyleComputer; -use nu_protocol::{Config, Record, Span, Value}; +use nu_protocol::{Config, Record, Span, TableIndent, Value}; + use tabled::{ grid::{ ansi::{ANSIBuf, ANSIStr}, @@ -11,9 +11,11 @@ use tabled::{ tables::{PoolTable, TableValue}, }; +use crate::{is_color_empty, string_width, string_wrap, TableTheme}; + /// UnstructuredTable has a recursive table representation of nu_protocol::Value. /// -/// It doesn't support alignment and a proper width control. +/// It doesn't support alignment and a proper width control (allthough it's possible to achieve). pub struct UnstructuredTable { value: TableValue, } @@ -34,28 +36,23 @@ impl UnstructuredTable { truncate_table_value(&mut self.value, has_vertical, available).is_none() } - pub fn draw( - self, - style_computer: &StyleComputer, - theme: &TableTheme, - indent: (usize, usize), - ) -> String { - build_table(self.value, style_computer, theme, indent) + pub fn draw(self, theme: &TableTheme, indent: TableIndent, style: &StyleComputer) -> String { + build_table(self.value, style, theme, indent) } } fn build_table( val: TableValue, - style_computer: &StyleComputer, + style: &StyleComputer, theme: &TableTheme, - indent: (usize, usize), + indent: TableIndent, ) -> String { let mut table = PoolTable::from(val); let mut theme = theme.as_full().clone(); theme.set_horizontal_lines(Default::default()); - table.with(Padding::new(indent.0, indent.1, 0, 0)); + table.with(Padding::new(indent.left, indent.right, 0, 0)); table.with(SetRawStyle(theme)); table.with(SetAlignment(AlignmentHorizontal::Left)); table.with(PoolTableDimension::new( @@ -64,25 +61,12 @@ fn build_table( )); // color_config closures for "separator" are just given a null. - let color = style_computer.compute("separator", &Value::nothing(Span::unknown())); + let color = style.compute("separator", &Value::nothing(Span::unknown())); let color = color.paint(" ").to_string(); if let Ok(color) = Color::try_from(color) { - // # SAFETY - // - // It's perfectly save to do cause table does not store the reference internally. - // We just need this unsafe section to cope with some limitations of [`PoolTable`]. - // Mitigation of this is definitely on a todo list. - - let color: ANSIBuf = color.into(); - let prefix = color.get_prefix(); - let suffix = color.get_suffix(); - let prefix: &'static str = unsafe { std::mem::transmute(prefix) }; - let suffix: &'static str = unsafe { std::mem::transmute(suffix) }; - - table.with(SetBorderColor(ANSIStr::new(prefix, suffix))); - let table = table.to_string(); - - return table; + if !is_color_empty(&color) { + table.with(SetBorderColor(color_into_ansistr(color))); + } } table.to_string() @@ -348,3 +332,19 @@ fn truncate_table_value( } } } + +fn color_into_ansistr(color: Color) -> ANSIStr<'static> { + // # SAFETY + // + // It's perfectly save to do cause table does not store the reference internally. + // We just need this unsafe section to cope with some limitations of [`PoolTable`]. + // Mitigation of this is definitely on a todo list. + + let color: ANSIBuf = color.into(); + let prefix = color.get_prefix(); + let suffix = color.get_suffix(); + let prefix: &'static str = unsafe { std::mem::transmute(prefix) }; + let suffix: &'static str = unsafe { std::mem::transmute(suffix) }; + + ANSIStr::new(prefix, suffix) +} diff --git a/crates/nu-table/src/util.rs b/crates/nu-table/src/util.rs index feb9a59616..eca68fb310 100644 --- a/crates/nu-table/src/util.rs +++ b/crates/nu-table/src/util.rs @@ -1,12 +1,15 @@ use nu_color_config::StyleComputer; + use tabled::{ - builder::Builder, grid::{ ansi::{ANSIBuf, ANSIStr}, records::vec_records::Text, util::string::get_text_width, }, - settings::{width::Truncate, Color, Modify, Padding, Style, Width}, + settings::{ + width::{Truncate, Wrap}, + Color, + }, }; use crate::common::get_leading_trailing_space_style; @@ -16,34 +19,16 @@ pub fn string_width(text: &str) -> usize { } pub fn string_wrap(text: &str, width: usize, keep_words: bool) -> String { - // todo: change me... - // - // well... it's not efficient to build a table to wrap a string, - // but ... it's better than a copy paste (is it?) - if text.is_empty() { return String::new(); } - let wrap = if keep_words { - Width::wrap(width).keep_words(true) - } else { - Width::wrap(width) - }; - - Builder::from_iter([[text]]) - .build() - .with(Style::empty()) - .with(Padding::zero()) - .with(Modify::new((0, 0)).with(wrap)) - .to_string() + Wrap::wrap(text, width, keep_words) } pub fn string_truncate(text: &str, width: usize) -> String { - // todo: change me... - let line = match text.lines().next() { - Some(first_line) => first_line, + Some(line) => line, None => return String::new(), }; @@ -51,35 +36,75 @@ pub fn string_truncate(text: &str, width: usize) -> String { } pub fn clean_charset(text: &str) -> String { - // todo: optimize, I bet it can be done in 1 path - text.replace('\t', " ").replace('\r', "") + // TODO: We could make an optimization to take a String and modify it + // We could check if there was any changes and if not make no allocations at all and don't change the origin. + // Why it's not done... + // Cause I am not sure how the `if` in a loop will affect performance. + // So it's better be profiled, but likely the optimization be worth it. + // At least because it's a base case where we won't change anything.... + + // allocating at least the text size, + // in most cases the buf will be a copy of text anyhow. + // + // but yes sometimes we will alloc more then nessary. + // We could shrink it but...it will be another realloc which make no scense. + let mut buf = String::with_capacity(text.len()); + + for c in text.chars() { + if c == '\n' { + buf.push(c); + continue; + } + + if c == '\t' { + buf.push(' '); + buf.push(' '); + buf.push(' '); + buf.push(' '); + continue; + } + + if c < ' ' { + continue; + } + + buf.push(c); + } + + buf } pub fn colorize_space(data: &mut [Vec>], style_computer: &StyleComputer<'_>) { - if let Some(style) = get_leading_trailing_space_style(style_computer).color_style { - let style = ANSIBuf::from(convert_style(style)); - let style = style.as_ref(); - colorize_lead_trail_space(data, Some(style), Some(style)); - } -} + let style = match get_leading_trailing_space_style(style_computer).color_style { + Some(color) => color, + None => return, + }; -pub fn colorize_space_str(text: &mut String, style_computer: &StyleComputer<'_>) { - if let Some(style) = get_leading_trailing_space_style(style_computer).color_style { - let style = ANSIBuf::from(convert_style(style)); - let style = style.as_ref(); - *text = colorize_space_one(text, Some(style), Some(style)); - } -} - -fn colorize_lead_trail_space( - data: &mut [Vec>], - lead: Option>, - trail: Option>, -) { - if lead.is_none() && trail.is_none() { + let style = ANSIBuf::from(convert_style(style)); + let style = style.as_ref(); + if style.is_empty() { return; } + colorize_list(data, style, style); +} + +pub fn colorize_space_str(text: &mut String, style_computer: &StyleComputer<'_>) { + let style = match get_leading_trailing_space_style(style_computer).color_style { + Some(color) => color, + None => return, + }; + + let style = ANSIBuf::from(convert_style(style)); + let style = style.as_ref(); + if style.is_empty() { + return; + } + + *text = colorize_space_one(text, style, style); +} + +fn colorize_list(data: &mut [Vec>], lead: ANSIStr<'_>, trail: ANSIStr<'_>) { for row in data.iter_mut() { for cell in row { let buf = colorize_space_one(cell.as_ref(), lead, trail); @@ -88,7 +113,7 @@ fn colorize_lead_trail_space( } } -fn colorize_space_one(text: &str, lead: Option>, trail: Option>) -> String { +fn colorize_space_one(text: &str, lead: ANSIStr<'_>, trail: ANSIStr<'_>) -> String { use fancy_regex::Captures; use fancy_regex::Regex; use std::sync::LazyLock; @@ -102,20 +127,20 @@ fn colorize_space_one(text: &str, lead: Option>, trail: Option>, trail: Option Color { Color::new(style.prefix().to_string(), style.suffix().to_string()) } + +pub fn is_color_empty(c: &Color) -> bool { + c.get_prefix().is_empty() && c.get_suffix().is_empty() +}