This commit is contained in:
Maxim Zhiburt 2024-11-24 01:58:52 +03:00
parent a5e1ff9450
commit 425c3d8380
4 changed files with 109 additions and 73 deletions

View File

@ -2867,13 +2867,52 @@ fn table_index_arg() {
#[test] #[test]
fn table_expand_index_arg() { fn table_expand_index_arg() {
let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i false"); let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i false");
assert_eq!(actual.out, "+---+-------+| a | b |+---+-------+| 1 | 2 |+---+-------+| 2 | +---+ || | | 4 | || | +---+ || | | 4 | || | +---+ |+---+-------+"); assert_eq!(
actual.out,
"+---+-------+\
| a | b |\
+---+-------+\
| 1 | 2 |\
+---+-------+\
| 2 | +---+ |\
| | | 4 | |\
| | +---+ |\
| | | 4 | |\
| | +---+ |\
+---+-------+"
);
let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i true"); let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i true");
assert_eq!(actual.out, "+---+---+-----------+| # | a | b |+---+---+-----------+| 0 | 1 | 2 |+---+---+-----------+| 1 | 2 | +---+---+ || | | | 0 | 4 | || | | +---+---+ || | | | 1 | 4 | || | | +---+---+ |+---+---+-----------+"); assert_eq!(
actual.out,
"+---+---+-----------+\
| # | a | b |\
+---+---+-----------+\
| 0 | 1 | 2 |\
+---+---+-----------+\
| 1 | 2 | +---+---+ |\
| | | | 0 | 4 | |\
| | | +---+---+ |\
| | | | 1 | 4 | |\
| | | +---+---+ |\
+---+---+-----------+"
);
let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i 10"); let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i 10");
assert_eq!(actual.out, "+----+---+-----------+| # | a | b |+----+---+-----------+| 10 | 1 | 2 |+----+---+-----------+| 11 | 2 | +---+---+ || | | | 0 | 4 | || | | +---+---+ || | | | 1 | 4 | || | | +---+---+ |+----+---+-----------+"); assert_eq!(
actual.out,
"+----+---+-----------+\
| # | a | b |\
+----+---+-----------+\
| 10 | 1 | 2 |\
+----+---+-----------+\
| 11 | 2 | +---+---+ |\
| | | | 0 | 4 | |\
| | | +---+---+ |\
| | | | 1 | 4 | |\
| | | +---+---+ |\
+----+---+-----------+"
);
} }
#[test] #[test]

View File

