More refactorings

This commit is contained in:
Maxim Zhiburt 2024-12-09 02:36:38 +03:00
parent 49318b91f3
commit 25629b41b6
8 changed files with 337 additions and 356 deletions

View file

@ -25,17 +25,10 @@ use std::{
use terminal_size::{Height, Width};
use url::Url;
const STREAM_PAGE_SIZE: usize = 1000;
type ShellResult<T> = Result<T, ShellError>;
fn get_width_param(width_param: Option<i64>) -> 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<PipelineData, ShellError> {
) -> ShellResult<PipelineData> {
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<Example> {
@ -221,74 +212,119 @@ impl Command for Table {
#[derive(Debug, Clone)]
struct TableConfig {
index: Option<usize>,
table_view: TableView,
term_width: usize,
view: TableView,
width: usize,
theme: TableMode,
abbreviation: Option<usize>,
index: Option<usize>,
}
impl TableConfig {
fn new(
table_view: TableView,
term_width: usize,
view: TableView,
width: usize,
theme: TableMode,
abbreviation: Option<usize>,
index: Option<usize>,
) -> Self {
Self {
index,
table_view,
term_width,
abbreviation,
view,
width,
theme,
abbreviation,
index,
}
}
}
#[derive(Debug, Clone)]
enum TableView {
General,
Collapsed,
Expanded {
limit: Option<usize>,
flatten: bool,
flatten_separator: Option<String>,
},
}
struct CLIArgs {
width: Option<i64>,
abbrivation: Option<usize>,
theme: TableMode,
expand: bool,
expand_limit: Option<usize>,
expand_flatten: bool,
expand_flatten_separator: Option<String>,
collapse: bool,
index: Option<usize>,
}
fn parse_table_config(
call: &Call,
state: &EngineState,
stack: &mut Stack,
) -> Result<TableConfig, ShellError> {
let width_param: Option<i64> = call.get_flag(state, stack, "width")?;
let expand: bool = call.has_flag(state, stack, "expand")?;
let expand_limit: Option<usize> = 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<String> = call.get_flag(state, stack, "flatten-separator")?;
let abbrivation: Option<usize> = call
.get_flag(state, stack, "abbreviated")?
.or_else(|| stack.get_config(state).table.abbreviated_row_count);
let table_view = match (expand, collapse) {
) -> ShellResult<TableConfig> {
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<CLIArgs> {
let width: Option<i64> = call.get_flag(state, stack, "width")?;
let expand: bool = call.has_flag(state, stack, "expand")?;
let expand_limit: Option<usize> = call.get_flag(state, stack, "expand-deep")?;
let expand_flatten: bool = call.has_flag(state, stack, "flatten")?;
let expand_flatten_separator: Option<String> =
call.get_flag(state, stack, "flatten-separator")?;
let collapse: bool = call.has_flag(state, stack, "collapse")?;
let abbrivation: Option<usize> = 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<Option<usize>, ShellError> {
) -> ShellResult<Option<usize>> {
let index: Option<Value> = 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<Option<TableMode>, ShellError> {
) -> ShellResult<Option<TableMode>> {
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<Absolute>,
}
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<Self> {
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<Config> {
self.stack.get_config(self.engine_state)
}
}
fn handle_table_command(
mut input: CmdInput<'_>,
cfg: TableConfig,
cwd: nu_path::PathBuf<Absolute>,
) -> Result<PipelineData, ShellError> {
fn handle_table_command(mut input: CmdInput<'_>) -> ShellResult<PipelineData> {
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<PipelineData, ShellError> {
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<PipelineData> {
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<Value>,
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<PipelineMetadata>,
cwd: nu_path::PathBuf<Absolute>,
) -> Result<PipelineData, ShellError> {
) -> ShellResult<PipelineData> {
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<Config>,
}
impl PagingTableCreator {
@ -745,114 +781,51 @@ impl PagingTableCreator {
stream: ListStream,
engine_state: EngineState,
stack: Stack,
cfg: TableConfig,
table_config: TableConfig,
config: std::sync::Arc<Config>,
) -> 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<Value>,
limit: Option<usize>,
flatten: bool,
flatten_separator: Option<String>,
) -> StringResult {
fn build_table(&mut self, batch: Vec<Value>) -> ShellResult<Option<String>> {
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<Value>) -> 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<Value>) -> 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<Value>) -> Result<Option<String>, 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<Vec<u8>, ShellError>;
type Item = ShellResult<Vec<u8>>;
fn next(&mut self) -> Option<Self::Item> {
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<usize>,
flatten: bool,
flatten_separator: Option<String>,
},
}
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<Option<String>, ShellError>,
table: ShellResult<Option<String>>,
config: &Config,
signals: &Signals,
term_width: usize,
) -> Option<Result<Vec<u8>, ShellError>> {
) -> Option<ShellResult<Vec<u8>>> {
match table {
Ok(Some(table)) => {
let table = maybe_strip_color(table, config);
@ -1148,3 +1105,31 @@ fn supported_table_modes() -> Vec<Value> {
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<i64>) -> 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
}
}

View file

@ -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)
}
}

View file

@ -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<Value>,
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<Value>, 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
}
}
}

View file

@ -95,6 +95,11 @@ impl NuTable {
}
}
pub fn set_row(&mut self, index: usize, row: Vec<NuRecordsValue>) {
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);

View file

@ -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))
}

View file

@ -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<Position, TextStyle>) {
@ -618,7 +619,7 @@ fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
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
}

View file

@ -35,9 +35,9 @@ fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, 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<String>,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, 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<Option<NuTable>, 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<Option<NuTable>, 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<Option<NuTable>, 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<String>, index: bool) -> Vec<NuRecordsValue> {
// 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
}
}

View file

@ -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<StyleComputer<'a>>,
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,