From 25629b41b662e87edb54b594baec962c23ced29c Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Mon, 9 Dec 2024 02:36:38 +0300 Subject: [PATCH] More refactorings --- crates/nu-command/src/viewers/table.rs | 397 +++++++++++------------ crates/nu-explore/src/commands/expand.rs | 9 +- crates/nu-explore/src/nu_common/table.rs | 71 ++-- crates/nu-table/src/table.rs | 5 + crates/nu-table/src/types/collapse.rs | 4 +- crates/nu-table/src/types/expanded.rs | 113 ++++--- crates/nu-table/src/types/general.rs | 69 ++-- crates/nu-table/src/types/mod.rs | 25 +- 8 files changed, 337 insertions(+), 356 deletions(-) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 80a6e938e4..721d10192f 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -25,17 +25,10 @@ use std::{ use terminal_size::{Height, Width}; use url::Url; -const STREAM_PAGE_SIZE: usize = 1000; +type ShellResult = Result; -fn get_width_param(width_param: Option) -> usize { - if let Some(col) = width_param { - col as usize - } else if let Some((Width(w), Height(_))) = terminal_size::terminal_size() { - w as usize - } else { - 80 - } -} +const STREAM_PAGE_SIZE: usize = 1000; +const DEFAULT_TABLE_WIDTH: usize = 80; #[derive(Clone)] pub struct Table; @@ -119,16 +112,14 @@ impl Command for Table { stack: &mut Stack, call: &Call, input: PipelineData, - ) -> Result { + ) -> ShellResult { let list_themes: bool = call.has_flag(engine_state, stack, "list")?; // if list argument is present we just need to return a list of supported table themes if list_themes { let val = Value::list(supported_table_modes(), Span::test_data()); return Ok(val.into_pipeline_data()); } - let cwd = engine_state.cwd(Some(stack))?; - let cfg = parse_table_config(call, engine_state, stack)?; - let input = CmdInput::new(engine_state, stack, call, input); + let input = CmdInput::parse(engine_state, stack, call, input)?; // reset vt processing, aka ansi because illbehaved externals can break it #[cfg(windows)] @@ -136,7 +127,7 @@ impl Command for Table { let _ = nu_utils::enable_vt_processing(); } - handle_table_command(input, cfg, cwd) + handle_table_command(input) } fn examples(&self) -> Vec { @@ -221,74 +212,119 @@ impl Command for Table { #[derive(Debug, Clone)] struct TableConfig { - index: Option, - table_view: TableView, - term_width: usize, + view: TableView, + width: usize, theme: TableMode, abbreviation: Option, + index: Option, } impl TableConfig { fn new( - table_view: TableView, - term_width: usize, + view: TableView, + width: usize, theme: TableMode, abbreviation: Option, index: Option, ) -> Self { Self { - index, - table_view, - term_width, - abbreviation, + view, + width, theme, + abbreviation, + index, } } } +#[derive(Debug, Clone)] +enum TableView { + General, + Collapsed, + Expanded { + limit: Option, + flatten: bool, + flatten_separator: Option, + }, +} + +struct CLIArgs { + width: Option, + abbrivation: Option, + theme: TableMode, + expand: bool, + expand_limit: Option, + expand_flatten: bool, + expand_flatten_separator: Option, + collapse: bool, + index: Option, +} + fn parse_table_config( call: &Call, state: &EngineState, stack: &mut Stack, -) -> Result { - let width_param: Option = call.get_flag(state, stack, "width")?; - let expand: bool = call.has_flag(state, stack, "expand")?; - let expand_limit: Option = call.get_flag(state, stack, "expand-deep")?; - let collapse: bool = call.has_flag(state, stack, "collapse")?; - let flatten: bool = call.has_flag(state, stack, "flatten")?; - let flatten_separator: Option = call.get_flag(state, stack, "flatten-separator")?; - let abbrivation: Option = call - .get_flag(state, stack, "abbreviated")? - .or_else(|| stack.get_config(state).table.abbreviated_row_count); - let table_view = match (expand, collapse) { +) -> ShellResult { + let args = get_cli_args(call, state, stack)?; + let table_view = get_table_view(&args); + let term_width = get_table_width(args.width); + + let cfg = TableConfig::new( + table_view, + term_width, + args.theme, + args.abbrivation, + args.index, + ); + + Ok(cfg) +} + +fn get_table_view(args: &CLIArgs) -> TableView { + match (args.expand, args.collapse) { (false, false) => TableView::General, (_, true) => TableView::Collapsed, (true, _) => TableView::Expanded { - limit: expand_limit, - flatten, - flatten_separator, + limit: args.expand_limit, + flatten: args.expand_flatten, + flatten_separator: args.expand_flatten_separator.clone(), }, - }; + } +} + +fn get_cli_args(call: &Call<'_>, state: &EngineState, stack: &mut Stack) -> ShellResult { + let width: Option = call.get_flag(state, stack, "width")?; + let expand: bool = call.has_flag(state, stack, "expand")?; + let expand_limit: Option = call.get_flag(state, stack, "expand-deep")?; + let expand_flatten: bool = call.has_flag(state, stack, "flatten")?; + let expand_flatten_separator: Option = + call.get_flag(state, stack, "flatten-separator")?; + let collapse: bool = call.has_flag(state, stack, "collapse")?; + let abbrivation: Option = call + .get_flag(state, stack, "abbreviated")? + .or_else(|| stack.get_config(state).table.abbreviated_row_count); let theme = get_theme_flag(call, state, stack)?.unwrap_or_else(|| stack.get_config(state).table.mode); let index = get_index_flag(call, state, stack)?; - let term_width = get_width_param(width_param); - - Ok(TableConfig::new( - table_view, - term_width, - theme, + Ok(CLIArgs { abbrivation, + collapse, + expand, + expand_limit, + expand_flatten, + expand_flatten_separator, + width, + theme, index, - )) + }) } fn get_index_flag( call: &Call, state: &EngineState, stack: &mut Stack, -) -> Result, ShellError> { +) -> ShellResult> { let index: Option = call.get_flag(state, stack, "index")?; let value = match index { Some(value) => value, @@ -329,7 +365,7 @@ fn get_theme_flag( call: &Call, state: &EngineState, stack: &mut Stack, -) -> Result, ShellError> { +) -> ShellResult> { call.get_flag(state, stack, "theme")? .map(|theme: String| { TableMode::from_str(&theme).map_err(|err| ShellError::CantConvert { @@ -347,29 +383,36 @@ struct CmdInput<'a> { stack: &'a mut Stack, call: &'a Call<'a>, data: PipelineData, + cfg: TableConfig, + cwd: nu_path::PathBuf, } impl<'a> CmdInput<'a> { - fn new( + fn parse( engine_state: &'a EngineState, stack: &'a mut Stack, call: &'a Call<'a>, data: PipelineData, - ) -> Self { - Self { + ) -> ShellResult { + let cwd = engine_state.cwd(Some(stack))?; + let cfg = parse_table_config(call, engine_state, stack)?; + + Ok(Self { engine_state, stack, call, data, - } + cfg, + cwd, + }) + } + + fn get_config(&self) -> std::sync::Arc { + self.stack.get_config(self.engine_state) } } -fn handle_table_command( - mut input: CmdInput<'_>, - cfg: TableConfig, - cwd: nu_path::PathBuf, -) -> Result { +fn handle_table_command(mut input: CmdInput<'_>) -> ShellResult { let span = input.data.span().unwrap_or(input.call.head); match input.data { // Binary streams should behave as if they really are `binary` data, and printed as hex @@ -391,15 +434,15 @@ fn handle_table_command( let stream = ListStream::new(vals.into_iter(), span, signals); input.data = PipelineData::Empty; - handle_row_stream(input, cfg, stream, metadata, cwd) + handle_row_stream(input, stream, metadata) } PipelineData::ListStream(stream, metadata) => { input.data = PipelineData::Empty; - handle_row_stream(input, cfg, stream, metadata, cwd) + handle_row_stream(input, stream, metadata) } PipelineData::Value(Value::Record { val, .. }, ..) => { input.data = PipelineData::Empty; - handle_record(input, cfg, val.into_owned()) + handle_record(input, val.into_owned()) } PipelineData::Value(Value::Error { error, .. }, ..) => { // Propagate this error outward, so that it goes to stderr @@ -415,7 +458,7 @@ fn handle_table_command( let stream = ListStream::new(val.into_range_iter(span, Signals::empty()), span, signals); input.data = PipelineData::Empty; - handle_row_stream(input, cfg, stream, metadata, cwd) + handle_row_stream(input, stream, metadata) } x => Ok(x), } @@ -490,59 +533,55 @@ fn pretty_hex_stream(stream: ByteStream, span: Span) -> ByteStream { ) } -fn handle_record( - input: CmdInput, - cfg: TableConfig, - mut record: Record, -) -> Result { - let config = { - let state = input.engine_state; - let stack: &Stack = input.stack; - stack.get_config(state) - }; +fn handle_record(input: CmdInput, mut record: Record) -> ShellResult { let span = input.data.span().unwrap_or(input.call.head); - let styles = &StyleComputer::from_config(input.engine_state, input.stack); if record.is_empty() { let value = - create_empty_placeholder("record", cfg.term_width, input.engine_state, input.stack); + create_empty_placeholder("record", input.cfg.width, input.engine_state, input.stack); let value = Value::string(value, span); return Ok(value.into_pipeline_data()); }; - if let Some(limit) = cfg.abbreviation { - let prev_len = record.len(); - if record.len() > limit * 2 + 1 { - // TODO: see if the following table builders would be happy with a simple iterator - let mut record_iter = record.into_iter(); - record = Record::with_capacity(limit * 2 + 1); - record.extend(record_iter.by_ref().take(limit)); - record.push(String::from("..."), Value::string("...", Span::unknown())); - record.extend(record_iter.skip(prev_len - 2 * limit)); - } + if let Some(limit) = input.cfg.abbreviation { + record = make_record_abbreviation(record, limit); } - let opts = TableOpts::new( + let config = input.get_config(); + let opts = create_table_opts( + input.engine_state, + input.stack, &config, - styles, - input.engine_state.signals(), + &input.cfg, span, - cfg.term_width, - config.table.padding, - cfg.theme, - cfg.index.unwrap_or(0), - cfg.index.is_none(), + 0, ); - let result = build_table_kv(record, cfg.table_view, opts, span)?; + let result = build_table_kv(record, input.cfg.view.clone(), opts, span)?; let result = match result { Some(output) => maybe_strip_color(output, &config), - None => report_unsuccessful_output(input.engine_state.signals(), cfg.term_width), + None => report_unsuccessful_output(input.engine_state.signals(), input.cfg.width), }; let val = Value::string(result, span); + let data = val.into_pipeline_data(); - Ok(val.into_pipeline_data()) + Ok(data) +} + +fn make_record_abbreviation(mut record: Record, limit: usize) -> Record { + if record.len() <= limit * 2 + 1 { + return record; + } + + // TODO: see if the following table builders would be happy with a simple iterator + let prev_len = record.len(); + let mut record_iter = record.into_iter(); + record = Record::with_capacity(limit * 2 + 1); + record.extend(record_iter.by_ref().take(limit)); + record.push(String::from("..."), Value::string("...", Span::unknown())); + record.extend(record_iter.skip(prev_len - 2 * limit)); + record } fn report_unsuccessful_output(signals: &Signals, term_width: usize) -> String { @@ -580,11 +619,11 @@ fn build_table_kv( fn build_table_batch( vals: Vec, - table_view: TableView, + view: TableView, opts: TableOpts<'_>, span: Span, ) -> StringResult { - match table_view { + match view { TableView::General => JustTable::table(&vals, opts), TableView::Expanded { limit, @@ -603,22 +642,17 @@ fn build_table_batch( fn handle_row_stream( input: CmdInput<'_>, - cfg: TableConfig, stream: ListStream, metadata: Option, - cwd: nu_path::PathBuf, -) -> Result { +) -> ShellResult { + let cfg = input.get_config(); let stream = match metadata.as_ref() { // First, `ls` sources: Some(PipelineMetadata { data_source: DataSource::Ls, .. }) => { - let config = { - let state = input.engine_state; - let stack: &Stack = input.stack; - stack.get_config(state) - }; + let config = cfg.clone(); let ls_colors_env_str = match input.stack.get_env_var(input.engine_state, "LS_COLORS") { Some(v) => Some(env_to_string( "LS_COLORS", @@ -637,7 +671,7 @@ fn handle_row_stream( let span = value.span(); if let Value::String { val, .. } = value { if let Some(val) = - render_path_name(val, &config, &ls_colors, cwd.clone(), span) + render_path_name(val, &config, &ls_colors, input.cwd.clone(), span) { *value = val; } @@ -693,6 +727,7 @@ fn handle_row_stream( // for the values it outputs. Because engine_state is passed in, config doesn't need to. input.engine_state.clone(), input.stack.clone(), + input.cfg, cfg, ); let stream = ByteStream::from_result_iter( @@ -735,8 +770,9 @@ struct PagingTableCreator { stack: Stack, elements_displayed: usize, reached_end: bool, - cfg: TableConfig, + table_config: TableConfig, row_offset: usize, + config: std::sync::Arc, } impl PagingTableCreator { @@ -745,114 +781,51 @@ impl PagingTableCreator { stream: ListStream, engine_state: EngineState, stack: Stack, - cfg: TableConfig, + table_config: TableConfig, + config: std::sync::Arc, ) -> Self { PagingTableCreator { head, stream: stream.into_inner(), engine_state, stack, - cfg, + config, + table_config, elements_displayed: 0, reached_end: false, row_offset: 0, } } - fn build_extended( - &mut self, - batch: Vec, - limit: Option, - flatten: bool, - flatten_separator: Option, - ) -> StringResult { + fn build_table(&mut self, batch: Vec) -> ShellResult> { if batch.is_empty() { return Ok(None); } - let cfg = { - let state = &self.engine_state; - let stack = &self.stack; - stack.get_config(state) - }; - let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack); - let opts = self.create_table_opts(&cfg, &style_comp); - let view = TableView::Expanded { - limit, - flatten, - flatten_separator, - }; - - build_table_batch(batch, view, opts, self.head) + let opts = self.create_table_opts(); + build_table_batch(batch, self.table_config.view.clone(), opts, self.head) } - fn build_collapsed(&mut self, batch: Vec) -> StringResult { - if batch.is_empty() { - return Ok(None); - } - - let cfg = { - let state = &self.engine_state; - let stack = &self.stack; - stack.get_config(state) - }; - let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack); - let opts = self.create_table_opts(&cfg, &style_comp); - - build_table_batch(batch, TableView::Collapsed, opts, self.head) - } - - fn build_general(&mut self, batch: Vec) -> StringResult { - let cfg = { - let state = &self.engine_state; - let stack = &self.stack; - stack.get_config(state) - }; - let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack); - let opts = self.create_table_opts(&cfg, &style_comp); - - build_table_batch(batch, TableView::General, opts, self.head) - } - - fn create_table_opts<'a>( - &'a self, - cfg: &'a Config, - style_comp: &'a StyleComputer<'a>, - ) -> TableOpts<'a> { - TableOpts::new( - cfg, - style_comp, - self.engine_state.signals(), + fn create_table_opts(&self) -> TableOpts<'_> { + create_table_opts( + &self.engine_state, + &self.stack, + &self.config, + &self.table_config, self.head, - self.cfg.term_width, - cfg.table.padding, - self.cfg.theme, - self.cfg.index.unwrap_or(0) + self.row_offset, - self.cfg.index.is_none(), + self.row_offset, ) } - - fn build_table(&mut self, batch: Vec) -> Result, ShellError> { - match &self.cfg.table_view { - TableView::General => self.build_general(batch), - TableView::Collapsed => self.build_collapsed(batch), - TableView::Expanded { - limit, - flatten, - flatten_separator, - } => self.build_extended(batch, *limit, *flatten, flatten_separator.clone()), - } - } } impl Iterator for PagingTableCreator { - type Item = Result, ShellError>; + type Item = ShellResult>; fn next(&mut self) -> Option { let batch; let end; - match self.cfg.abbreviation { + match self.table_config.abbreviation { Some(abbr) => { (batch, _, end) = stream_collect_abbriviated(&mut self.stream, abbr, self.engine_state.signals()); @@ -882,7 +855,7 @@ impl Iterator for PagingTableCreator { self.elements_displayed = 1; let result = create_empty_placeholder( "list", - self.cfg.term_width, + self.table_config.width, &self.engine_state, &self.stack, ); @@ -896,16 +869,11 @@ impl Iterator for PagingTableCreator { self.row_offset += batch_size; - let config = { - let state = &self.engine_state; - let stack = &self.stack; - stack.get_config(state) - }; convert_table_to_output( table, - &config, + &self.config, self.engine_state.signals(), - self.cfg.term_width, + self.table_config.width, ) } } @@ -1050,17 +1018,6 @@ fn render_path_name( Some(Value::string(val, span)) } -#[derive(Debug, Clone)] -enum TableView { - General, - Collapsed, - Expanded { - limit: Option, - flatten: bool, - flatten_separator: Option, - }, -} - fn maybe_strip_color(output: String, config: &Config) -> String { // the terminal is for when people do ls from vim, there should be no coloring there if !config.use_ansi_coloring || !std::io::stdout().is_terminal() { @@ -1098,11 +1055,11 @@ fn create_empty_placeholder( } fn convert_table_to_output( - table: Result, ShellError>, + table: ShellResult>, config: &Config, signals: &Signals, term_width: usize, -) -> Option, ShellError>> { +) -> Option>> { match table { Ok(Some(table)) => { let table = maybe_strip_color(table, config); @@ -1148,3 +1105,31 @@ fn supported_table_modes() -> Vec { Value::test_string("basic_compact"), ] } + +fn create_table_opts<'a>( + engine_state: &'a EngineState, + stack: &'a Stack, + cfg: &'a Config, + table_cfg: &'a TableConfig, + span: Span, + offset: usize, +) -> TableOpts<'a> { + let comp = StyleComputer::from_config(engine_state, stack); + let signals = engine_state.signals(); + let offset = table_cfg.index.unwrap_or(0) + offset; + let index = table_cfg.index.is_none(); + let width = table_cfg.width; + let theme = table_cfg.theme; + + TableOpts::new(cfg, comp, signals, span, width, theme, offset, index) +} + +fn get_table_width(width: Option) -> usize { + if let Some(col) = width { + col as usize + } else if let Some((Width(w), Height(_))) = terminal_size::terminal_size() { + w as usize + } else { + DEFAULT_TABLE_WIDTH + } +} diff --git a/crates/nu-explore/src/commands/expand.rs b/crates/nu-explore/src/commands/expand.rs index 8b03b71dc1..a62dc3538e 100644 --- a/crates/nu-explore/src/commands/expand.rs +++ b/crates/nu-explore/src/commands/expand.rs @@ -69,12 +69,9 @@ fn convert_value_to_string( } else { let config = engine_state.get_config(); let style_computer = StyleComputer::from_config(engine_state, stack); + let table = + nu_common::try_build_table(value, engine_state.signals(), config, style_computer); - Ok(nu_common::try_build_table( - engine_state.signals(), - config, - &style_computer, - value, - )) + Ok(table) } } diff --git a/crates/nu-explore/src/nu_common/table.rs b/crates/nu-explore/src/nu_common/table.rs index 1757efc148..66ea63d7ad 100644 --- a/crates/nu-explore/src/nu_common/table.rs +++ b/crates/nu-explore/src/nu_common/table.rs @@ -1,80 +1,57 @@ use crate::nu_common::NuConfig; use nu_color_config::StyleComputer; -use nu_protocol::{Record, Signals, Span, Value}; +use nu_protocol::{Record, Signals, Value}; use nu_table::{ common::{nu_value_to_string, nu_value_to_string_clean}, ExpandedTable, TableOpts, }; pub fn try_build_table( + value: Value, signals: &Signals, config: &NuConfig, - style_computer: &StyleComputer, - value: Value, + style_computer: StyleComputer, ) -> String { let span = value.span(); - match value { - Value::List { vals, .. } => try_build_list(vals, signals, config, span, style_computer), - Value::Record { val, .. } => try_build_map(&val, span, style_computer, signals, config), - val if matches!(val, Value::String { .. }) => { - nu_value_to_string_clean(&val, config, style_computer).0 - } - val => nu_value_to_string(&val, config, style_computer).0, - } -} - -fn try_build_map( - record: &Record, - span: Span, - style_computer: &StyleComputer, - signals: &Signals, - config: &NuConfig, -) -> String { let opts = TableOpts::new( config, style_computer, signals, - Span::unknown(), + span, usize::MAX, - config.table.padding, config.table.mode, 0, false, ); - let result = ExpandedTable::new(None, false, String::new()).build_map(record, opts); + match value { + Value::List { vals, .. } => try_build_list(vals, opts), + Value::Record { val, .. } => try_build_map(&val, opts), + val if matches!(val, Value::String { .. }) => { + nu_value_to_string_clean(&val, config, &opts.style_computer).0 + } + val => nu_value_to_string(&val, config, &opts.style_computer).0, + } +} + +fn try_build_map(record: &Record, opts: TableOpts<'_>) -> String { + let result = ExpandedTable::new(None, false, String::new()).build_map(record, opts.clone()); match result { Ok(Some(result)) => result, - Ok(None) | Err(_) => { - nu_value_to_string(&Value::record(record.clone(), span), config, style_computer).0 + _ => { + let value = Value::record(record.clone(), opts.span); + nu_value_to_string(&value, opts.config, &opts.style_computer).0 } } } -fn try_build_list( - vals: Vec, - signals: &Signals, - config: &NuConfig, - span: Span, - style_computer: &StyleComputer, -) -> String { - let opts = TableOpts::new( - config, - style_computer, - signals, - Span::unknown(), - usize::MAX, - config.table.padding, - config.table.mode, - 0, - false, - ); - - let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts); +fn try_build_list(vals: Vec, opts: TableOpts<'_>) -> String { + let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts.clone()); match result { Ok(Some(out)) => out, - Ok(None) | Err(_) => { + _ => { // it means that the list is empty - nu_value_to_string(&Value::list(vals, span), config, style_computer).0 + let value = Value::list(vals, opts.span); + nu_value_to_string(&value, opts.config, &opts.style_computer).0 } } } diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index e8d64451b5..32687651aa 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -95,6 +95,11 @@ impl NuTable { } } + 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); diff --git a/crates/nu-table/src/types/collapse.rs b/crates/nu-table/src/types/collapse.rs index 9674ea2264..9bc01742a0 100644 --- a/crates/nu-table/src/types/collapse.rs +++ b/crates/nu-table/src/types/collapse.rs @@ -17,7 +17,7 @@ impl CollapsedTable { } fn collapsed_table(mut value: Value, opts: TableOpts<'_>) -> StringResult { - colorize_value(&mut value, opts.config, opts.style_computer); + colorize_value(&mut value, opts.config, &opts.style_computer); let mut table = UnstructuredTable::new(value, opts.config); @@ -27,7 +27,7 @@ fn collapsed_table(mut value: Value, opts: TableOpts<'_>) -> StringResult { return Ok(None); } - let table = table.draw(&theme, opts.config.table.padding, opts.style_computer); + let table = table.draw(&theme, opts.config.table.padding, &opts.style_computer); Ok(Some(table)) } diff --git a/crates/nu-table/src/types/expanded.rs b/crates/nu-table/src/types/expanded.rs index 585ce3809b..9e78de5067 100644 --- a/crates/nu-table/src/types/expanded.rs +++ b/crates/nu-table/src/types/expanded.rs @@ -45,16 +45,14 @@ impl ExpandedTable { } pub fn build_list(self, vals: &[Value], opts: TableOpts<'_>) -> StringResult { - let cfg = Cfg { - opts: opts.clone(), - format: self, - }; - let out = match expand_list(vals, cfg)? { + let cfg = Cfg { opts, format: self }; + let output = expand_list(vals, cfg.clone())?; + let output = match output { Some(out) => out, None => return Ok(None), }; - maybe_expand_table(out, opts.width, &opts) + maybe_expand_table(output, cfg.opts.width, &cfg.opts) } } @@ -181,7 +179,7 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { cfg.opts.signals.check(cfg.opts.span)?; check_value(item)?; - let inner_cfg = update_config(&cfg, available_width); + let inner_cfg = cfg_expand_reset_table(cfg.clone(), available_width); let mut cell = expand_entry(item, inner_cfg); let value_width = string_width(&cell.text); @@ -202,8 +200,11 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { } let mut table = NuTable::from(data); - table.set_indent(cfg.opts.indent.left, cfg.opts.indent.right); - table.set_index_style(get_index_style(cfg.opts.style_computer)); + table.set_indent( + cfg.opts.config.table.padding.left, + cfg.opts.config.table.padding.right, + ); + table.set_index_style(get_index_style(&cfg.opts.style_computer)); set_data_styles(&mut table, data_styles); return Ok(Some(TableOutput::new(table, false, with_index, rows_count))); @@ -267,7 +268,7 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { cfg.opts.signals.check(cfg.opts.span)?; check_value(item)?; - let inner_cfg = update_config(&cfg, available); + let inner_cfg = cfg_expand_reset_table(cfg.clone(), available); let mut cell = expand_entry_with_header(item, &header, inner_cfg); let mut value_width = string_width(&cell.text); @@ -360,9 +361,12 @@ fn expand_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.left, cfg.opts.indent.right); + 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.config.table.padding.left, + cfg.opts.config.table.padding.right, + ); set_data_styles(&mut table, data_styles); Ok(Some(TableOutput::new(table, true, with_index, rows_count))) @@ -417,7 +421,10 @@ 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.left, cfg.opts.indent.right); + table.set_indent( + cfg.opts.config.table.padding.left, + cfg.opts.config.table.padding.right, + ); let out = TableOutput::new(table, false, true, count_rows); @@ -427,8 +434,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult { // the flag is used as an optimization to not do `value.lines().count()` search. fn expand_value(value: &Value, width: usize, cfg: &Cfg<'_>) -> CellResult { - let is_limited = matches!(cfg.format.expand_limit, Some(0)); - if is_limited { + if is_limit_reached(cfg) { let value = value_to_string_clean(value, cfg); return Ok(Some(CellOutput::clean(value, 1, false))); } @@ -436,7 +442,7 @@ fn expand_value(value: &Value, width: usize, cfg: &Cfg<'_>) -> CellResult { let span = value.span(); match value { Value::List { vals, .. } => { - let inner_cfg = update_config(&dive_options(cfg, span), width); + let inner_cfg = cfg_expand_reset_table(cfg_expand_next_level(cfg.clone(), span), width); let table = expand_list(vals, inner_cfg)?; match table { @@ -462,7 +468,7 @@ fn expand_value(value: &Value, width: usize, cfg: &Cfg<'_>) -> CellResult { return Ok(Some(CellOutput::text(value))); } - let inner_cfg = update_config(&dive_options(cfg, span), width); + let inner_cfg = cfg_expand_reset_table(cfg_expand_next_level(cfg.clone(), span), width); let result = expanded_table_kv(record, inner_cfg)?; match result { Some(result) => Ok(Some(CellOutput::clean(result.text, result.size, true))), @@ -480,23 +486,22 @@ fn expand_value(value: &Value, width: usize, cfg: &Cfg<'_>) -> CellResult { } fn get_key_style(cfg: &Cfg<'_>) -> TextStyle { - get_header_style(cfg.opts.style_computer).alignment(Alignment::Left) + get_header_style(&cfg.opts.style_computer).alignment(Alignment::Left) } fn expand_entry_with_header(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOutput { match item { Value::Record { val, .. } => match val.get(header) { Some(val) => expand_entry(val, cfg), - None => CellOutput::styled(error_sign(cfg.opts.style_computer)), + None => CellOutput::styled(error_sign(&cfg.opts.style_computer)), }, _ => expand_entry(item, cfg), } } fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput { - let is_limit_reached = matches!(cfg.format.expand_limit, Some(0)); - if is_limit_reached { - let value = nu_value_to_string_clean(item, cfg.opts.config, cfg.opts.style_computer); + if is_limit_reached(&cfg) { + let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer); return CellOutput::styled(value); } @@ -504,18 +509,18 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput { match &item { Value::Record { val: record, .. } => { if record.is_empty() { - let value = nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer); + let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer); return CellOutput::styled(value); } // we verify what is the structure of a Record cause it might represent - let inner_cfg = dive_options(&cfg, span); + let inner_cfg = cfg_expand_next_level(cfg.clone(), span); let table = expanded_table_kv(record, inner_cfg); match table { Ok(Some(table)) => table, _ => { - let value = nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer); + let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer); CellOutput::styled(value) } } @@ -525,19 +530,19 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput { let value = list_to_string( vals, cfg.opts.config, - cfg.opts.style_computer, + &cfg.opts.style_computer, &cfg.format.flatten_sep, ); return CellOutput::text(value); } - let inner_cfg = dive_options(&cfg, span); + let inner_cfg = cfg_expand_next_level(cfg.clone(), span); let table = expand_list(vals, inner_cfg); let out = match table { Ok(Some(out)) => out, _ => { - let value = nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer); + let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer); return CellOutput::styled(value); } }; @@ -547,18 +552,22 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput { match table { Some(table) => CellOutput::clean(table, out.count_rows, false), None => { - let value = nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer); + let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer); CellOutput::styled(value) } } } _ => { - let value = nu_value_to_string_clean(item, cfg.opts.config, cfg.opts.style_computer); + let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer); CellOutput::styled(value) } } } +fn is_limit_reached(cfg: &Cfg<'_>) -> bool { + matches!(cfg.format.expand_limit, Some(0)) +} + fn is_simple_list(vals: &[Value]) -> bool { vals.iter() .all(|v| !matches!(v, Value::Record { .. } | Value::List { .. })) @@ -583,19 +592,9 @@ fn list_to_string( buf } -fn dive_options<'b>(cfg: &Cfg<'b>, span: Span) -> Cfg<'b> { - let mut cfg = cfg.clone(); - cfg.opts.span = span; - if let Some(deep) = cfg.format.expand_limit.as_mut() { - *deep -= 1 - } - - cfg -} - fn maybe_expand_table(out: TableOutput, term_width: usize, opts: &TableOpts<'_>) -> StringResult { let mut table_config = - create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode); + create_nu_table_config(opts.config, &opts.style_computer, &out, false, opts.mode); let total_width = out.table.total_width(&table_config); if total_width < term_width { const EXPAND_THRESHOLD: f32 = 0.80; @@ -606,7 +605,9 @@ fn maybe_expand_table(out: TableOutput, term_width: usize, opts: &TableOpts<'_>) } } - Ok(out.table.draw(table_config, term_width)) + let table = out.table.draw(table_config, term_width); + + Ok(table) } fn set_data_styles(table: &mut NuTable, styles: HashMap) { @@ -618,7 +619,7 @@ fn set_data_styles(table: &mut NuTable, styles: HashMap) { fn create_table_cfg(cfg: &Cfg<'_>, out: &TableOutput) -> crate::NuTableConfig { create_nu_table_config( cfg.opts.config, - cfg.opts.style_computer, + &cfg.opts.style_computer, out, false, cfg.opts.mode, @@ -626,11 +627,11 @@ fn create_table_cfg(cfg: &Cfg<'_>, out: &TableOutput) -> crate::NuTableConfig { } fn value_to_string(value: &Value, cfg: &Cfg<'_>) -> String { - nu_value_to_string(value, cfg.opts.config, cfg.opts.style_computer).0 + nu_value_to_string(value, cfg.opts.config, &cfg.opts.style_computer).0 } fn value_to_string_clean(value: &Value, cfg: &Cfg<'_>) -> String { - nu_value_to_string_clean(value, cfg.opts.config, cfg.opts.style_computer).0 + nu_value_to_string_clean(value, cfg.opts.config, &cfg.opts.style_computer).0 } fn value_to_wrapped_string(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> String { @@ -638,13 +639,21 @@ fn value_to_wrapped_string(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> } fn value_to_wrapped_string_clean(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> String { - let text = nu_value_to_string_colored(value, cfg.opts.config, cfg.opts.style_computer); + let text = nu_value_to_string_colored(value, cfg.opts.config, &cfg.opts.style_computer); wrap_text(&text, value_width, cfg.opts.config) } -fn update_config<'a>(cfg: &Cfg<'a>, width: usize) -> Cfg<'a> { - let mut new = cfg.clone(); - new.opts.width = width; - new.opts.index_offset = 0; - new +fn cfg_expand_next_level(mut cfg: Cfg<'_>, span: Span) -> Cfg<'_> { + cfg.opts.span = span; + if let Some(deep) = cfg.format.expand_limit.as_mut() { + *deep -= 1 + } + + cfg +} + +fn cfg_expand_reset_table(mut cfg: Cfg<'_>, width: usize) -> Cfg<'_> { + cfg.opts.width = width; + cfg.opts.index_offset = 0; + cfg } diff --git a/crates/nu-table/src/types/general.rs b/crates/nu-table/src/types/general.rs index 29f6d0467d..9571de9406 100644 --- a/crates/nu-table/src/types/general.rs +++ b/crates/nu-table/src/types/general.rs @@ -35,9 +35,9 @@ fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result, Sh opts.config.table.padding.right, ); - colorize_space(out.table.get_records_mut(), opts.style_computer); + 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 config = create_nu_table_config(opts.config, &opts.style_computer, &out, false, opts.mode); let table = out.table.draw(config, opts.width); Ok(table) @@ -51,7 +51,7 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult { let key = NuRecordsValue::new(column.to_string()); - let value = nu_value_to_string_colored(value, opts.config, opts.style_computer); + let value = nu_value_to_string_colored(value, opts.config, &opts.style_computer); let value = NuRecordsValue::new(value); row.push(key); @@ -67,7 +67,7 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult { let out = TableOutput::from_table(table, false, true); let table_config = - create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode); + create_nu_table_config(opts.config, &opts.style_computer, &out, false, opts.mode); let table = out.table.draw(table_config, opts.width); Ok(table) @@ -100,28 +100,23 @@ fn create_table_with_header( 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 - .into_iter() - .filter(|header| header != INDEX_COLUMN_NAME) - .collect(); + let headers = collect_headers(headers, false); 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.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()); + table.set_row(0, headers.clone()); for (row, item) in input.iter().enumerate() { opts.signals.check(opts.span)?; check_value(item)?; for (col, header) in headers.iter().enumerate() { - let (text, style) = get_string_value_with_header(item, header, opts); + let (text, style) = get_string_value_with_header(item, header.as_ref(), opts); let pos = (row + 1, col); table.insert(pos, text); @@ -138,23 +133,16 @@ fn create_table_with_header_and_index( row_offset: usize, 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 mut headers: Vec<_> = headers - .into_iter() - .filter(|header| header != INDEX_COLUMN_NAME) - .collect(); - - headers.insert(0, "#".into()); + let headers = collect_headers(headers, true); 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.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()); + table.set_row(0, headers.clone()); for (row, item) in input.iter().enumerate() { opts.signals.check(opts.span)?; @@ -164,7 +152,7 @@ fn create_table_with_header_and_index( 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 (text, style) = get_string_value_with_header(item, header.as_ref(), opts); let pos = (row + 1, col); table.insert(pos, text); @@ -180,7 +168,7 @@ fn create_table_with_no_header( opts: &TableOpts<'_>, ) -> Result, ShellError> { let mut table = NuTable::new(input.len(), 1); - table.set_index_style(get_index_style(opts.style_computer)); + table.set_index_style(get_index_style(&opts.style_computer)); for (row, item) in input.iter().enumerate() { opts.signals.check(opts.span)?; @@ -202,7 +190,7 @@ fn create_table_with_no_header_and_index( opts: &TableOpts<'_>, ) -> Result, ShellError> { let mut table = NuTable::new(input.len(), 1 + 1); - table.set_index_style(get_index_style(opts.style_computer)); + table.set_index_style(get_index_style(&opts.style_computer)); for (row, item) in input.iter().enumerate() { opts.signals.check(opts.span)?; @@ -225,14 +213,14 @@ fn get_string_value_with_header(item: &Value, header: &str, opts: &TableOpts) -> match item { Value::Record { val, .. } => match val.get(header) { Some(value) => get_string_value(value, opts), - None => get_empty_style(opts.style_computer), + None => get_empty_style(&opts.style_computer), }, value => get_string_value(value, opts), } } fn get_string_value(item: &Value, opts: &TableOpts) -> NuText { - let (mut text, style) = get_value_style(item, opts.config, opts.style_computer); + let (mut text, style) = get_value_style(item, opts.config, &opts.style_computer); let is_string = matches!(item, Value::String { .. }); if is_string { @@ -251,3 +239,24 @@ fn get_table_row_index(item: &Value, config: &Config, row: usize, offset: usize) _ => (row + offset).to_string(), } } + +fn collect_headers(headers: Vec, index: bool) -> Vec { + // The header with the INDEX is removed from the table headers since + // it is added to the natural table index + + if !index { + headers + .into_iter() + .filter(|header| header != INDEX_COLUMN_NAME) + .map(NuRecordsValue::new) + .collect() + } else { + let mut v = Vec::with_capacity(headers.len() + 1); + v.insert(0, NuRecordsValue::new("#".into())); + for text in headers { + v.push(NuRecordsValue::new(text)); + } + + v + } +} diff --git a/crates/nu-table/src/types/mod.rs b/crates/nu-table/src/types/mod.rs index d1e5c1a500..227c0c0db2 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, TableIndent, TableIndexMode, TableMode}; +use nu_protocol::{Config, Signals, Span, TableIndexMode, TableMode}; use crate::{common::INDEX_COLUMN_NAME, NuTable}; @@ -37,36 +37,35 @@ impl TableOutput { #[derive(Debug, Clone)] pub struct TableOpts<'a> { - signals: &'a Signals, - config: &'a Config, - style_computer: &'a StyleComputer<'a>, - span: Span, - width: usize, - indent: TableIndent, - mode: TableMode, - index_offset: usize, - index_remove: bool, + pub signals: &'a Signals, + pub config: &'a Config, + pub style_computer: std::rc::Rc>, + pub span: Span, + pub width: usize, + pub mode: TableMode, + pub index_offset: usize, + pub index_remove: bool, } impl<'a> TableOpts<'a> { #[allow(clippy::too_many_arguments)] pub fn new( config: &'a Config, - style_computer: &'a StyleComputer<'a>, + style_computer: StyleComputer<'a>, signals: &'a Signals, span: Span, width: usize, - indent: TableIndent, mode: TableMode, index_offset: usize, index_remove: bool, ) -> Self { + let style_computer = std::rc::Rc::new(style_computer); + Self { signals, config, style_computer, span, - indent, width, mode, index_offset,