diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index dd7ddb4235..f5ef483b5f 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -362,7 +362,8 @@ fn handle_record( let result = if cols.is_empty() { create_empty_placeholder("record", term_width, engine_state, stack) } else { - let opts = TableOpts::new(config, style_computer, ctrlc, span, 0, term_width); + let indent = (config.table_indent.left, config.table_indent.right); + let opts = TableOpts::new(config, style_computer, ctrlc, span, 0, term_width, indent); let result = build_table_kv(cols, vals, table_view, opts, span)?; match result { Some(output) => maybe_strip_color(output, config), @@ -691,6 +692,7 @@ impl PagingTableCreator { self.head, self.row_offset, get_width_param(self.width_param), + (cfg.table_indent.left, cfg.table_indent.right), ) } } diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index 826cbae94d..e2c8b6f93a 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -2560,3 +2560,107 @@ fn theme_cmd(theme: &str, footer: bool, then: &str) -> String { format!("$env.config.table.mode = {theme}; $env.config.table.header_on_separator = true; {with_foorter}; {then}") } + +#[test] +fn table_padding_not_default() { + let actual = nu!("$env.config.table.padding = 5; [[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table"); + assert_eq!( + actual.out, + "╭───────────┬───────────┬───────────┬────────────────────────╮\ + │ # │ a │ b │ c │\ + ├───────────┼───────────┼───────────┼────────────────────────┤\ + │ 0 │ 1 │ 2 │ 3 │\ + │ 1 │ 4 │ 5 │ [list 3 items] │\ + ╰───────────┴───────────┴───────────┴────────────────────────╯" + ); +} + +#[test] +fn table_padding_zero() { + let actual = nu!( + "$env.config.table.padding = {left: 0, right: 0}; [[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table" + ); + assert_eq!( + actual.out, + "╭─┬─┬─┬──────────────╮\ + │#│a│b│ c │\ + ├─┼─┼─┼──────────────┤\ + │0│1│2│ 3│\ + │1│4│5│[list 3 items]│\ + ╰─┴─┴─┴──────────────╯" + ); +} + +#[test] +fn table_expand_padding_not_default() { + let actual = nu!("$env.config.table.padding = 5; [[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table -e"); + assert_eq!( + actual.out, + "╭───────────┬───────────┬───────────┬───────────────────────────────────╮\ + │ # │ a │ b │ c │\ + ├───────────┼───────────┼───────────┼───────────────────────────────────┤\ + │ 0 │ 1 │ 2 │ 3 │\ + │ 1 │ 4 │ 5 │ ╭───────────┬───────────╮ │\ + │ │ │ │ │ 0 │ 1 │ │\ + │ │ │ │ │ 1 │ 2 │ │\ + │ │ │ │ │ 2 │ 3 │ │\ + │ │ │ │ ╰───────────┴───────────╯ │\ + ╰───────────┴───────────┴───────────┴───────────────────────────────────╯" + ); +} + +#[test] +fn table_expand_padding_zero() { + let actual = nu!("$env.config.table.padding = {left: 0, right: 0}; [[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table -e"); + assert_eq!( + actual.out, + "╭─┬─┬─┬─────╮\ + │#│a│b│ c │\ + ├─┼─┼─┼─────┤\ + │0│1│2│ 3│\ + │1│4│5│╭─┬─╮│\ + │ │ │ ││0│1││\ + │ │ │ ││1│2││\ + │ │ │ ││2│3││\ + │ │ │ │╰─┴─╯│\ + ╰─┴─┴─┴─────╯" + ); +} + +#[test] +fn table_collapse_padding_not_default() { + let actual = nu!("$env.config.table.padding = 5; [[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table -c"); + assert_eq!( + actual.out, + "╭───────────┬───────────┬───────────╮\ + │ a │ b │ c │\ + ├───────────┼───────────┼───────────┤\ + │ 1 │ 2 │ 3 │\ + ├───────────┼───────────┼───────────┤\ + │ 4 │ 5 │ 1 │\ + │ │ ├───────────┤\ + │ │ │ 2 │\ + │ │ ├───────────┤\ + │ │ │ 3 │\ + ╰───────────┴───────────┴───────────╯" + ); +} + +#[test] +fn table_collapse_padding_zero() { + let actual = nu!("$env.config.table.padding = {left: 0, right: 0}; [[a b, c]; [1 2 3] [4 5 [1 2 3]]] | table -c"); + assert_eq!( + actual.out, + "╭─┬─┬─╮\ + │a│b│c│\ + ├─┼─┼─┤\ + │1│2│3│\ + ├─┼─┼─┤\ + │4│5│1│\ + │ │ ├─┤\ + │ │ │2│\ + │ │ ├─┤\ + │ │ │3│\ + ╰─┴─┴─╯" + ); +} diff --git a/crates/nu-explore/src/nu_common/table.rs b/crates/nu-explore/src/nu_common/table.rs index 345e8a1113..1d36a74f0f 100644 --- a/crates/nu-explore/src/nu_common/table.rs +++ b/crates/nu-explore/src/nu_common/table.rs @@ -42,6 +42,7 @@ fn try_build_map( Span::unknown(), 0, usize::MAX, + (config.table_indent.left, config.table_indent.right), ); let result = ExpandedTable::new(None, false, String::new()).build_map(&cols, &vals, opts); match result { @@ -66,6 +67,7 @@ fn try_build_list( Span::unknown(), 0, usize::MAX, + (config.table_indent.left, config.table_indent.right), ); let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts); match result { diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index fea9e8da6f..925fbf2076 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -75,6 +75,7 @@ pub struct Config { pub table_mode: String, pub table_move_header: bool, pub table_show_empty: bool, + pub table_indent: TableIndent, pub use_ls_colors: bool, pub color_config: HashMap, pub use_grid_icons: bool, @@ -131,6 +132,7 @@ impl Default for Config { table_show_empty: true, trim_strategy: TRIM_STRATEGY_DEFAULT, table_move_header: false, + table_indent: TableIndent { left: 1, right: 1 }, datetime_normal_format: None, datetime_table_format: None, @@ -243,6 +245,12 @@ impl TrimStrategy { } } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TableIndent { + pub left: usize, + pub right: usize, +} + impl Value { pub fn into_config(&mut self, config: &Config) -> (Config, Option) { // Clone the passed-in config rather than mutating it. @@ -934,6 +942,55 @@ impl Value { "header_on_separator" => { try_bool!(cols, vals, index, span, table_move_header) } + "padding" => match value { + Value::Int { val, .. } => { + if *val < 0 { + invalid!(Some(span), "unexpected $env.config.{key}.{key2} '{val}'; expected a unsigned integer"); + } + + config.table_indent.left = *val as usize; + config.table_indent.right = *val as usize; + } + Value::Record { vals, cols, .. } => { + let left = cols.iter().position(|e| e == "left"); + let right = cols.iter().position(|e| e == "right"); + + if let Some(i) = left { + let value = vals[i].as_int(); + match value { + Ok(val) => { + if val < 0 { + invalid!(Some(span), "unexpected $env.config.{key}.{key2} '{val}'; expected a unsigned integer"); + } + + config.table_indent.left = val as usize; + } + Err(_) => { + invalid!(Some(span), "unexpected $env.config.{key}.{key2} value; expected a unsigned integer or a record"); + } + } + } + + if let Some(i) = right { + let value = vals[i].as_int(); + match value { + Ok(val) => { + if val < 0 { + invalid!(Some(span), "unexpected $env.config.{key}.{key2} '{val}'; expected a unsigned integer"); + } + + config.table_indent.right = val as usize; + } + Err(_) => { + invalid!(Some(span), "unexpected $env.config.{key}.{key2} value; expected a unsigned integer or a record"); + } + } + } + } + _ => { + invalid!(Some(span), "unexpected $env.config.{key}.{key2} value; expected a unsigned integer or a record"); + } + }, "index_mode" => { if let Ok(b) = value.as_string() { let val_str = b.to_lowercase(); diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 2d0ec77ad0..bf81beea23 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -18,7 +18,7 @@ use tabled::{ }, settings::{ formatting::AlignmentStrategy, object::Segment, peaker::Peaker, themes::ColumnNames, Color, - Modify, Settings, TableOption, Width, + Modify, Padding, Settings, TableOption, Width, }, Table, }; @@ -29,6 +29,7 @@ pub struct NuTable { data: NuTableData, styles: Styles, alignments: Alignments, + indent: (usize, usize), } type NuTableData = VecRecords; @@ -57,6 +58,7 @@ impl NuTable { Self { data: VecRecords::new(vec![vec![CellInfo::default(); count_columns]; count_rows]), styles: Styles::default(), + indent: (1, 1), alignments: Alignments { data: AlignmentHorizontal::Left, index: AlignmentHorizontal::Right, @@ -135,11 +137,22 @@ impl NuTable { self.alignments.data = convert_alignment(style.alignment); } + pub fn set_indent(&mut self, left: usize, right: usize) { + self.indent = (left, right); + } + /// Converts a table to a String. /// /// It returns None in case where table cannot be fit to a terminal width. pub fn draw(self, config: NuTableConfig, termwidth: usize) -> Option { - build_table(self.data, config, self.alignments, self.styles, termwidth) + build_table( + self.data, + config, + self.alignments, + self.styles, + termwidth, + self.indent, + ) } /// Return a total table width. @@ -192,6 +205,7 @@ fn build_table( alignments: Alignments, styles: Styles, termwidth: usize, + indent: (usize, usize), ) -> Option { if data.count_columns() == 0 || data.count_rows() == 0 { return Some(String::new()); @@ -206,7 +220,7 @@ fn build_table( duplicate_row(&mut data, 0); } - draw_table(data, alignments, styles, widths, cfg, termwidth) + draw_table(data, alignments, styles, widths, cfg, termwidth, indent) } fn draw_table( @@ -216,6 +230,7 @@ fn draw_table( widths: Vec, cfg: NuTableConfig, termwidth: usize, + indent: (usize, usize), ) -> Option { let with_index = cfg.with_index; let with_header = cfg.with_header && data.count_rows() > 1; @@ -226,6 +241,7 @@ fn draw_table( let data: Vec> = data.into(); let mut table = Builder::from(data).build(); + set_indent(&mut table, indent.0, indent.1); load_theme(&mut table, &cfg.theme, with_footer, with_header, sep_color); align_table(&mut table, alignments, with_index, with_header, with_footer); colorize_table(&mut table, styles, with_index, with_header, with_footer); @@ -240,6 +256,10 @@ fn draw_table( build_table_with_width_check(table, total_width, termwidth) } +fn set_indent(table: &mut Table, left: usize, right: usize) { + table.with(Padding::new(left, right, 0, 0)); +} + fn set_border_head(table: &mut Table, with_footer: bool) { let count_rows = table.count_rows(); diff --git a/crates/nu-table/src/types/collapse.rs b/crates/nu-table/src/types/collapse.rs index f42af91242..b2367d4bfb 100644 --- a/crates/nu-table/src/types/collapse.rs +++ b/crates/nu-table/src/types/collapse.rs @@ -32,7 +32,8 @@ fn collapsed_table( return Ok(None); } - let table = table.draw(style_computer, &theme); + let indent = (config.table_indent.left, config.table_indent.right); + let table = table.draw(style_computer, &theme, indent); Ok(Some(table)) } diff --git a/crates/nu-table/src/types/expanded.rs b/crates/nu-table/src/types/expanded.rs index 5c904d683c..458478496d 100644 --- a/crates/nu-table/src/types/expanded.rs +++ b/crates/nu-table/src/types/expanded.rs @@ -174,6 +174,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_index_style(get_index_style(cfg.opts.style_computer)); set_data_styles(&mut table, data_styles); @@ -333,6 +334,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); set_data_styles(&mut table, data_styles); Ok(Some(TableOutput::new(table, true, with_index))) @@ -378,6 +380,7 @@ fn expanded_table_kv(cols: &[String], vals: &[Value], cfg: Cfg<'_>) -> StringRes 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); let out = TableOutput::new(table, false, true); diff --git a/crates/nu-table/src/types/general.rs b/crates/nu-table/src/types/general.rs index 7f403bad14..bc32c91969 100644 --- a/crates/nu-table/src/types/general.rs +++ b/crates/nu-table/src/types/general.rs @@ -25,7 +25,11 @@ impl JustTable { fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result, ShellError> { match table(input, opts.row_offset, opts.clone())? { - Some(out) => { + Some(mut out) => { + let left = opts.config.table_indent.left; + let right = opts.config.table_indent.right; + out.table.set_indent(left, right); + let table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false); Ok(out.table.draw(table_config, opts.width)) @@ -56,7 +60,12 @@ fn kv_table(cols: &[String], vals: &[Value], opts: TableOpts<'_>) -> StringResul let mut table = NuTable::from(data); table.set_index_style(TextStyle::default_field()); - let out = TableOutput::new(table, false, true); + let mut out = TableOutput::new(table, false, true); + + let left = opts.config.table_indent.left; + let right = opts.config.table_indent.right; + out.table.set_indent(left, right); + let table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false); let table = out.table.draw(table_config, opts.width); diff --git a/crates/nu-table/src/types/mod.rs b/crates/nu-table/src/types/mod.rs index cceb895b0b..ad3625a81d 100644 --- a/crates/nu-table/src/types/mod.rs +++ b/crates/nu-table/src/types/mod.rs @@ -36,6 +36,7 @@ pub struct TableOpts<'a> { span: Span, row_offset: usize, width: usize, + indent: (usize, usize), } impl<'a> TableOpts<'a> { @@ -45,7 +46,8 @@ impl<'a> TableOpts<'a> { ctrlc: Option>, span: Span, row_offset: usize, - available_width: usize, + width: usize, + indent: (usize, usize), ) -> Self { Self { ctrlc, @@ -53,7 +55,8 @@ impl<'a> TableOpts<'a> { style_computer, span, row_offset, - width: available_width, + indent, + width, } } } diff --git a/crates/nu-table/src/unstructured_table.rs b/crates/nu-table/src/unstructured_table.rs index d0ea2e73d9..bc3196cb49 100644 --- a/crates/nu-table/src/unstructured_table.rs +++ b/crates/nu-table/src/unstructured_table.rs @@ -6,7 +6,7 @@ use tabled::{ config::{AlignmentHorizontal, Borders, CompactMultilineConfig}, dimension::{DimensionPriority, PoolTableDimension}, }, - settings::{style::RawStyle, Color, TableOption}, + settings::{style::RawStyle, Color, Padding, TableOption}, tables::{PoolTable, TableValue}, }; @@ -35,17 +35,28 @@ impl UnstructuredTable { truncate_table_value(&mut self.value, has_vertical, available).is_none() } - pub fn draw(self, style_computer: &StyleComputer, theme: &TableTheme) -> String { - build_table(self.value, style_computer, theme) + pub fn draw( + self, + style_computer: &StyleComputer, + theme: &TableTheme, + indent: (usize, usize), + ) -> String { + build_table(self.value, style_computer, theme, indent) } } -fn build_table(val: TableValue, style_computer: &StyleComputer, theme: &TableTheme) -> String { +fn build_table( + val: TableValue, + style_computer: &StyleComputer, + theme: &TableTheme, + indent: (usize, usize), +) -> String { let mut table = PoolTable::from(val); let mut theme = theme.get_theme_full(); theme.set_horizontals(std::collections::HashMap::default()); + table.with(Padding::new(indent.0, indent.1, 0, 0)); table.with(SetRawStyle(theme)); table.with(SetAlignment(AlignmentHorizontal::Left)); table.with(PoolTableDimension::new( diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index e7d2ba630f..9d3f6753ee 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -158,6 +158,7 @@ $env.config = { mode: rounded # basic, compact, compact_double, light, thin, with_love, rounded, reinforced, heavy, none, other index_mode: always # "always" show indexes, "never" show indexes, "auto" = show indexes when a table has "index" column show_empty: true # show 'empty list' and 'empty record' placeholders for command output + padding: { left: 1, right: 1 } # a left right padding of each column in a table trim: { methodology: wrapping # wrapping or truncating wrapping_try_keep_words: true # A strategy used by the 'wrapping' methodology