@ -631,8 +631,9 @@ fn load_theme(
let mut theme = theme.as_base().clone(); let mut theme = theme.as_base().clone();
if !structure.with_header { if !structure.with_header {
theme.set_horizontal_lines(Default::default()); let borders = *theme.get_borders();
theme.remove_horizontal_lines(); theme.remove_horizontal_lines();
theme.set_borders(borders);
} else if structure.with_footer { } else if structure.with_footer {
theme_copy_horizontal_line(&mut theme, 1, table.count_rows() - 1); theme_copy_horizontal_line(&mut theme, 1, table.count_rows() - 1);
} }

View File

@ -35,7 +35,7 @@ impl ExpandedTable {
pub fn build_value(self, item: &Value, opts: TableOpts<'_>) -> NuText { pub fn build_value(self, item: &Value, opts: TableOpts<'_>) -> NuText {
let cfg = Cfg { opts, format: self }; let cfg = Cfg { opts, format: self };
let cell = expanded_table_entry2(item, cfg); let cell = expand_entry(item, cfg);
(cell.text, cell.style) (cell.text, cell.style)
} }
@ -49,7 +49,7 @@ impl ExpandedTable {
opts: opts.clone(), opts: opts.clone(),
format: self, format: self,
}; };
let out = match expanded_table_list(vals, cfg)? { let out = match expand_list(vals, cfg)? {
Some(out) => out, Some(out) => out,
None => return Ok(None), None => return Ok(None),
}; };
@ -97,7 +97,7 @@ impl CellOutput {
type CellResult = Result<Option<CellOutput>, ShellError>; type CellResult = Result<Option<CellOutput>, ShellError>;
fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
const PADDING_SPACE: usize = 2; const PADDING_SPACE: usize = 2;
const SPLIT_LINE_SPACE: usize = 1; const SPLIT_LINE_SPACE: usize = 1;
const ADDITIONAL_CELL_SPACE: usize = PADDING_SPACE + SPLIT_LINE_SPACE; const ADDITIONAL_CELL_SPACE: usize = PADDING_SPACE + SPLIT_LINE_SPACE;
@ -181,8 +181,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
cfg.opts.signals.check(cfg.opts.span)?; cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?; check_value(item)?;
let inner_cfg = update_config(cfg.clone(), available_width); let inner_cfg = update_config(&cfg, available_width);
let mut cell = expanded_table_entry2(item, inner_cfg); let mut cell = expand_entry(item, inner_cfg);
let value_width = string_width(&cell.text); let value_width = string_width(&cell.text);
if value_width > available_width { if value_width > available_width {
@ -267,8 +267,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
cfg.opts.signals.check(cfg.opts.span)?; cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?; check_value(item)?;
let inner_cfg = update_config(cfg.clone(), available); let inner_cfg = update_config(&cfg, available);
let mut cell = expanded_table_entry(item, header.as_str(), inner_cfg); let mut cell = expand_entry_with_header(item, &header, inner_cfg);
let mut value_width = string_width(&cell.text); let mut value_width = string_width(&cell.text);
if value_width > available { if value_width > available {
@ -426,7 +426,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
} }
// the flag is used as an optimization to not do `value.lines().count()` search. // the flag is used as an optimization to not do `value.lines().count()` search.
fn expand_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellResult { fn expand_value(value: &Value, width: usize, cfg: &Cfg<'_>) -> CellResult {
let is_limited = matches!(cfg.format.expand_limit, Some(0)); let is_limited = matches!(cfg.format.expand_limit, Some(0));
if is_limited { if is_limited {
let value = value_to_string_clean(value, cfg); let value = value_to_string_clean(value, cfg);
@ -436,13 +436,13 @@ fn expand_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellResult
let span = value.span(); let span = value.span();
match value { match value {
Value::List { vals, .. } => { Value::List { vals, .. } => {
let inner_cfg = update_config(dive_options(cfg, span), value_width); let inner_cfg = update_config(&dive_options(cfg, span), width);
let table = expanded_table_list(vals, inner_cfg)?; let table = expand_list(vals, inner_cfg)?;
match table { match table {
Some(out) => { Some(out) => {
let cfg = create_table_cfg(cfg, &out); let cfg = create_table_cfg(cfg, &out);
let value = out.table.draw(cfg, value_width); let value = out.table.draw(cfg, width);
match value { match value {
Some(value) => Ok(Some(CellOutput::clean(value, out.count_rows, true))), Some(value) => Ok(Some(CellOutput::clean(value, out.count_rows, true))),
None => Ok(None), None => Ok(None),
@ -450,7 +450,7 @@ fn expand_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellResult
} }
None => { None => {
// it means that the list is empty // it means that the list is empty
let value = value_to_wrapped_string(value, cfg, value_width); let value = value_to_wrapped_string(value, cfg, width);
Ok(Some(CellOutput::text(value))) Ok(Some(CellOutput::text(value)))
} }
} }
@ -458,22 +458,22 @@ fn expand_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellResult
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
if record.is_empty() { if record.is_empty() {
// Like list case return styled string instead of empty value // Like list case return styled string instead of empty value
let value = value_to_wrapped_string(value, cfg, value_width); let value = value_to_wrapped_string(value, cfg, width);
return Ok(Some(CellOutput::text(value))); return Ok(Some(CellOutput::text(value)));
} }
let inner_cfg = update_config(dive_options(cfg, span), value_width); let inner_cfg = update_config(&dive_options(cfg, span), width);
let result = expanded_table_kv(record, inner_cfg)?; let result = expanded_table_kv(record, inner_cfg)?;
match result { match result {
Some(result) => Ok(Some(CellOutput::clean(result.text, result.size, true))), Some(result) => Ok(Some(CellOutput::clean(result.text, result.size, true))),
None => { None => {
let value = value_to_wrapped_string(value, cfg, value_width); let value = value_to_wrapped_string(value, cfg, width);
Ok(Some(CellOutput::text(value))) Ok(Some(CellOutput::text(value)))
} }
} }
} }
_ => { _ => {
let value = value_to_wrapped_string_clean(value, cfg, value_width); let value = value_to_wrapped_string_clean(value, cfg, width);
Ok(Some(CellOutput::text(value))) Ok(Some(CellOutput::text(value)))
} }
} }
@ -483,35 +483,29 @@ 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 expanded_table_entry(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOutput { fn expand_entry_with_header(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOutput {
match item { match item {
Value::Record { val, .. } => match val.get(header) { Value::Record { val, .. } => match val.get(header) {
Some(val) => expanded_table_entry2(val, cfg), Some(val) => expand_entry(val, cfg),
None => CellOutput::styled(error_sign(cfg.opts.style_computer)), None => CellOutput::styled(error_sign(cfg.opts.style_computer)),
}, },
_ => expanded_table_entry2(item, cfg), _ => expand_entry(item, cfg),
} }
} }
fn expanded_table_entry2(item: &Value, cfg: Cfg<'_>) -> CellOutput { fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
let is_limit_reached = matches!(cfg.format.expand_limit, Some(0)); let is_limit_reached = matches!(cfg.format.expand_limit, Some(0));
if is_limit_reached { if is_limit_reached {
return CellOutput::styled(nu_value_to_string_clean( let value = nu_value_to_string_clean(item, cfg.opts.config, cfg.opts.style_computer);
item, return CellOutput::styled(value);
cfg.opts.config,
cfg.opts.style_computer,
));
} }
let span = item.span(); let span = item.span();
match &item { match &item {
Value::Record { val: record, .. } => { Value::Record { val: record, .. } => {
if record.is_empty() { if record.is_empty() {
return CellOutput::styled(nu_value_to_string( let value = nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer);
item, return CellOutput::styled(value);
cfg.opts.config,
cfg.opts.style_computer,
));
} }
// we verify what is the structure of a Record cause it might represent // we verify what is the structure of a Record cause it might represent
@ -520,34 +514,31 @@ fn expanded_table_entry2(item: &Value, cfg: Cfg<'_>) -> CellOutput {
match table { match table {
Ok(Some(table)) => table, Ok(Some(table)) => table,
_ => CellOutput::styled(nu_value_to_string( _ => {
item, let value = nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer);
cfg.opts.config, CellOutput::styled(value)
cfg.opts.style_computer, }
)),
} }
} }
Value::List { vals, .. } => { Value::List { vals, .. } => {
if cfg.format.flatten && is_simple_list(vals) { if cfg.format.flatten && is_simple_list(vals) {
return CellOutput::styled(value_list_to_string( let value = list_to_string(
vals, vals,
cfg.opts.config, cfg.opts.config,
cfg.opts.style_computer, cfg.opts.style_computer,
&cfg.format.flatten_sep, &cfg.format.flatten_sep,
)); );
return CellOutput::text(value);
} }
let inner_cfg = dive_options(&cfg, span); let inner_cfg = dive_options(&cfg, span);
let table = expanded_table_list(vals, inner_cfg); let table = expand_list(vals, inner_cfg);
let out = match table { let out = match table {
Ok(Some(out)) => out, Ok(Some(out)) => out,
_ => { _ => {
return CellOutput::styled(nu_value_to_string( let value = nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer);
item, return CellOutput::styled(value);
cfg.opts.config,
cfg.opts.style_computer,
))
} }
}; };
@ -555,18 +546,16 @@ fn expanded_table_entry2(item: &Value, cfg: Cfg<'_>) -> CellOutput {
let table = out.table.draw(table_config, usize::MAX); let table = out.table.draw(table_config, usize::MAX);
match table { match table {
Some(table) => CellOutput::clean(table, out.count_rows, false), Some(table) => CellOutput::clean(table, out.count_rows, false),
None => CellOutput::styled(nu_value_to_string( None => {
item, let value = nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer);
cfg.opts.config, CellOutput::styled(value)
cfg.opts.style_computer, }
)),
} }
} }
_ => CellOutput::styled(nu_value_to_string_clean( _ => {
item, let value = nu_value_to_string_clean(item, cfg.opts.config, cfg.opts.style_computer);
cfg.opts.config, CellOutput::styled(value)
cfg.opts.style_computer, }
)),
} }
} }
@ -575,23 +564,23 @@ fn is_simple_list(vals: &[Value]) -> bool {
.all(|v| !matches!(v, Value::Record { .. } | Value::List { .. })) .all(|v| !matches!(v, Value::Record { .. } | Value::List { .. }))
} }
fn value_list_to_string( fn list_to_string(
vals: &[Value], vals: &[Value],
config: &Config, config: &Config,
style_computer: &StyleComputer, style_computer: &StyleComputer,
flatten_sep: &str, sep: &str,
) -> NuText { ) -> String {
let mut buf = String::new(); let mut buf = String::new();
for (i, value) in vals.iter().enumerate() { for (i, value) in vals.iter().enumerate() {
if i > 0 { if i > 0 {
buf.push_str(flatten_sep); buf.push_str(sep);
} }
let text = nu_value_to_string_clean(value, config, style_computer).0; let (text, _) = nu_value_to_string_clean(value, config, style_computer);
buf.push_str(&text); buf.push_str(&text);
} }
(buf, TextStyle::default()) buf
} }
fn dive_options<'b>(cfg: &Cfg<'b>, span: Span) -> Cfg<'b> { fn dive_options<'b>(cfg: &Cfg<'b>, span: Span) -> Cfg<'b> {
@ -653,9 +642,9 @@ fn value_to_wrapped_string_clean(value: &Value, cfg: &Cfg<'_>, value_width: usiz
wrap_text(&text, value_width, cfg.opts.config) wrap_text(&text, value_width, cfg.opts.config)
} }
fn update_config(cfg: Cfg<'_>, width: usize) -> Cfg<'_> { fn update_config<'a>(cfg: &Cfg<'a>, width: usize) -> Cfg<'a> {
let mut inner_cfg = cfg.clone(); let mut new = cfg.clone();
inner_cfg.opts.width = width; new.opts.width = width;
inner_cfg.opts.index_offset = 0; new.opts.index_offset = 0;
inner_cfg new
} }

View File

@ -3,7 +3,7 @@ use nu_protocol::{Config, Record, Span, TableIndent, Value};
use tabled::{ use tabled::{
grid::{ grid::{
ansi::{ANSIBuf, ANSIStr}, ansi::ANSIStr,
config::{AlignmentHorizontal, Borders, CompactMultilineConfig}, config::{AlignmentHorizontal, Borders, CompactMultilineConfig},
dimension::{DimensionPriority, PoolTableDimension}, dimension::{DimensionPriority, PoolTableDimension},
}, },
@ -28,7 +28,7 @@ impl UnstructuredTable {
pub fn truncate(&mut self, theme: &TableTheme, width: usize) -> bool { pub fn truncate(&mut self, theme: &TableTheme, width: usize) -> bool {
let mut available = width; let mut available = width;
let has_vertical = theme.as_base().borders_has_left(); let has_vertical = theme.as_full().borders_has_left();
if has_vertical { if has_vertical {
available = available.saturating_sub(2); available = available.saturating_sub(2);
} }
@ -65,13 +65,21 @@ fn build_table(
let color = color.paint(" ").to_string(); let color = color.paint(" ").to_string();
if let Ok(color) = Color::try_from(color) { if let Ok(color) = Color::try_from(color) {
if !is_color_empty(&color) { if !is_color_empty(&color) {
table.with(SetBorderColor(color_into_ansistr(color))); return build_table_with_border_color(table, color);
} }
} }
table.to_string() table.to_string()
} }
fn build_table_with_border_color(mut table: PoolTable, color: Color) -> String {
// NOTE: We have this function presizely because of color_into_ansistr internals
// color must be alive why we build table
table.with(SetBorderColor(color_into_ansistr(&color)));
table.to_string()
}
fn convert_nu_value_to_table_value(value: Value, config: &Config) -> TableValue { fn convert_nu_value_to_table_value(value: Value, config: &Config) -> TableValue {
match value { match value {
Value::Record { val, .. } => build_vertical_map(val.into_owned(), config), Value::Record { val, .. } => build_vertical_map(val.into_owned(), config),
@ -333,14 +341,13 @@ fn truncate_table_value(
} }
} }
fn color_into_ansistr(color: Color) -> ANSIStr<'static> { fn color_into_ansistr(color: &Color) -> ANSIStr<'static> {
// # SAFETY // # SAFETY
// //
// It's perfectly save to do cause table does not store the reference internally. // It's perfectly save to do cause table does not store the reference internally.
// We just need this unsafe section to cope with some limitations of [`PoolTable`]. // We just need this unsafe section to cope with some limitations of [`PoolTable`].
// Mitigation of this is definitely on a todo list. // Mitigation of this is definitely on a todo list.
let color: ANSIBuf = color.into();
let prefix = color.get_prefix(); let prefix = color.get_prefix();
let suffix = color.get_suffix(); let suffix = color.get_suffix();
let prefix: &'static str = unsafe { std::mem::transmute(prefix) }; let prefix: &'static str = unsafe { std::mem::transmute(prefix) };