forked from extern/nushell
Add an option to move header on borders (#9796)
A patch to play with. Need to make a few tests after all. The question is what shall be done with `table.mode = none`, as it has no borders. ```nu $env.config.table.move_header = true ``` ![image](https://github.com/nushell/nushell/assets/20165848/cdcffa6d-989c-4368-a436-fdf7d3400e31) cc: @fdncred --------- Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
This commit is contained in:
parent
14bf25da14
commit
7162289d77
23
Cargo.lock
generated
23
Cargo.lock
generated
@ -2737,7 +2737,7 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"sqlparser",
|
"sqlparser",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"tabled",
|
"tabled 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"terminal_size 0.2.6",
|
"terminal_size 0.2.6",
|
||||||
"titlecase",
|
"titlecase",
|
||||||
"toml",
|
"toml",
|
||||||
@ -2904,7 +2904,7 @@ dependencies = [
|
|||||||
"nu-engine",
|
"nu-engine",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-utils",
|
"nu-utils",
|
||||||
"tabled",
|
"tabled 0.13.0 (git+https://github.com/zhiburt/tabled/?rev=6c51e3eaa362914a71b868ccb78e7addbebd3657)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3289,9 +3289,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "papergrid"
|
name = "papergrid"
|
||||||
version = "0.9.1"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae7891b22598926e4398790c8fe6447930c72a67d36d983a49d6ce682ce83290"
|
checksum = "a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-str",
|
"ansi-str",
|
||||||
"ansitok",
|
"ansitok",
|
||||||
@ -4947,9 +4947,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tabled"
|
name = "tabled"
|
||||||
version = "0.12.2"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ce69a5028cd9576063ec1f48edb2c75339fd835e6094ef3e05b3a079bf594a6"
|
checksum = "4d38d39c754ae037a9bc3ca1580a985db7371cd14f1229172d1db9093feb6739"
|
||||||
|
dependencies = [
|
||||||
|
"ansi-str",
|
||||||
|
"ansitok",
|
||||||
|
"papergrid",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tabled"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "git+https://github.com/zhiburt/tabled/?rev=6c51e3eaa362914a71b868ccb78e7addbebd3657#6c51e3eaa362914a71b868ccb78e7addbebd3657"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-str",
|
"ansi-str",
|
||||||
"ansitok",
|
"ansitok",
|
||||||
|
@ -84,7 +84,7 @@ serde_yaml = "0.9"
|
|||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
sqlparser = { version = "0.34", features = ["serde"], optional = true }
|
sqlparser = { version = "0.34", features = ["serde"], optional = true }
|
||||||
sysinfo = "0.29"
|
sysinfo = "0.29"
|
||||||
tabled = { version = "0.12.2", features = ["color"], default-features = false }
|
tabled = { version = "0.13.0", features = ["color"], default-features = false }
|
||||||
terminal_size = "0.2"
|
terminal_size = "0.2"
|
||||||
titlecase = "2.0"
|
titlecase = "2.0"
|
||||||
toml = "0.7"
|
toml = "0.7"
|
||||||
|
@ -6,12 +6,13 @@ use nu_engine::{env::get_config, env_to_string, CallExt};
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Config, DataSource, Example, FooterMode, IntoPipelineData, ListStream, PipelineData,
|
Category, Config, DataSource, Example, IntoPipelineData, ListStream, PipelineData,
|
||||||
PipelineMetadata, RawStream, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
PipelineMetadata, RawStream, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
use nu_table::common::create_nu_table_config;
|
||||||
use nu_table::{
|
use nu_table::{
|
||||||
BuildConfig, Cell, CollapsedTable, ExpandedTable, JustTable, NuTable, StringResult,
|
CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, StringResult, TableOpts,
|
||||||
TableConfig, TableOutput, TableTheme,
|
TableOutput,
|
||||||
};
|
};
|
||||||
use nu_utils::get_ls_colors;
|
use nu_utils::get_ls_colors;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -361,8 +362,8 @@ fn handle_record(
|
|||||||
let result = if cols.is_empty() {
|
let result = if cols.is_empty() {
|
||||||
create_empty_placeholder("record", term_width, engine_state, stack)
|
create_empty_placeholder("record", term_width, engine_state, stack)
|
||||||
} else {
|
} else {
|
||||||
let opts = BuildConfig::new(ctrlc, config, style_computer, span, term_width);
|
let opts = TableOpts::new(config, style_computer, ctrlc, span, 0, term_width);
|
||||||
let result = build_table_kv(cols, vals, table_view, opts)?;
|
let result = build_table_kv(cols, vals, table_view, opts, span)?;
|
||||||
match result {
|
match result {
|
||||||
Some(output) => maybe_strip_color(output, config),
|
Some(output) => maybe_strip_color(output, config),
|
||||||
None => report_unsuccessful_output(ctrlc1, term_width),
|
None => report_unsuccessful_output(ctrlc1, term_width),
|
||||||
@ -391,7 +392,8 @@ fn build_table_kv(
|
|||||||
cols: Vec<String>,
|
cols: Vec<String>,
|
||||||
vals: Vec<Value>,
|
vals: Vec<Value>,
|
||||||
table_view: TableView,
|
table_view: TableView,
|
||||||
opts: BuildConfig<'_>,
|
opts: TableOpts<'_>,
|
||||||
|
span: Span,
|
||||||
) -> StringResult {
|
) -> StringResult {
|
||||||
match table_view {
|
match table_view {
|
||||||
TableView::General => JustTable::kv_table(&cols, &vals, opts),
|
TableView::General => JustTable::kv_table(&cols, &vals, opts),
|
||||||
@ -404,7 +406,6 @@ fn build_table_kv(
|
|||||||
ExpandedTable::new(limit, flatten, sep).build_map(&cols, &vals, opts)
|
ExpandedTable::new(limit, flatten, sep).build_map(&cols, &vals, opts)
|
||||||
}
|
}
|
||||||
TableView::Collapsed => {
|
TableView::Collapsed => {
|
||||||
let span = opts.span();
|
|
||||||
let value = Value::Record { cols, vals, span };
|
let value = Value::Record { cols, vals, span };
|
||||||
CollapsedTable::build(value, opts)
|
CollapsedTable::build(value, opts)
|
||||||
}
|
}
|
||||||
@ -414,21 +415,20 @@ fn build_table_kv(
|
|||||||
fn build_table_batch(
|
fn build_table_batch(
|
||||||
vals: Vec<Value>,
|
vals: Vec<Value>,
|
||||||
table_view: TableView,
|
table_view: TableView,
|
||||||
row_offset: usize,
|
opts: TableOpts<'_>,
|
||||||
opts: BuildConfig<'_>,
|
span: Span,
|
||||||
) -> StringResult {
|
) -> StringResult {
|
||||||
match table_view {
|
match table_view {
|
||||||
TableView::General => JustTable::table(&vals, row_offset, opts),
|
TableView::General => JustTable::table(&vals, opts),
|
||||||
TableView::Expanded {
|
TableView::Expanded {
|
||||||
limit,
|
limit,
|
||||||
flatten,
|
flatten,
|
||||||
flatten_separator,
|
flatten_separator,
|
||||||
} => {
|
} => {
|
||||||
let sep = flatten_separator.unwrap_or_else(|| String::from(' '));
|
let sep = flatten_separator.unwrap_or_else(|| String::from(' '));
|
||||||
ExpandedTable::new(limit, flatten, sep).build_list(&vals, opts, row_offset)
|
ExpandedTable::new(limit, flatten, sep).build_list(&vals, opts)
|
||||||
}
|
}
|
||||||
TableView::Collapsed => {
|
TableView::Collapsed => {
|
||||||
let span = opts.span();
|
|
||||||
let value = Value::List { vals, span };
|
let value = Value::List { vals, span };
|
||||||
CollapsedTable::build(value, opts)
|
CollapsedTable::build(value, opts)
|
||||||
}
|
}
|
||||||
@ -647,20 +647,16 @@ impl PagingTableCreator {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = get_config(&self.engine_state, &self.stack);
|
let cfg = get_config(&self.engine_state, &self.stack);
|
||||||
let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack);
|
let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack);
|
||||||
let term_width = get_width_param(self.width_param);
|
let opts = self.create_table_opts(&cfg, &style_comp);
|
||||||
|
|
||||||
let ctrlc = self.ctrlc.clone();
|
|
||||||
let span = self.head;
|
|
||||||
let opts = BuildConfig::new(ctrlc, &config, &style_computer, span, term_width);
|
|
||||||
let view = TableView::Expanded {
|
let view = TableView::Expanded {
|
||||||
limit,
|
limit,
|
||||||
flatten,
|
flatten,
|
||||||
flatten_separator,
|
flatten_separator,
|
||||||
};
|
};
|
||||||
|
|
||||||
build_table_batch(batch, view, self.row_offset, opts)
|
build_table_batch(batch, view, opts, self.head)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_collapsed(&mut self, batch: Vec<Value>) -> StringResult {
|
fn build_collapsed(&mut self, batch: Vec<Value>) -> StringResult {
|
||||||
@ -668,26 +664,34 @@ impl PagingTableCreator {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = get_config(&self.engine_state, &self.stack);
|
let cfg = get_config(&self.engine_state, &self.stack);
|
||||||
let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack);
|
let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack);
|
||||||
let term_width = get_width_param(self.width_param);
|
let opts = self.create_table_opts(&cfg, &style_comp);
|
||||||
let ctrlc = self.ctrlc.clone();
|
|
||||||
let span = self.head;
|
|
||||||
let opts = BuildConfig::new(ctrlc, &config, &style_computer, span, term_width);
|
|
||||||
|
|
||||||
build_table_batch(batch, TableView::Collapsed, self.row_offset, opts)
|
build_table_batch(batch, TableView::Collapsed, opts, self.head)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_general(&mut self, batch: Vec<Value>) -> StringResult {
|
fn build_general(&mut self, batch: Vec<Value>) -> StringResult {
|
||||||
let term_width = get_width_param(self.width_param);
|
let cfg = get_config(&self.engine_state, &self.stack);
|
||||||
let config = get_config(&self.engine_state, &self.stack);
|
let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack);
|
||||||
let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack);
|
let opts = self.create_table_opts(&cfg, &style_comp);
|
||||||
let ctrlc = self.ctrlc.clone();
|
|
||||||
let span = self.head;
|
|
||||||
let row_offset = self.row_offset;
|
|
||||||
let opts = BuildConfig::new(ctrlc, &config, &style_computer, span, term_width);
|
|
||||||
|
|
||||||
build_table_batch(batch, TableView::General, row_offset, opts)
|
build_table_batch(batch, TableView::General, opts, self.head)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_table_opts<'a>(
|
||||||
|
&self,
|
||||||
|
cfg: &'a Config,
|
||||||
|
style_comp: &'a StyleComputer<'a>,
|
||||||
|
) -> TableOpts<'a> {
|
||||||
|
TableOpts::new(
|
||||||
|
cfg,
|
||||||
|
style_comp,
|
||||||
|
self.ctrlc.clone(),
|
||||||
|
self.head,
|
||||||
|
self.row_offset,
|
||||||
|
get_width_param(self.width_param),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -780,22 +784,6 @@ impl Iterator for PagingTableCreator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_theme_from_config(config: &Config) -> TableTheme {
|
|
||||||
match config.table_mode.as_str() {
|
|
||||||
"basic" => TableTheme::basic(),
|
|
||||||
"thin" => TableTheme::thin(),
|
|
||||||
"light" => TableTheme::light(),
|
|
||||||
"compact" => TableTheme::compact(),
|
|
||||||
"with_love" => TableTheme::with_love(),
|
|
||||||
"compact_double" => TableTheme::compact_double(),
|
|
||||||
"rounded" => TableTheme::rounded(),
|
|
||||||
"reinforced" => TableTheme::reinforced(),
|
|
||||||
"heavy" => TableTheme::heavy(),
|
|
||||||
"none" => TableTheme::none(),
|
|
||||||
_ => TableTheme::rounded(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_path_name(
|
fn render_path_name(
|
||||||
path: &str,
|
path: &str,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
@ -859,34 +847,6 @@ fn maybe_strip_color(output: String, config: &Config) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_table_config(config: &Config, comp: &StyleComputer, out: &TableOutput) -> TableConfig {
|
|
||||||
let theme = load_theme_from_config(config);
|
|
||||||
let footer = with_footer(config, out.with_header, out.table.count_rows());
|
|
||||||
let line_style = lookup_separator_color(comp);
|
|
||||||
let trim = config.trim_strategy.clone();
|
|
||||||
|
|
||||||
TableConfig::new()
|
|
||||||
.theme(theme)
|
|
||||||
.with_footer(footer)
|
|
||||||
.with_header(out.with_header)
|
|
||||||
.with_index(out.with_index)
|
|
||||||
.line_style(line_style)
|
|
||||||
.trim(trim)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup_separator_color(style_computer: &StyleComputer) -> nu_ansi_term::Style {
|
|
||||||
style_computer.compute("separator", &Value::nothing(Span::unknown()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_footer(config: &Config, with_header: bool, count_records: usize) -> bool {
|
|
||||||
with_header && need_footer(config, count_records as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn need_footer(config: &Config, count_records: u64) -> bool {
|
|
||||||
matches!(config.footer_mode, FooterMode::RowCount(limit) if count_records > limit)
|
|
||||||
|| matches!(config.footer_mode, FooterMode::Always)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_empty_placeholder(
|
fn create_empty_placeholder(
|
||||||
value_type_name: &str,
|
value_type_name: &str,
|
||||||
termwidth: usize,
|
termwidth: usize,
|
||||||
@ -898,14 +858,14 @@ fn create_empty_placeholder(
|
|||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let cell = Cell::new(format!("empty {}", value_type_name));
|
let cell = NuTableCell::new(format!("empty {}", value_type_name));
|
||||||
let data = vec![vec![cell]];
|
let data = vec![vec![cell]];
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.set_cell_style((0, 0), TextStyle::default().dimmed());
|
table.set_data_style(TextStyle::default().dimmed());
|
||||||
let out = TableOutput::new(table, false, false);
|
let out = TableOutput::new(table, false, false);
|
||||||
|
|
||||||
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
||||||
let config = create_table_config(&config, style_computer, &out);
|
let config = create_nu_table_config(&config, style_computer, &out, false);
|
||||||
|
|
||||||
out.table
|
out.table
|
||||||
.draw(config, termwidth)
|
.draw(config, termwidth)
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_protocol::{Span, Value};
|
use nu_protocol::{Span, Value};
|
||||||
use nu_table::{value_to_clean_styled_string, value_to_styled_string, BuildConfig, ExpandedTable};
|
use nu_table::{
|
||||||
|
common::{nu_value_to_string, nu_value_to_string_clean},
|
||||||
|
ExpandedTable, TableOpts,
|
||||||
|
};
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -18,9 +21,9 @@ pub fn try_build_table(
|
|||||||
try_build_map(cols, vals, span, style_computer, ctrlc, config)
|
try_build_map(cols, vals, span, style_computer, ctrlc, config)
|
||||||
}
|
}
|
||||||
val if matches!(val, Value::String { .. }) => {
|
val if matches!(val, Value::String { .. }) => {
|
||||||
value_to_clean_styled_string(&val, config, style_computer).0
|
nu_value_to_string_clean(&val, config, style_computer).0
|
||||||
}
|
}
|
||||||
val => value_to_styled_string(&val, config, style_computer).0,
|
val => nu_value_to_string(&val, config, style_computer).0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,12 +35,19 @@ fn try_build_map(
|
|||||||
ctrlc: Option<Arc<AtomicBool>>,
|
ctrlc: Option<Arc<AtomicBool>>,
|
||||||
config: &NuConfig,
|
config: &NuConfig,
|
||||||
) -> String {
|
) -> String {
|
||||||
let opts = BuildConfig::new(ctrlc, config, style_computer, Span::unknown(), usize::MAX);
|
let opts = TableOpts::new(
|
||||||
|
config,
|
||||||
|
style_computer,
|
||||||
|
ctrlc,
|
||||||
|
Span::unknown(),
|
||||||
|
0,
|
||||||
|
usize::MAX,
|
||||||
|
);
|
||||||
let result = ExpandedTable::new(None, false, String::new()).build_map(&cols, &vals, opts);
|
let result = ExpandedTable::new(None, false, String::new()).build_map(&cols, &vals, opts);
|
||||||
match result {
|
match result {
|
||||||
Ok(Some(result)) => result,
|
Ok(Some(result)) => result,
|
||||||
Ok(None) | Err(_) => {
|
Ok(None) | Err(_) => {
|
||||||
value_to_styled_string(&Value::Record { cols, vals, span }, config, style_computer).0
|
nu_value_to_string(&Value::Record { cols, vals, span }, config, style_computer).0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,13 +59,20 @@ fn try_build_list(
|
|||||||
span: Span,
|
span: Span,
|
||||||
style_computer: &StyleComputer,
|
style_computer: &StyleComputer,
|
||||||
) -> String {
|
) -> String {
|
||||||
let opts = BuildConfig::new(ctrlc, config, style_computer, Span::unknown(), usize::MAX);
|
let opts = TableOpts::new(
|
||||||
let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts, 0);
|
config,
|
||||||
|
style_computer,
|
||||||
|
ctrlc,
|
||||||
|
Span::unknown(),
|
||||||
|
0,
|
||||||
|
usize::MAX,
|
||||||
|
);
|
||||||
|
let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts);
|
||||||
match result {
|
match result {
|
||||||
Ok(Some(out)) => out,
|
Ok(Some(out)) => out,
|
||||||
Ok(None) | Err(_) => {
|
Ok(None) | Err(_) => {
|
||||||
// it means that the list is empty
|
// it means that the list is empty
|
||||||
value_to_styled_string(&Value::List { vals, span }, config, style_computer).0
|
nu_value_to_string(&Value::List { vals, span }, config, style_computer).0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,7 @@ pub struct Config {
|
|||||||
pub external_completer: Option<usize>,
|
pub external_completer: Option<usize>,
|
||||||
pub filesize_metric: bool,
|
pub filesize_metric: bool,
|
||||||
pub table_mode: String,
|
pub table_mode: String,
|
||||||
|
pub table_move_header: bool,
|
||||||
pub table_show_empty: bool,
|
pub table_show_empty: bool,
|
||||||
pub use_ls_colors: bool,
|
pub use_ls_colors: bool,
|
||||||
pub color_config: HashMap<String, Value>,
|
pub color_config: HashMap<String, Value>,
|
||||||
@ -126,6 +127,7 @@ impl Default for Config {
|
|||||||
table_index_mode: TableIndexMode::Always,
|
table_index_mode: TableIndexMode::Always,
|
||||||
table_show_empty: true,
|
table_show_empty: true,
|
||||||
trim_strategy: TRIM_STRATEGY_DEFAULT,
|
trim_strategy: TRIM_STRATEGY_DEFAULT,
|
||||||
|
table_move_header: false,
|
||||||
|
|
||||||
datetime_normal_format: None,
|
datetime_normal_format: None,
|
||||||
datetime_table_format: None,
|
datetime_table_format: None,
|
||||||
@ -926,6 +928,9 @@ impl Value {
|
|||||||
Value::string(config.table_mode.clone(), span);
|
Value::string(config.table_mode.clone(), span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"header_on_separator" => {
|
||||||
|
try_bool!(cols, vals, index, span, table_move_header)
|
||||||
|
}
|
||||||
"index_mode" => {
|
"index_mode" => {
|
||||||
if let Ok(b) = value.as_string() {
|
if let Ok(b) = value.as_string() {
|
||||||
let val_str = b.to_lowercase();
|
let val_str = b.to_lowercase();
|
||||||
|
@ -16,7 +16,7 @@ nu-utils = { path = "../nu-utils", version = "0.83.2" }
|
|||||||
nu-engine = { path = "../nu-engine", version = "0.83.2" }
|
nu-engine = { path = "../nu-engine", version = "0.83.2" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.83.2" }
|
nu-color-config = { path = "../nu-color-config", version = "0.83.2" }
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
tabled = { version = "0.12.2", features = ["color"], default-features = false }
|
tabled = { git = "https://github.com/zhiburt/tabled/", rev = "6c51e3eaa362914a71b868ccb78e7addbebd3657", features = ["color"], default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# nu-test-support = { path="../nu-test-support", version = "0.83.2" }
|
# nu-test-support = { path="../nu-test-support", version = "0.83.2" }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nu_ansi_term::{Color, Style};
|
use nu_ansi_term::{Color, Style};
|
||||||
use nu_color_config::TextStyle;
|
use nu_color_config::TextStyle;
|
||||||
use nu_table::{NuTable, TableConfig, TableTheme};
|
use nu_table::{NuTable, NuTableConfig, TableTheme};
|
||||||
use tabled::grid::records::vec_records::CellInfo;
|
use tabled::grid::records::vec_records::CellInfo;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -29,8 +29,11 @@ fn main() {
|
|||||||
table.set_data_style(TextStyle::basic_left());
|
table.set_data_style(TextStyle::basic_left());
|
||||||
table.set_header_style(TextStyle::basic_center().style(Style::new().on(Color::Blue)));
|
table.set_header_style(TextStyle::basic_center().style(Style::new().on(Color::Blue)));
|
||||||
|
|
||||||
let theme = TableTheme::rounded();
|
let table_cfg = NuTableConfig {
|
||||||
let table_cfg = TableConfig::new().theme(theme).with_header(true);
|
theme: TableTheme::rounded(),
|
||||||
|
with_header: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let output_table = table
|
let output_table = table
|
||||||
.draw(table_cfg, width)
|
.draw(table_cfg, width)
|
||||||
|
175
crates/nu-table/src/common.rs
Normal file
175
crates/nu-table/src/common.rs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
||||||
|
use nu_protocol::TrimStrategy;
|
||||||
|
use nu_protocol::{Config, FooterMode, ShellError, Span, Value};
|
||||||
|
|
||||||
|
use crate::{clean_charset, string_wrap, NuTableConfig, TableOutput, TableTheme};
|
||||||
|
|
||||||
|
pub type NuText = (String, TextStyle);
|
||||||
|
pub type TableResult = Result<Option<TableOutput>, ShellError>;
|
||||||
|
pub type StringResult = Result<Option<String>, ShellError>;
|
||||||
|
|
||||||
|
pub const INDEX_COLUMN_NAME: &str = "index";
|
||||||
|
|
||||||
|
pub fn create_nu_table_config(
|
||||||
|
config: &Config,
|
||||||
|
comp: &StyleComputer,
|
||||||
|
out: &TableOutput,
|
||||||
|
expand: bool,
|
||||||
|
) -> NuTableConfig {
|
||||||
|
NuTableConfig {
|
||||||
|
theme: load_theme_from_config(config),
|
||||||
|
with_footer: with_footer(config, out.with_header, out.table.count_rows()),
|
||||||
|
with_index: out.with_index,
|
||||||
|
with_header: out.with_header,
|
||||||
|
split_color: Some(lookup_separator_color(comp)),
|
||||||
|
trim: config.trim_strategy.clone(),
|
||||||
|
header_on_border: config.table_move_header,
|
||||||
|
expand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nu_value_to_string(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
|
||||||
|
let float_precision = cfg.float_precision as usize;
|
||||||
|
let text = val.into_abbreviated_string(cfg);
|
||||||
|
make_styled_string(style, text, Some(val), float_precision)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nu_value_to_string_clean(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
|
||||||
|
let (text, style) = nu_value_to_string(val, cfg, style);
|
||||||
|
let text = clean_charset(&text);
|
||||||
|
(text, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error_sign(style_computer: &StyleComputer) -> (String, TextStyle) {
|
||||||
|
make_styled_string(style_computer, String::from("❎"), None, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrap_text(text: &str, width: usize, config: &Config) -> String {
|
||||||
|
string_wrap(text, width, is_cfg_trim_keep_words(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_header_style(style_computer: &StyleComputer) -> TextStyle {
|
||||||
|
TextStyle::with_style(
|
||||||
|
Alignment::Center,
|
||||||
|
style_computer.compute("header", &Value::string("", Span::unknown())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_index_style(style_computer: &StyleComputer) -> TextStyle {
|
||||||
|
TextStyle::with_style(
|
||||||
|
Alignment::Right,
|
||||||
|
style_computer.compute("row_index", &Value::string("", Span::unknown())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_value_style(value: &Value, config: &Config, style_computer: &StyleComputer) -> NuText {
|
||||||
|
match value {
|
||||||
|
// Float precision is required here.
|
||||||
|
Value::Float { val, .. } => (
|
||||||
|
format!("{:.prec$}", val, prec = config.float_precision as usize),
|
||||||
|
style_computer.style_primitive(value),
|
||||||
|
),
|
||||||
|
_ => (
|
||||||
|
value.into_abbreviated_string(config),
|
||||||
|
style_computer.style_primitive(value),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_empty_style(style_computer: &StyleComputer) -> NuText {
|
||||||
|
(
|
||||||
|
String::from("❎"),
|
||||||
|
TextStyle::with_style(
|
||||||
|
Alignment::Right,
|
||||||
|
style_computer.compute("empty", &Value::nothing(Span::unknown())),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_styled_string(
|
||||||
|
style_computer: &StyleComputer,
|
||||||
|
text: String,
|
||||||
|
value: Option<&Value>, // None represents table holes.
|
||||||
|
float_precision: usize,
|
||||||
|
) -> NuText {
|
||||||
|
match value {
|
||||||
|
Some(value) => {
|
||||||
|
match value {
|
||||||
|
Value::Float { .. } => {
|
||||||
|
// set dynamic precision from config
|
||||||
|
let precise_number = match convert_with_precision(&text, float_precision) {
|
||||||
|
Ok(num) => num,
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
};
|
||||||
|
(precise_number, style_computer.style_primitive(value))
|
||||||
|
}
|
||||||
|
_ => (text, style_computer.style_primitive(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Though holes are not the same as null, the closure for "empty" is passed a null anyway.
|
||||||
|
(
|
||||||
|
text,
|
||||||
|
TextStyle::with_style(
|
||||||
|
Alignment::Center,
|
||||||
|
style_computer.compute("empty", &Value::nothing(Span::unknown())),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellError> {
|
||||||
|
// vall will always be a f64 so convert it with precision formatting
|
||||||
|
let val_float = match val.trim().parse::<f64>() {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(ShellError::GenericError(
|
||||||
|
format!("error converting string [{}] to f64", &val),
|
||||||
|
"".to_string(),
|
||||||
|
None,
|
||||||
|
Some(e.to_string()),
|
||||||
|
Vec::new(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(format!("{val_float:.precision$}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_cfg_trim_keep_words(config: &Config) -> bool {
|
||||||
|
matches!(
|
||||||
|
config.trim_strategy,
|
||||||
|
TrimStrategy::Wrap {
|
||||||
|
try_to_keep_words: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_theme_from_config(config: &Config) -> TableTheme {
|
||||||
|
match config.table_mode.as_str() {
|
||||||
|
"basic" => TableTheme::basic(),
|
||||||
|
"thin" => TableTheme::thin(),
|
||||||
|
"light" => TableTheme::light(),
|
||||||
|
"compact" => TableTheme::compact(),
|
||||||
|
"with_love" => TableTheme::with_love(),
|
||||||
|
"compact_double" => TableTheme::compact_double(),
|
||||||
|
"rounded" => TableTheme::rounded(),
|
||||||
|
"reinforced" => TableTheme::reinforced(),
|
||||||
|
"heavy" => TableTheme::heavy(),
|
||||||
|
"none" => TableTheme::none(),
|
||||||
|
_ => TableTheme::rounded(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_separator_color(style_computer: &StyleComputer) -> nu_ansi_term::Style {
|
||||||
|
style_computer.compute("separator", &Value::nothing(Span::unknown()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_footer(config: &Config, with_header: bool, count_records: usize) -> bool {
|
||||||
|
with_header && need_footer(config, count_records as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn need_footer(config: &Config, count_records: u64) -> bool {
|
||||||
|
matches!(config.footer_mode, FooterMode::RowCount(limit) if count_records > limit)
|
||||||
|
|| matches!(config.footer_mode, FooterMode::Always)
|
||||||
|
}
|
@ -4,12 +4,12 @@ mod types;
|
|||||||
mod unstructured_table;
|
mod unstructured_table;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
pub mod common;
|
||||||
|
|
||||||
|
pub use common::{StringResult, TableResult};
|
||||||
pub use nu_color_config::TextStyle;
|
pub use nu_color_config::TextStyle;
|
||||||
pub use table::{Alignments, Cell, NuTable, TableConfig};
|
pub use table::{NuTable, NuTableCell, NuTableConfig};
|
||||||
pub use table_theme::TableTheme;
|
pub use table_theme::TableTheme;
|
||||||
pub use types::{
|
pub use types::{CollapsedTable, ExpandedTable, JustTable, TableOpts, TableOutput};
|
||||||
clean_charset, value_to_clean_styled_string, value_to_styled_string, BuildConfig,
|
|
||||||
CollapsedTable, ExpandedTable, JustTable, NuText, StringResult, TableOutput, TableResult,
|
|
||||||
};
|
|
||||||
pub use unstructured_table::UnstructuredTable;
|
pub use unstructured_table::UnstructuredTable;
|
||||||
pub use util::*;
|
pub use util::*;
|
||||||
|
@ -12,25 +12,27 @@ use tabled::{
|
|||||||
dimension::CompleteDimensionVecRecords,
|
dimension::CompleteDimensionVecRecords,
|
||||||
records::{
|
records::{
|
||||||
vec_records::{CellInfo, VecRecords},
|
vec_records::{CellInfo, VecRecords},
|
||||||
ExactRecords, Records,
|
ExactRecords, PeekableRecords, Records, Resizable,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
settings::{
|
settings::{
|
||||||
formatting::AlignmentStrategy, object::Segment, peaker::Peaker, Color, Modify, Settings,
|
formatting::AlignmentStrategy, object::Segment, peaker::Peaker, themes::ColumnNames, Color,
|
||||||
TableOption, Width,
|
Modify, Settings, TableOption, Width,
|
||||||
},
|
},
|
||||||
Table,
|
Table,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Table represent a table view.
|
/// NuTable is a table rendering implementation.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NuTable {
|
pub struct NuTable {
|
||||||
data: Data,
|
data: NuTableData,
|
||||||
styles: Styles,
|
styles: Styles,
|
||||||
alignments: Alignments,
|
alignments: Alignments,
|
||||||
size: (usize, usize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NuTableData = VecRecords<NuTableCell>;
|
||||||
|
pub type NuTableCell = CellInfo<String>;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
struct Styles {
|
struct Styles {
|
||||||
index: AnsiColor<'static>,
|
index: AnsiColor<'static>,
|
||||||
@ -39,27 +41,39 @@ struct Styles {
|
|||||||
data_is_set: bool,
|
data_is_set: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Data = VecRecords<Cell>;
|
#[derive(Debug, Clone)]
|
||||||
pub type Cell = CellInfo<String>;
|
struct Alignments {
|
||||||
|
data: AlignmentHorizontal,
|
||||||
|
index: AlignmentHorizontal,
|
||||||
|
header: AlignmentHorizontal,
|
||||||
|
columns: HashMap<usize, AlignmentHorizontal>,
|
||||||
|
cells: HashMap<Position, AlignmentHorizontal>,
|
||||||
|
}
|
||||||
|
|
||||||
impl NuTable {
|
impl NuTable {
|
||||||
/// Creates an empty [Table] instance.
|
/// Creates an empty [Table] instance.
|
||||||
pub fn new(count_rows: usize, count_columns: usize) -> Self {
|
pub fn new(count_rows: usize, count_columns: usize) -> Self {
|
||||||
let data = VecRecords::new(vec![vec![CellInfo::default(); count_columns]; count_rows]);
|
|
||||||
Self {
|
Self {
|
||||||
data,
|
data: VecRecords::new(vec![vec![CellInfo::default(); count_columns]; count_rows]),
|
||||||
size: (count_rows, count_columns),
|
|
||||||
styles: Styles::default(),
|
styles: Styles::default(),
|
||||||
alignments: Alignments::default(),
|
alignments: Alignments {
|
||||||
|
data: AlignmentHorizontal::Left,
|
||||||
|
index: AlignmentHorizontal::Right,
|
||||||
|
header: AlignmentHorizontal::Center,
|
||||||
|
columns: HashMap::default(),
|
||||||
|
cells: HashMap::default(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return amount of rows.
|
||||||
pub fn count_rows(&self) -> usize {
|
pub fn count_rows(&self) -> usize {
|
||||||
self.size.0
|
self.data.count_rows()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return amount of columns.
|
||||||
pub fn count_columns(&self) -> usize {
|
pub fn count_columns(&self) -> usize {
|
||||||
self.size.1
|
self.data.count_columns()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, pos: Position, text: String) {
|
pub fn insert(&mut self, pos: Position, text: String) {
|
||||||
@ -79,7 +93,7 @@ impl NuTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cell_style(&mut self, pos: Position, style: TextStyle) {
|
pub fn insert_style(&mut self, pos: Position, style: TextStyle) {
|
||||||
if let Some(style) = style.color_style {
|
if let Some(style) = style.color_style {
|
||||||
let style = AnsiColor::from(convert_style(style));
|
let style = AnsiColor::from(convert_style(style));
|
||||||
self.styles.data.insert(Entity::Cell(pos.0, pos.1), style);
|
self.styles.data.insert(Entity::Cell(pos.0, pos.1), style);
|
||||||
@ -123,12 +137,12 @@ impl NuTable {
|
|||||||
/// Converts a table to a String.
|
/// Converts a table to a String.
|
||||||
///
|
///
|
||||||
/// It returns None in case where table cannot be fit to a terminal width.
|
/// It returns None in case where table cannot be fit to a terminal width.
|
||||||
pub fn draw(self, config: TableConfig, termwidth: usize) -> Option<String> {
|
pub fn draw(self, config: NuTableConfig, termwidth: usize) -> Option<String> {
|
||||||
build_table(self.data, config, self.alignments, self.styles, termwidth)
|
build_table(self.data, config, self.alignments, self.styles, termwidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a total table width.
|
/// Return a total table width.
|
||||||
pub fn total_width(&self, config: &TableConfig) -> usize {
|
pub fn total_width(&self, config: &NuTableConfig) -> usize {
|
||||||
let config = get_config(&config.theme, false, None);
|
let config = get_config(&config.theme, false, None);
|
||||||
let widths = build_width(&self.data);
|
let widths = build_width(&self.data);
|
||||||
get_total_width2(&widths, &config)
|
get_total_width2(&widths, &config)
|
||||||
@ -137,107 +151,43 @@ impl NuTable {
|
|||||||
|
|
||||||
impl From<Vec<Vec<CellInfo<String>>>> for NuTable {
|
impl From<Vec<Vec<CellInfo<String>>>> for NuTable {
|
||||||
fn from(value: Vec<Vec<CellInfo<String>>>) -> Self {
|
fn from(value: Vec<Vec<CellInfo<String>>>) -> Self {
|
||||||
let data = VecRecords::new(value);
|
let mut nutable = Self::new(0, 0);
|
||||||
let size = (data.count_rows(), data.count_columns());
|
nutable.data = VecRecords::new(value);
|
||||||
Self {
|
|
||||||
data,
|
nutable
|
||||||
size,
|
|
||||||
alignments: Alignments::default(),
|
|
||||||
styles: Styles::default(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TableConfig {
|
pub struct NuTableConfig {
|
||||||
theme: TableTheme,
|
pub theme: TableTheme,
|
||||||
trim: TrimStrategy,
|
pub trim: TrimStrategy,
|
||||||
split_color: Option<Style>,
|
pub split_color: Option<Style>,
|
||||||
expand: bool,
|
pub expand: bool,
|
||||||
with_index: bool,
|
pub with_index: bool,
|
||||||
with_header: bool,
|
pub with_header: bool,
|
||||||
with_footer: bool,
|
pub with_footer: bool,
|
||||||
|
pub header_on_border: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableConfig {
|
impl Default for NuTableConfig {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
theme: TableTheme::basic(),
|
theme: TableTheme::basic(),
|
||||||
|
trim: TrimStrategy::truncate(None),
|
||||||
with_header: false,
|
with_header: false,
|
||||||
with_index: false,
|
with_index: false,
|
||||||
with_footer: false,
|
with_footer: false,
|
||||||
expand: false,
|
expand: false,
|
||||||
trim: TrimStrategy::truncate(None),
|
|
||||||
split_color: None,
|
split_color: None,
|
||||||
}
|
header_on_border: false,
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expand(mut self, on: bool) -> Self {
|
|
||||||
self.expand = on;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn trim(mut self, strategy: TrimStrategy) -> Self {
|
|
||||||
self.trim = strategy;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn line_style(mut self, color: Style) -> Self {
|
|
||||||
self.split_color = Some(color);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_header(mut self, on: bool) -> Self {
|
|
||||||
self.with_header = on;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_footer(mut self, on: bool) -> Self {
|
|
||||||
self.with_footer = on;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_index(mut self, on: bool) -> Self {
|
|
||||||
self.with_index = on;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn theme(mut self, theme: TableTheme) -> Self {
|
|
||||||
self.theme = theme;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TableConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Alignments {
|
|
||||||
data: AlignmentHorizontal,
|
|
||||||
index: AlignmentHorizontal,
|
|
||||||
header: AlignmentHorizontal,
|
|
||||||
columns: HashMap<usize, AlignmentHorizontal>,
|
|
||||||
cells: HashMap<Position, AlignmentHorizontal>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Alignments {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
data: AlignmentHorizontal::Left,
|
|
||||||
index: AlignmentHorizontal::Right,
|
|
||||||
header: AlignmentHorizontal::Center,
|
|
||||||
columns: HashMap::default(),
|
|
||||||
cells: HashMap::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_table(
|
fn build_table(
|
||||||
mut data: Data,
|
mut data: NuTableData,
|
||||||
cfg: TableConfig,
|
cfg: NuTableConfig,
|
||||||
alignments: Alignments,
|
alignments: Alignments,
|
||||||
styles: Styles,
|
styles: Styles,
|
||||||
termwidth: usize,
|
termwidth: usize,
|
||||||
@ -259,22 +209,24 @@ fn build_table(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_table(
|
fn draw_table(
|
||||||
data: Data,
|
data: NuTableData,
|
||||||
alignments: Alignments,
|
mut alignments: Alignments,
|
||||||
styles: Styles,
|
mut styles: Styles,
|
||||||
widths: Vec<usize>,
|
widths: Vec<usize>,
|
||||||
cfg: TableConfig,
|
cfg: NuTableConfig,
|
||||||
termwidth: usize,
|
termwidth: usize,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
let data: Vec<Vec<_>> = data.into();
|
let data: Vec<Vec<_>> = data.into();
|
||||||
let mut table = Builder::from(data).build();
|
let mut table = Builder::from(data).build();
|
||||||
|
|
||||||
let with_footer = cfg.with_footer;
|
let need_header_move = cfg.header_on_border && has_horizontals_for_header(&cfg);
|
||||||
let with_index = cfg.with_index;
|
let with_index = cfg.with_index;
|
||||||
let with_header = cfg.with_header && table.count_rows() > 1;
|
let with_header = cfg.with_header && table.count_rows() > 1 && !need_header_move;
|
||||||
|
let with_footer = cfg.with_footer && !need_header_move;
|
||||||
let sep_color = cfg.split_color;
|
let sep_color = cfg.split_color;
|
||||||
|
|
||||||
load_theme(&mut table, &cfg.theme, with_footer, with_header, sep_color);
|
load_theme(&mut table, &cfg.theme, with_footer, with_header, sep_color);
|
||||||
|
move_header_on_border(&mut table, &mut alignments, &mut styles, &cfg);
|
||||||
align_table(&mut table, alignments, with_index, with_header, with_footer);
|
align_table(&mut table, alignments, with_index, with_header, with_footer);
|
||||||
colorize_table(&mut table, styles, with_index, with_header, with_footer);
|
colorize_table(&mut table, styles, with_index, with_header, with_footer);
|
||||||
|
|
||||||
@ -301,6 +253,55 @@ fn draw_table(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_header_on_border(
|
||||||
|
table: &mut Table,
|
||||||
|
alignments: &mut Alignments,
|
||||||
|
styles: &mut Styles,
|
||||||
|
cfg: &NuTableConfig,
|
||||||
|
) {
|
||||||
|
if !cfg.header_on_border || table.count_rows() <= 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let color = Color::from(styles.header.clone());
|
||||||
|
let has_bottom_line = table
|
||||||
|
.get_config()
|
||||||
|
.has_horizontal(table.count_rows(), table.count_rows());
|
||||||
|
let has_top_line = table.get_config().has_horizontal(0, table.count_rows());
|
||||||
|
|
||||||
|
if cfg.with_header && has_top_line {
|
||||||
|
move_row_on_border(table, 0, color.clone(), alignments.header)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.with_footer && has_bottom_line {
|
||||||
|
let last_row = table.count_rows() - 1;
|
||||||
|
move_row_on_border(table, last_row, color, alignments.header)
|
||||||
|
}
|
||||||
|
|
||||||
|
// because we remove rows we will invalidate the data alignments and colors
|
||||||
|
// so we need to restore it back
|
||||||
|
|
||||||
|
if cfg.with_header && has_top_line {
|
||||||
|
if !alignments.cells.is_empty() {
|
||||||
|
for row in 1..table.count_rows() {
|
||||||
|
for col in 0..table.count_rows() {
|
||||||
|
let val = alignments.cells.get(&(row, col));
|
||||||
|
if let Some(val) = val {
|
||||||
|
alignments.cells.insert((row - 1, col), *val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in 1..table.count_rows() {
|
||||||
|
for col in 0..table.count_rows() {
|
||||||
|
let val = styles.data.get(Entity::Cell(row, col)).clone();
|
||||||
|
styles.data.insert(Entity::Cell(row - 1, col), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn align_table(
|
fn align_table(
|
||||||
table: &mut Table,
|
table: &mut Table,
|
||||||
alignments: Alignments,
|
alignments: Alignments,
|
||||||
@ -429,7 +430,11 @@ fn table_trim_columns(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_truncate_columns(data: &mut Data, theme: &TableTheme, termwidth: usize) -> Vec<usize> {
|
fn maybe_truncate_columns(
|
||||||
|
data: &mut NuTableData,
|
||||||
|
theme: &TableTheme,
|
||||||
|
termwidth: usize,
|
||||||
|
) -> Vec<usize> {
|
||||||
const TERMWIDTH_THRESHOLD: usize = 120;
|
const TERMWIDTH_THRESHOLD: usize = 120;
|
||||||
|
|
||||||
let truncate = if termwidth > TERMWIDTH_THRESHOLD {
|
let truncate = if termwidth > TERMWIDTH_THRESHOLD {
|
||||||
@ -443,7 +448,7 @@ fn maybe_truncate_columns(data: &mut Data, theme: &TableTheme, termwidth: usize)
|
|||||||
|
|
||||||
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
|
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
|
||||||
fn truncate_columns_by_content(
|
fn truncate_columns_by_content(
|
||||||
data: &mut Data,
|
data: &mut NuTableData,
|
||||||
theme: &TableTheme,
|
theme: &TableTheme,
|
||||||
termwidth: usize,
|
termwidth: usize,
|
||||||
) -> Vec<usize> {
|
) -> Vec<usize> {
|
||||||
@ -522,7 +527,7 @@ fn truncate_columns_by_content(
|
|||||||
|
|
||||||
// VERSION where we are showing AS MANY COLUMNS AS POSSIBLE but as a side affect they MIGHT CONTAIN AS LITTLE CONTENT AS POSSIBLE
|
// VERSION where we are showing AS MANY COLUMNS AS POSSIBLE but as a side affect they MIGHT CONTAIN AS LITTLE CONTENT AS POSSIBLE
|
||||||
fn truncate_columns_by_columns(
|
fn truncate_columns_by_columns(
|
||||||
data: &mut Data,
|
data: &mut NuTableData,
|
||||||
theme: &TableTheme,
|
theme: &TableTheme,
|
||||||
termwidth: usize,
|
termwidth: usize,
|
||||||
) -> Vec<usize> {
|
) -> Vec<usize> {
|
||||||
@ -620,7 +625,7 @@ fn get_config(theme: &TableTheme, with_header: bool, color: Option<Style>) -> Co
|
|||||||
table.get_config().clone()
|
table.get_config().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_empty_column(data: &mut Data) {
|
fn push_empty_column(data: &mut NuTableData) {
|
||||||
let records = std::mem::take(data);
|
let records = std::mem::take(data);
|
||||||
let mut inner: Vec<Vec<_>> = records.into();
|
let mut inner: Vec<Vec<_>> = records.into();
|
||||||
|
|
||||||
@ -632,7 +637,7 @@ fn push_empty_column(data: &mut Data) {
|
|||||||
*data = VecRecords::new(inner);
|
*data = VecRecords::new(inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn duplicate_row(data: &mut Data, row: usize) {
|
fn duplicate_row(data: &mut NuTableData, row: usize) {
|
||||||
let records = std::mem::take(data);
|
let records = std::mem::take(data);
|
||||||
let mut inner: Vec<Vec<_>> = records.into();
|
let mut inner: Vec<Vec<_>> = records.into();
|
||||||
|
|
||||||
@ -642,7 +647,7 @@ fn duplicate_row(data: &mut Data, row: usize) {
|
|||||||
*data = VecRecords::new(inner);
|
*data = VecRecords::new(inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn truncate_columns(data: &mut Data, count: usize) {
|
fn truncate_columns(data: &mut NuTableData, count: usize) {
|
||||||
let records = std::mem::take(data);
|
let records = std::mem::take(data);
|
||||||
let mut inner: Vec<Vec<_>> = records.into();
|
let mut inner: Vec<Vec<_>> = records.into();
|
||||||
|
|
||||||
@ -697,3 +702,39 @@ fn build_width(records: &VecRecords<CellInfo<String>>) -> Vec<usize> {
|
|||||||
|
|
||||||
widths
|
widths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_row_on_border(table: &mut Table, row: usize, color: Color, alignment: AlignmentHorizontal) {
|
||||||
|
if table.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let columns = (0..table.get_records().count_columns())
|
||||||
|
.map(|column| table.get_records().get_text((row, column)).to_owned())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
table.get_records_mut().remove_row(row);
|
||||||
|
|
||||||
|
if table.is_empty() {
|
||||||
|
table.get_records_mut().push_row();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut line = 0;
|
||||||
|
if row != 0 {
|
||||||
|
line = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
let colors = std::iter::repeat(color)
|
||||||
|
.take(table.count_columns())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let names = ColumnNames::new(columns)
|
||||||
|
.set_line(line)
|
||||||
|
.set_colors(colors)
|
||||||
|
.set_alignment(alignment);
|
||||||
|
|
||||||
|
table.with(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_horizontals_for_header(cfg: &NuTableConfig) -> bool {
|
||||||
|
cfg.theme.get_theme().get_horizontal(0).is_some()
|
||||||
|
|| cfg.theme.get_theme().get_borders().has_horizontal()
|
||||||
|
}
|
||||||
|
@ -191,6 +191,10 @@ impl TableTheme {
|
|||||||
self.has_inner
|
self.has_inner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_horizontals(&self) -> bool {
|
||||||
|
self.full_theme.get_borders().has_horizontal()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_theme_full(&self) -> RawStyle {
|
pub fn get_theme_full(&self) -> RawStyle {
|
||||||
self.full_theme.clone()
|
self.full_theme.clone()
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,17 @@ use nu_protocol::{Config, Span, Value};
|
|||||||
|
|
||||||
use crate::UnstructuredTable;
|
use crate::UnstructuredTable;
|
||||||
|
|
||||||
use super::{
|
use crate::common::nu_value_to_string_clean;
|
||||||
clean_charset, general::BuildConfig, get_index_style, load_theme_from_config,
|
use crate::{
|
||||||
value_to_styled_string, StringResult,
|
common::{get_index_style, load_theme_from_config},
|
||||||
|
StringResult, TableOpts,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct CollapsedTable;
|
pub struct CollapsedTable;
|
||||||
|
|
||||||
impl CollapsedTable {
|
impl CollapsedTable {
|
||||||
pub fn build(value: Value, opts: BuildConfig<'_>) -> StringResult {
|
pub fn build(value: Value, opts: TableOpts<'_>) -> StringResult {
|
||||||
collapsed_table(value, opts.config, opts.term_width, opts.style_computer)
|
collapsed_table(value, opts.config, opts.width, opts.style_computer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,20 +57,7 @@ fn colorize_value(value: &mut Value, config: &Config, style_computer: &StyleComp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
value => {
|
value => {
|
||||||
let (text, style) = value_to_styled_string(value, config, style_computer);
|
let (text, style) = nu_value_to_string_clean(value, config, style_computer);
|
||||||
|
|
||||||
let is_string = matches!(value, Value::String { .. });
|
|
||||||
if is_string {
|
|
||||||
let mut text = clean_charset(&text);
|
|
||||||
if let Some(color) = style.color_style {
|
|
||||||
text = color.paint(text).to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
let span = value.span().unwrap_or(Span::unknown());
|
|
||||||
*value = Value::string(text, span);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(color) = style.color_style {
|
if let Some(color) = style.color_style {
|
||||||
let text = color.paint(text).to_string();
|
let text = color.paint(text).to_string();
|
||||||
let span = value.span().unwrap_or(Span::unknown());
|
let span = value.span().unwrap_or(Span::unknown());
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
|
use std::cmp::max;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
||||||
use nu_engine::column::get_columns;
|
use nu_engine::column::get_columns;
|
||||||
use nu_protocol::{ast::PathMember, Config, Span, TableIndexMode, Value};
|
use nu_protocol::{ast::PathMember, Config, ShellError, Span, TableIndexMode, Value};
|
||||||
use std::collections::HashMap;
|
use tabled::grid::config::Position;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{cmp::max, sync::atomic::AtomicBool};
|
|
||||||
|
|
||||||
use crate::{string_width, Cell, NuTable};
|
use crate::{
|
||||||
|
common::{
|
||||||
use super::{clean_charset, value_to_clean_styled_string};
|
create_nu_table_config, error_sign, get_header_style, get_index_style,
|
||||||
use super::{
|
load_theme_from_config, nu_value_to_string, nu_value_to_string_clean, wrap_text, NuText,
|
||||||
create_table_config, error_sign, general::BuildConfig, get_header_style, get_index_style,
|
StringResult, TableResult, INDEX_COLUMN_NAME,
|
||||||
load_theme_from_config, set_data_styles, value_to_styled_string, wrap_text, NuText,
|
},
|
||||||
StringResult, TableOutput, TableResult, INDEX_COLUMN_NAME,
|
string_width, NuTable, NuTableCell, TableOpts, TableOutput,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -30,69 +31,35 @@ impl ExpandedTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_value(&self, item: &Value, opts: BuildConfig<'_>) -> NuText {
|
pub fn build_value(self, item: &Value, opts: TableOpts<'_>) -> NuText {
|
||||||
let opts = Options {
|
expanded_table_entry2(item, Cfg { opts, format: self })
|
||||||
ctrlc: opts.ctrlc,
|
|
||||||
config: opts.config,
|
|
||||||
style_computer: opts.style_computer,
|
|
||||||
available_width: opts.term_width,
|
|
||||||
span: opts.span,
|
|
||||||
format: self.clone(),
|
|
||||||
};
|
|
||||||
expanded_table_entry2(item, opts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_map(
|
pub fn build_map(self, cols: &[String], vals: &[Value], opts: TableOpts<'_>) -> StringResult {
|
||||||
&self,
|
expanded_table_kv(cols, vals, Cfg { opts, format: self })
|
||||||
cols: &[String],
|
|
||||||
vals: &[Value],
|
|
||||||
opts: BuildConfig<'_>,
|
|
||||||
) -> StringResult {
|
|
||||||
let opts = Options {
|
|
||||||
ctrlc: opts.ctrlc,
|
|
||||||
config: opts.config,
|
|
||||||
style_computer: opts.style_computer,
|
|
||||||
available_width: opts.term_width,
|
|
||||||
span: opts.span,
|
|
||||||
format: self.clone(),
|
|
||||||
};
|
|
||||||
expanded_table_kv(cols, vals, opts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_list(
|
pub fn build_list(self, vals: &[Value], opts: TableOpts<'_>) -> StringResult {
|
||||||
&self,
|
let cfg = Cfg {
|
||||||
vals: &[Value],
|
opts: opts.clone(),
|
||||||
opts: BuildConfig<'_>,
|
format: self,
|
||||||
row_offset: usize,
|
|
||||||
) -> StringResult {
|
|
||||||
let opts1 = Options {
|
|
||||||
ctrlc: opts.ctrlc,
|
|
||||||
config: opts.config,
|
|
||||||
style_computer: opts.style_computer,
|
|
||||||
available_width: opts.term_width,
|
|
||||||
span: opts.span,
|
|
||||||
format: self.clone(),
|
|
||||||
};
|
};
|
||||||
let out = match expanded_table_list(vals, row_offset, opts1)? {
|
let out = match expanded_table_list(vals, cfg)? {
|
||||||
Some(out) => out,
|
Some(out) => out,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
maybe_expand_table(out, opts.term_width, opts.config, opts.style_computer)
|
maybe_expand_table(out, opts.width, &opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Options<'a> {
|
struct Cfg<'a> {
|
||||||
ctrlc: Option<Arc<AtomicBool>>,
|
opts: TableOpts<'a>,
|
||||||
config: &'a Config,
|
|
||||||
style_computer: &'a StyleComputer<'a>,
|
|
||||||
available_width: usize,
|
|
||||||
format: ExpandedTable,
|
format: ExpandedTable,
|
||||||
span: Span,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> TableResult {
|
fn expanded_table_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;
|
||||||
@ -105,8 +72,9 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2 - split lines
|
// 2 - split lines
|
||||||
let mut available_width = opts
|
let mut available_width = cfg
|
||||||
.available_width
|
.opts
|
||||||
|
.width
|
||||||
.saturating_sub(SPLIT_LINE_SPACE + SPLIT_LINE_SPACE);
|
.saturating_sub(SPLIT_LINE_SPACE + SPLIT_LINE_SPACE);
|
||||||
if available_width < MIN_CELL_CONTENT_WIDTH {
|
if available_width < MIN_CELL_CONTENT_WIDTH {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@ -114,7 +82,7 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
|
|
||||||
let headers = get_columns(input);
|
let headers = get_columns(input);
|
||||||
|
|
||||||
let with_index = match opts.config.table_index_mode {
|
let with_index = match cfg.opts.config.table_index_mode {
|
||||||
TableIndexMode::Always => true,
|
TableIndexMode::Always => true,
|
||||||
TableIndexMode::Never => false,
|
TableIndexMode::Never => false,
|
||||||
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
|
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
|
||||||
@ -134,11 +102,11 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
|
|
||||||
if with_index {
|
if with_index {
|
||||||
if with_header {
|
if with_header {
|
||||||
data[0].push(Cell::exact(String::from("#"), 1, vec![]));
|
data[0].push(NuTableCell::exact(String::from("#"), 1, vec![]));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (row, item) in input.iter().enumerate() {
|
for (row, item) in input.iter().enumerate() {
|
||||||
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
|
if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,14 +114,15 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
return Err(*error.clone());
|
return Err(*error.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = row + row_offset;
|
let index = row + cfg.opts.row_offset;
|
||||||
let text = matches!(item, Value::Record { .. })
|
let text = matches!(item, Value::Record { .. })
|
||||||
.then(|| lookup_index_value(item, opts.config).unwrap_or_else(|| index.to_string()))
|
.then(|| {
|
||||||
|
lookup_index_value(item, cfg.opts.config).unwrap_or_else(|| index.to_string())
|
||||||
|
})
|
||||||
.unwrap_or_else(|| index.to_string());
|
.unwrap_or_else(|| index.to_string());
|
||||||
|
|
||||||
let value = Cell::new(text);
|
|
||||||
|
|
||||||
let row = row + with_header as usize;
|
let row = row + with_header as usize;
|
||||||
|
let value = NuTableCell::new(text);
|
||||||
data[row].push(value);
|
data[row].push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +146,7 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (row, item) in input.iter().enumerate() {
|
for (row, item) in input.iter().enumerate() {
|
||||||
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
|
if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,9 +154,9 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
return Err(*error.clone());
|
return Err(*error.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut oopts = opts.clone();
|
let mut inner_cfg = cfg.clone();
|
||||||
oopts.available_width = available_width;
|
inner_cfg.opts.width = available_width;
|
||||||
let (mut text, style) = expanded_table_entry2(item, oopts.clone());
|
let (mut text, style) = expanded_table_entry2(item, inner_cfg);
|
||||||
|
|
||||||
let value_width = string_width(&text);
|
let value_width = string_width(&text);
|
||||||
if value_width > available_width {
|
if value_width > available_width {
|
||||||
@ -196,16 +165,16 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
//
|
//
|
||||||
// todo: Maybe convert_to_table2_entry could do for strings to not mess caller code?
|
// todo: Maybe convert_to_table2_entry could do for strings to not mess caller code?
|
||||||
|
|
||||||
text = wrap_text(&text, available_width, opts.config);
|
text = wrap_text(&text, available_width, cfg.opts.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = Cell::new(text);
|
let value = NuTableCell::new(text);
|
||||||
data[row].push(value);
|
data[row].push(value);
|
||||||
data_styles.insert((row, with_index as usize), style);
|
data_styles.insert((row, with_index as usize), style);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.set_index_style(get_index_style(opts.style_computer));
|
table.set_index_style(get_index_style(cfg.opts.style_computer));
|
||||||
set_data_styles(&mut table, data_styles);
|
set_data_styles(&mut table, data_styles);
|
||||||
|
|
||||||
return Ok(Some(TableOutput::new(table, false, with_index)));
|
return Ok(Some(TableOutput::new(table, false, with_index)));
|
||||||
@ -264,7 +233,7 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (row, item) in input.iter().enumerate() {
|
for (row, item) in input.iter().enumerate() {
|
||||||
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
|
if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,27 +241,27 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
return Err(*error.clone());
|
return Err(*error.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut oopts = opts.clone();
|
let mut inner_cfg = cfg.clone();
|
||||||
oopts.available_width = available;
|
inner_cfg.opts.width = available;
|
||||||
let (mut text, style) = expanded_table_entry(item, header.as_str(), oopts);
|
let (mut text, style) = expanded_table_entry(item, header.as_str(), inner_cfg);
|
||||||
|
|
||||||
let mut value_width = string_width(&text);
|
let mut value_width = string_width(&text);
|
||||||
if value_width > available {
|
if value_width > available {
|
||||||
// it must only happen when a string is produced, so we can safely wrap it.
|
// it must only happen when a string is produced, so we can safely wrap it.
|
||||||
// (it might be string table representation as well)
|
// (it might be string table representation as well)
|
||||||
|
|
||||||
text = wrap_text(&text, available, opts.config);
|
text = wrap_text(&text, available, cfg.opts.config);
|
||||||
value_width = available;
|
value_width = available;
|
||||||
}
|
}
|
||||||
|
|
||||||
column_width = max(column_width, value_width);
|
column_width = max(column_width, value_width);
|
||||||
|
|
||||||
let value = Cell::new(text);
|
let value = NuTableCell::new(text);
|
||||||
data[row + 1].push(value);
|
data[row + 1].push(value);
|
||||||
data_styles.insert((row + 1, col + with_index as usize), style);
|
data_styles.insert((row + 1, col + with_index as usize), style);
|
||||||
}
|
}
|
||||||
|
|
||||||
let head_cell = Cell::new(header);
|
let head_cell = NuTableCell::new(header);
|
||||||
data[0].push(head_cell);
|
data[0].push(head_cell);
|
||||||
|
|
||||||
if column_width > available {
|
if column_width > available {
|
||||||
@ -352,7 +321,7 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
|
|
||||||
let is_last_column = widths.len() == count_columns;
|
let is_last_column = widths.len() == count_columns;
|
||||||
if !is_last_column {
|
if !is_last_column {
|
||||||
let shift = Cell::exact(String::from("..."), 3, vec![]);
|
let shift = NuTableCell::exact(String::from("..."), 3, vec![]);
|
||||||
for row in &mut data {
|
for row in &mut data {
|
||||||
row.push(shift.clone());
|
row.push(shift.clone());
|
||||||
}
|
}
|
||||||
@ -362,92 +331,34 @@ fn expanded_table_list(input: &[Value], row_offset: usize, opts: Options) -> Tab
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.set_index_style(get_index_style(opts.style_computer));
|
table.set_index_style(get_index_style(cfg.opts.style_computer));
|
||||||
table.set_header_style(get_header_style(opts.style_computer));
|
table.set_header_style(get_header_style(cfg.opts.style_computer));
|
||||||
set_data_styles(&mut table, data_styles);
|
set_data_styles(&mut table, data_styles);
|
||||||
|
|
||||||
Ok(Some(TableOutput::new(table, true, with_index)))
|
Ok(Some(TableOutput::new(table, true, with_index)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expanded_table_kv(cols: &[String], vals: &[Value], opts: Options<'_>) -> StringResult {
|
fn expanded_table_kv(cols: &[String], vals: &[Value], cfg: Cfg<'_>) -> StringResult {
|
||||||
let theme = load_theme_from_config(opts.config);
|
let theme = load_theme_from_config(cfg.opts.config);
|
||||||
let key_width = cols.iter().map(|col| string_width(col)).max().unwrap_or(0);
|
let key_width = cols.iter().map(|col| string_width(col)).max().unwrap_or(0);
|
||||||
let count_borders =
|
let count_borders =
|
||||||
theme.has_inner() as usize + theme.has_right() as usize + theme.has_left() as usize;
|
theme.has_inner() as usize + theme.has_right() as usize + theme.has_left() as usize;
|
||||||
let padding = 2;
|
let padding = 2;
|
||||||
if key_width + count_borders + padding + padding > opts.available_width {
|
if key_width + count_borders + padding + padding > cfg.opts.width {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let value_width = opts.available_width - key_width - count_borders - padding - padding;
|
let value_width = cfg.opts.width - key_width - count_borders - padding - padding;
|
||||||
|
|
||||||
let mut data = Vec::with_capacity(cols.len());
|
let mut data = Vec::with_capacity(cols.len());
|
||||||
for (key, value) in cols.iter().zip(vals) {
|
for (key, value) in cols.iter().zip(vals) {
|
||||||
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
|
if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_limited = matches!(opts.format.expand_limit, Some(0));
|
let (value, is_expanded) = match expand_table_value(value, value_width, &cfg)? {
|
||||||
let mut is_expanded = false;
|
Some(val) => val,
|
||||||
let value = if is_limited {
|
|
||||||
let (text, _) = value_to_styled_string(value, opts.config, opts.style_computer);
|
|
||||||
clean_charset(&text)
|
|
||||||
} else {
|
|
||||||
match value {
|
|
||||||
Value::List { vals, span } => {
|
|
||||||
let mut oopts = dive_options(&opts, *span);
|
|
||||||
oopts.available_width = value_width;
|
|
||||||
let table = expanded_table_list(vals, 0, oopts)?;
|
|
||||||
|
|
||||||
match table {
|
|
||||||
Some(out) => {
|
|
||||||
is_expanded = true;
|
|
||||||
|
|
||||||
let table_config =
|
|
||||||
create_table_config(opts.config, opts.style_computer, &out);
|
|
||||||
let value = out.table.draw(table_config, value_width);
|
|
||||||
match value {
|
|
||||||
Some(result) => result,
|
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// it means that the list is empty
|
|
||||||
let text =
|
|
||||||
value_to_styled_string(value, opts.config, opts.style_computer).0;
|
|
||||||
wrap_text(&text, value_width, opts.config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Record { cols, vals, span } => {
|
|
||||||
if cols.is_empty() {
|
|
||||||
// Like list case return styled string instead of empty value
|
|
||||||
let text =
|
|
||||||
value_to_styled_string(value, opts.config, opts.style_computer).0;
|
|
||||||
wrap_text(&text, value_width, opts.config)
|
|
||||||
} else {
|
|
||||||
let mut oopts = dive_options(&opts, *span);
|
|
||||||
oopts.available_width = value_width;
|
|
||||||
let result = expanded_table_kv(cols, vals, oopts)?;
|
|
||||||
match result {
|
|
||||||
Some(result) => {
|
|
||||||
is_expanded = true;
|
|
||||||
result
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let failed_value =
|
|
||||||
value_to_styled_string(value, opts.config, opts.style_computer);
|
|
||||||
wrap_text(&failed_value.0, value_width, opts.config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val => {
|
|
||||||
let text =
|
|
||||||
value_to_clean_styled_string(val, opts.config, opts.style_computer).0;
|
|
||||||
wrap_text(&text, value_width, opts.config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// we want to have a key being aligned to 2nd line,
|
// we want to have a key being aligned to 2nd line,
|
||||||
@ -458,90 +369,154 @@ fn expanded_table_kv(cols: &[String], vals: &[Value], opts: Options<'_>) -> Stri
|
|||||||
key.insert(0, '\n');
|
key.insert(0, '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = Cell::new(key);
|
let key = NuTableCell::new(key);
|
||||||
let val = Cell::new(value);
|
let val = NuTableCell::new(value);
|
||||||
|
|
||||||
let row = vec![key, val];
|
let row = vec![key, val];
|
||||||
|
|
||||||
data.push(row);
|
data.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
let keys_style = get_header_style(opts.style_computer).alignment(Alignment::Left);
|
table.set_index_style(get_key_style(&cfg));
|
||||||
table.set_index_style(keys_style);
|
|
||||||
|
|
||||||
let out = TableOutput::new(table, false, true);
|
let out = TableOutput::new(table, false, true);
|
||||||
|
|
||||||
maybe_expand_table(out, opts.available_width, opts.config, opts.style_computer)
|
maybe_expand_table(out, cfg.opts.width, &cfg.opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expanded_table_entry(item: &Value, header: &str, opts: Options<'_>) -> NuText {
|
// the flag is used as an optimization to not do `value.lines().count()` search.
|
||||||
|
fn expand_table_value(
|
||||||
|
value: &Value,
|
||||||
|
value_width: usize,
|
||||||
|
cfg: &Cfg<'_>,
|
||||||
|
) -> Result<Option<(String, bool)>, ShellError> {
|
||||||
|
let is_limited = matches!(cfg.format.expand_limit, Some(0));
|
||||||
|
if is_limited {
|
||||||
|
return Ok(Some((value_to_string_clean(value, cfg), false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Value::List { vals, span } => {
|
||||||
|
let mut inner_cfg = dive_options(cfg, *span);
|
||||||
|
inner_cfg.opts.width = value_width;
|
||||||
|
let table = expanded_table_list(vals, inner_cfg)?;
|
||||||
|
|
||||||
|
match table {
|
||||||
|
Some(out) => {
|
||||||
|
let cfg = create_table_cfg(cfg, &out);
|
||||||
|
let value = out.table.draw(cfg, value_width);
|
||||||
|
match value {
|
||||||
|
Some(result) => Ok(Some((result, true))),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// it means that the list is empty
|
||||||
|
Ok(Some((
|
||||||
|
value_to_wrapped_string(value, cfg, value_width),
|
||||||
|
false,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Record { cols, vals, span } => {
|
||||||
|
if cols.is_empty() {
|
||||||
|
// Like list case return styled string instead of empty value
|
||||||
|
return Ok(Some((
|
||||||
|
value_to_wrapped_string(value, cfg, value_width),
|
||||||
|
false,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inner_cfg = dive_options(cfg, *span);
|
||||||
|
inner_cfg.opts.width = value_width;
|
||||||
|
let result = expanded_table_kv(cols, vals, inner_cfg)?;
|
||||||
|
match result {
|
||||||
|
Some(result) => Ok(Some((result, true))),
|
||||||
|
None => Ok(Some((
|
||||||
|
value_to_wrapped_string(value, cfg, value_width),
|
||||||
|
false,
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(Some((
|
||||||
|
value_to_wrapped_string_clean(value, cfg, value_width),
|
||||||
|
false,
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key_style(cfg: &Cfg<'_>) -> TextStyle {
|
||||||
|
get_header_style(cfg.opts.style_computer).alignment(Alignment::Left)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expanded_table_entry(item: &Value, header: &str, cfg: Cfg<'_>) -> NuText {
|
||||||
match item {
|
match item {
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
let val = header.to_owned();
|
let val = header.to_owned();
|
||||||
let path = PathMember::String {
|
let path = PathMember::String {
|
||||||
val,
|
val,
|
||||||
span: opts.span,
|
span: cfg.opts.span,
|
||||||
optional: false,
|
optional: false,
|
||||||
};
|
};
|
||||||
let val = item.clone().follow_cell_path(&[path], false);
|
let val = item.clone().follow_cell_path(&[path], false);
|
||||||
|
|
||||||
match val {
|
match val {
|
||||||
Ok(val) => expanded_table_entry2(&val, opts),
|
Ok(val) => expanded_table_entry2(&val, cfg),
|
||||||
Err(_) => error_sign(opts.style_computer),
|
Err(_) => error_sign(cfg.opts.style_computer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => expanded_table_entry2(item, opts),
|
_ => expanded_table_entry2(item, cfg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expanded_table_entry2(item: &Value, opts: Options<'_>) -> NuText {
|
fn expanded_table_entry2(item: &Value, cfg: Cfg<'_>) -> NuText {
|
||||||
let is_limit_reached = matches!(opts.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 value_to_clean_styled_string(item, opts.config, opts.style_computer);
|
return nu_value_to_string_clean(item, cfg.opts.config, cfg.opts.style_computer);
|
||||||
}
|
}
|
||||||
|
|
||||||
match &item {
|
match &item {
|
||||||
Value::Record { cols, vals, span } => {
|
Value::Record { cols, vals, span } => {
|
||||||
if cols.is_empty() && vals.is_empty() {
|
if cols.is_empty() && vals.is_empty() {
|
||||||
return value_to_styled_string(item, opts.config, opts.style_computer);
|
return nu_value_to_string(item, 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
|
||||||
let oopts = dive_options(&opts, *span);
|
let inner_cfg = dive_options(&cfg, *span);
|
||||||
let table = expanded_table_kv(cols, vals, oopts);
|
let table = expanded_table_kv(cols, vals, inner_cfg);
|
||||||
|
|
||||||
match table {
|
match table {
|
||||||
Ok(Some(table)) => (table, TextStyle::default()),
|
Ok(Some(table)) => (table, TextStyle::default()),
|
||||||
_ => value_to_styled_string(item, opts.config, opts.style_computer),
|
_ => nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::List { vals, span } => {
|
Value::List { vals, span } => {
|
||||||
if opts.format.flatten && is_simple_list(vals) {
|
if cfg.format.flatten && is_simple_list(vals) {
|
||||||
return value_list_to_string(
|
return value_list_to_string(
|
||||||
vals,
|
vals,
|
||||||
opts.config,
|
cfg.opts.config,
|
||||||
opts.style_computer,
|
cfg.opts.style_computer,
|
||||||
&opts.format.flatten_sep,
|
&cfg.format.flatten_sep,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let oopts = dive_options(&opts, *span);
|
let inner_cfg = dive_options(&cfg, *span);
|
||||||
let table = expanded_table_list(vals, 0, oopts);
|
let table = expanded_table_list(vals, inner_cfg);
|
||||||
|
|
||||||
let out = match table {
|
let out = match table {
|
||||||
Ok(Some(out)) => out,
|
Ok(Some(out)) => out,
|
||||||
_ => return value_to_styled_string(item, opts.config, opts.style_computer),
|
_ => return nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer),
|
||||||
};
|
};
|
||||||
|
|
||||||
let table_config = create_table_config(opts.config, opts.style_computer, &out);
|
let table_config = create_table_cfg(&cfg, &out);
|
||||||
|
|
||||||
let table = out.table.draw(table_config, usize::MAX);
|
let table = out.table.draw(table_config, usize::MAX);
|
||||||
match table {
|
match table {
|
||||||
Some(table) => (table, TextStyle::default()),
|
Some(table) => (table, TextStyle::default()),
|
||||||
None => value_to_styled_string(item, opts.config, opts.style_computer),
|
None => nu_value_to_string(item, cfg.opts.config, cfg.opts.style_computer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => value_to_clean_styled_string(item, opts.config, opts.style_computer),
|
_ => nu_value_to_string_clean(item, cfg.opts.config, cfg.opts.style_computer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -562,21 +537,21 @@ fn value_list_to_string(
|
|||||||
buf.push_str(flatten_sep);
|
buf.push_str(flatten_sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (text, _) = value_to_clean_styled_string(value, config, style_computer);
|
let text = nu_value_to_string_clean(value, config, style_computer).0;
|
||||||
buf.push_str(&text);
|
buf.push_str(&text);
|
||||||
}
|
}
|
||||||
|
|
||||||
(buf, TextStyle::default())
|
(buf, TextStyle::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dive_options<'b>(opts: &Options<'b>, span: Span) -> Options<'b> {
|
fn dive_options<'b>(cfg: &Cfg<'b>, span: Span) -> Cfg<'b> {
|
||||||
let mut opts = opts.clone();
|
let mut cfg = cfg.clone();
|
||||||
opts.span = span;
|
cfg.opts.span = span;
|
||||||
if let Some(deep) = opts.format.expand_limit.as_mut() {
|
if let Some(deep) = cfg.format.expand_limit.as_mut() {
|
||||||
*deep -= 1
|
*deep -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
opts
|
cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_index_value(item: &Value, config: &Config) -> Option<String> {
|
fn lookup_index_value(item: &Value, config: &Config) -> Option<String> {
|
||||||
@ -584,23 +559,47 @@ fn lookup_index_value(item: &Value, config: &Config) -> Option<String> {
|
|||||||
.map(|value| value.into_string("", config))
|
.map(|value| value.into_string("", config))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_expand_table(
|
fn maybe_expand_table(out: TableOutput, term_width: usize, opts: &TableOpts<'_>) -> StringResult {
|
||||||
out: TableOutput,
|
let mut table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false);
|
||||||
term_width: usize,
|
|
||||||
config: &Config,
|
|
||||||
style_computer: &StyleComputer,
|
|
||||||
) -> StringResult {
|
|
||||||
let mut table_config = create_table_config(config, style_computer, &out);
|
|
||||||
let total_width = out.table.total_width(&table_config);
|
let total_width = out.table.total_width(&table_config);
|
||||||
if total_width < term_width {
|
if total_width < term_width {
|
||||||
const EXPAND_THRESHOLD: f32 = 0.80;
|
const EXPAND_THRESHOLD: f32 = 0.80;
|
||||||
let used_percent = total_width as f32 / term_width as f32;
|
let used_percent = total_width as f32 / term_width as f32;
|
||||||
let need_expansion = total_width < term_width && used_percent > EXPAND_THRESHOLD;
|
let need_expansion = total_width < term_width && used_percent > EXPAND_THRESHOLD;
|
||||||
if need_expansion {
|
if need_expansion {
|
||||||
table_config = table_config.expand(true);
|
table_config.expand = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = out.table.draw(table_config, term_width);
|
Ok(out.table.draw(table_config, term_width))
|
||||||
Ok(output)
|
}
|
||||||
|
|
||||||
|
fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
|
||||||
|
for (pos, style) in styles {
|
||||||
|
table.insert_style(pos, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_table_cfg(cfg: &Cfg<'_>, out: &TableOutput) -> crate::NuTableConfig {
|
||||||
|
create_nu_table_config(cfg.opts.config, cfg.opts.style_computer, out, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_to_string(value: &Value, cfg: &Cfg<'_>) -> String {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_to_wrapped_string(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> String {
|
||||||
|
wrap_text(&value_to_string(value, cfg), value_width, cfg.opts.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_to_wrapped_string_clean(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> String {
|
||||||
|
wrap_text(
|
||||||
|
&value_to_string_clean(value, cfg),
|
||||||
|
value_width,
|
||||||
|
cfg.opts.config,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,84 +1,40 @@
|
|||||||
use nu_color_config::{StyleComputer, TextStyle};
|
use nu_color_config::TextStyle;
|
||||||
use nu_engine::column::get_columns;
|
use nu_engine::column::get_columns;
|
||||||
use nu_protocol::{ast::PathMember, Config, ShellError, Span, TableIndexMode, Value};
|
use nu_protocol::{ast::PathMember, Config, ShellError, Span, TableIndexMode, Value};
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::{Cell, NuTable, NuText};
|
use crate::{
|
||||||
|
clean_charset,
|
||||||
use super::{
|
common::{
|
||||||
clean_charset, create_table_config, get_empty_style, get_header_style, get_index_style,
|
create_nu_table_config, get_empty_style, get_header_style, get_index_style,
|
||||||
get_value_style, StringResult, TableOutput, TableResult, INDEX_COLUMN_NAME,
|
get_value_style, NuText, INDEX_COLUMN_NAME,
|
||||||
|
},
|
||||||
|
NuTable, NuTableCell, StringResult, TableOpts, TableOutput, TableResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct JustTable;
|
pub struct JustTable;
|
||||||
|
|
||||||
impl JustTable {
|
impl JustTable {
|
||||||
pub fn table(input: &[Value], row_offset: usize, opts: BuildConfig<'_>) -> StringResult {
|
pub fn table(input: &[Value], opts: TableOpts<'_>) -> StringResult {
|
||||||
let out = match table(input, row_offset, opts.clone())? {
|
create_table(input, opts)
|
||||||
Some(out) => out,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let table_config = create_table_config(opts.config, opts.style_computer, &out);
|
|
||||||
let table = out.table.draw(table_config, opts.term_width);
|
|
||||||
|
|
||||||
Ok(table)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kv_table(cols: &[String], vals: &[Value], opts: BuildConfig<'_>) -> StringResult {
|
pub fn kv_table(cols: &[String], vals: &[Value], opts: TableOpts<'_>) -> StringResult {
|
||||||
kv_table(cols, vals, opts)
|
kv_table(cols, vals, opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
|
||||||
pub struct BuildConfig<'a> {
|
match table(input, opts.row_offset, opts.clone())? {
|
||||||
pub(crate) ctrlc: Option<Arc<AtomicBool>>,
|
Some(out) => {
|
||||||
pub(crate) config: &'a Config,
|
let table_config =
|
||||||
pub(crate) style_computer: &'a StyleComputer<'a>,
|
create_nu_table_config(opts.config, opts.style_computer, &out, false);
|
||||||
pub(crate) span: Span,
|
Ok(out.table.draw(table_config, opts.width))
|
||||||
pub(crate) term_width: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> BuildConfig<'a> {
|
|
||||||
pub fn new(
|
|
||||||
ctrlc: Option<Arc<AtomicBool>>,
|
|
||||||
config: &'a Config,
|
|
||||||
style_computer: &'a StyleComputer<'a>,
|
|
||||||
span: Span,
|
|
||||||
term_width: usize,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
ctrlc,
|
|
||||||
config,
|
|
||||||
style_computer,
|
|
||||||
span,
|
|
||||||
term_width,
|
|
||||||
}
|
}
|
||||||
}
|
None => Ok(None),
|
||||||
|
|
||||||
pub fn span(&self) -> Span {
|
|
||||||
self.span
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn term_width(&self) -> usize {
|
|
||||||
self.term_width
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn config(&self) -> &Config {
|
|
||||||
self.config
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn style_computer(&self) -> &StyleComputer {
|
|
||||||
self.style_computer
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ctrlc(&self) -> Option<&Arc<AtomicBool>> {
|
|
||||||
self.ctrlc.as_ref()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kv_table(cols: &[String], vals: &[Value], opts: BuildConfig<'_>) -> StringResult {
|
fn kv_table(cols: &[String], vals: &[Value], opts: TableOpts<'_>) -> StringResult {
|
||||||
let mut data = vec![Vec::with_capacity(2); cols.len()];
|
let mut data = vec![Vec::with_capacity(2); cols.len()];
|
||||||
for ((column, value), row) in cols.iter().zip(vals.iter()).zip(data.iter_mut()) {
|
for ((column, value), row) in cols.iter().zip(vals.iter()).zip(data.iter_mut()) {
|
||||||
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
|
if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) {
|
||||||
@ -91,8 +47,8 @@ fn kv_table(cols: &[String], vals: &[Value], opts: BuildConfig<'_>) -> StringRes
|
|||||||
value = clean_charset(&value);
|
value = clean_charset(&value);
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = Cell::new(column.to_string());
|
let key = NuTableCell::new(column.to_string());
|
||||||
let value = Cell::new(value);
|
let value = NuTableCell::new(value);
|
||||||
row.push(key);
|
row.push(key);
|
||||||
row.push(value);
|
row.push(value);
|
||||||
}
|
}
|
||||||
@ -101,13 +57,13 @@ fn kv_table(cols: &[String], vals: &[Value], opts: BuildConfig<'_>) -> StringRes
|
|||||||
table.set_index_style(TextStyle::default_field());
|
table.set_index_style(TextStyle::default_field());
|
||||||
|
|
||||||
let out = TableOutput::new(table, false, true);
|
let out = TableOutput::new(table, false, true);
|
||||||
let table_config = create_table_config(opts.config, opts.style_computer, &out);
|
let table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false);
|
||||||
let table = out.table.draw(table_config, opts.term_width);
|
let table = out.table.draw(table_config, opts.width);
|
||||||
|
|
||||||
Ok(table)
|
Ok(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table(input: &[Value], row_offset: usize, opts: BuildConfig<'_>) -> TableResult {
|
fn table(input: &[Value], row_offset: usize, opts: TableOpts<'_>) -> TableResult {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@ -148,7 +104,7 @@ fn to_table_with_header(
|
|||||||
headers: Vec<String>,
|
headers: Vec<String>,
|
||||||
with_index: bool,
|
with_index: bool,
|
||||||
row_offset: usize,
|
row_offset: usize,
|
||||||
opts: BuildConfig<'_>,
|
opts: TableOpts<'_>,
|
||||||
) -> Result<Option<NuTable>, ShellError> {
|
) -> Result<Option<NuTable>, ShellError> {
|
||||||
let count_rows = input.len() + 1;
|
let count_rows = input.len() + 1;
|
||||||
let count_columns = headers.len();
|
let count_columns = headers.len();
|
||||||
@ -179,7 +135,7 @@ fn to_table_with_header(
|
|||||||
let (text, style) = get_string_value_with_header(item, header, &opts);
|
let (text, style) = get_string_value_with_header(item, header, &opts);
|
||||||
|
|
||||||
table.insert((row + 1, col), text);
|
table.insert((row + 1, col), text);
|
||||||
table.set_cell_style((row + 1, col), style);
|
table.insert_style((row + 1, col), style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +146,7 @@ fn to_table_with_no_header(
|
|||||||
input: &[Value],
|
input: &[Value],
|
||||||
with_index: bool,
|
with_index: bool,
|
||||||
row_offset: usize,
|
row_offset: usize,
|
||||||
opts: BuildConfig<'_>,
|
opts: TableOpts<'_>,
|
||||||
) -> Result<Option<NuTable>, ShellError> {
|
) -> Result<Option<NuTable>, ShellError> {
|
||||||
let mut table = NuTable::new(input.len(), with_index as usize + 1);
|
let mut table = NuTable::new(input.len(), with_index as usize + 1);
|
||||||
table.set_index_style(get_index_style(opts.style_computer));
|
table.set_index_style(get_index_style(opts.style_computer));
|
||||||
@ -213,13 +169,13 @@ fn to_table_with_no_header(
|
|||||||
|
|
||||||
let pos = (row, with_index as usize);
|
let pos = (row, with_index as usize);
|
||||||
table.insert(pos, text);
|
table.insert(pos, text);
|
||||||
table.set_cell_style(pos, style);
|
table.insert_style(pos, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(table))
|
Ok(Some(table))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_string_value_with_header(item: &Value, header: &str, opts: &BuildConfig) -> NuText {
|
fn get_string_value_with_header(item: &Value, header: &str, opts: &TableOpts) -> NuText {
|
||||||
match item {
|
match item {
|
||||||
Value::Record { .. } => {
|
Value::Record { .. } => {
|
||||||
let path = PathMember::String {
|
let path = PathMember::String {
|
||||||
@ -238,7 +194,7 @@ fn get_string_value_with_header(item: &Value, header: &str, opts: &BuildConfig)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_string_value(item: &Value, opts: &BuildConfig) -> NuText {
|
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_value = matches!(item, Value::String { .. });
|
let is_string_value = matches!(item, Value::String { .. });
|
||||||
if is_string_value {
|
if is_string_value {
|
||||||
|
@ -2,20 +2,15 @@ mod collapse;
|
|||||||
mod expanded;
|
mod expanded;
|
||||||
mod general;
|
mod general;
|
||||||
|
|
||||||
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use nu_protocol::TrimStrategy;
|
|
||||||
use nu_protocol::{Config, FooterMode, ShellError, Span, Value};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::{string_wrap, NuTable, TableConfig, TableTheme};
|
|
||||||
|
|
||||||
pub use collapse::CollapsedTable;
|
pub use collapse::CollapsedTable;
|
||||||
pub use expanded::ExpandedTable;
|
pub use expanded::ExpandedTable;
|
||||||
pub use general::{BuildConfig, JustTable};
|
pub use general::JustTable;
|
||||||
|
use nu_color_config::StyleComputer;
|
||||||
|
use nu_protocol::{Config, Span};
|
||||||
|
|
||||||
pub type NuText = (String, TextStyle);
|
use crate::NuTable;
|
||||||
pub type TableResult = Result<Option<TableOutput>, ShellError>;
|
|
||||||
pub type StringResult = Result<Option<String>, ShellError>;
|
|
||||||
|
|
||||||
pub struct TableOutput {
|
pub struct TableOutput {
|
||||||
pub table: NuTable,
|
pub table: NuTable,
|
||||||
@ -33,176 +28,32 @@ impl TableOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value_to_styled_string(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
|
#[derive(Debug, Clone)]
|
||||||
let float_precision = cfg.float_precision as usize;
|
pub struct TableOpts<'a> {
|
||||||
let text = val.into_abbreviated_string(cfg);
|
ctrlc: Option<Arc<AtomicBool>>,
|
||||||
make_styled_string(style, text, Some(val), float_precision)
|
config: &'a Config,
|
||||||
|
style_computer: &'a StyleComputer<'a>,
|
||||||
|
span: Span,
|
||||||
|
row_offset: usize,
|
||||||
|
width: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value_to_clean_styled_string(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
|
impl<'a> TableOpts<'a> {
|
||||||
let (text, style) = value_to_styled_string(val, cfg, style);
|
pub fn new(
|
||||||
let text = clean_charset(&text);
|
config: &'a Config,
|
||||||
(text, style)
|
style_computer: &'a StyleComputer<'a>,
|
||||||
}
|
ctrlc: Option<Arc<AtomicBool>>,
|
||||||
|
span: Span,
|
||||||
pub fn clean_charset(text: &str) -> String {
|
row_offset: usize,
|
||||||
// todo: optimize, I bet it can be done in 1 path
|
available_width: usize,
|
||||||
text.replace('\t', " ").replace('\r', "")
|
) -> Self {
|
||||||
}
|
Self {
|
||||||
|
ctrlc,
|
||||||
const INDEX_COLUMN_NAME: &str = "index";
|
config,
|
||||||
|
style_computer,
|
||||||
fn error_sign(style_computer: &StyleComputer) -> (String, TextStyle) {
|
span,
|
||||||
make_styled_string(style_computer, String::from("❎"), None, 0)
|
row_offset,
|
||||||
}
|
width: available_width,
|
||||||
|
|
||||||
fn wrap_text(text: &str, width: usize, config: &Config) -> String {
|
|
||||||
string_wrap(text, width, is_cfg_trim_keep_words(config))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_styled_string(
|
|
||||||
style_computer: &StyleComputer,
|
|
||||||
text: String,
|
|
||||||
value: Option<&Value>, // None represents table holes.
|
|
||||||
float_precision: usize,
|
|
||||||
) -> NuText {
|
|
||||||
match value {
|
|
||||||
Some(value) => {
|
|
||||||
match value {
|
|
||||||
Value::Float { .. } => {
|
|
||||||
// set dynamic precision from config
|
|
||||||
let precise_number = match convert_with_precision(&text, float_precision) {
|
|
||||||
Ok(num) => num,
|
|
||||||
Err(e) => e.to_string(),
|
|
||||||
};
|
|
||||||
(precise_number, style_computer.style_primitive(value))
|
|
||||||
}
|
|
||||||
_ => (text, style_computer.style_primitive(value)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Though holes are not the same as null, the closure for "empty" is passed a null anyway.
|
|
||||||
(
|
|
||||||
text,
|
|
||||||
TextStyle::with_style(
|
|
||||||
Alignment::Center,
|
|
||||||
style_computer.compute("empty", &Value::nothing(Span::unknown())),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellError> {
|
|
||||||
// vall will always be a f64 so convert it with precision formatting
|
|
||||||
let val_float = match val.trim().parse::<f64>() {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(ShellError::GenericError(
|
|
||||||
format!("error converting string [{}] to f64", &val),
|
|
||||||
"".to_string(),
|
|
||||||
None,
|
|
||||||
Some(e.to_string()),
|
|
||||||
Vec::new(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(format!("{val_float:.precision$}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_cfg_trim_keep_words(config: &Config) -> bool {
|
|
||||||
matches!(
|
|
||||||
config.trim_strategy,
|
|
||||||
TrimStrategy::Wrap {
|
|
||||||
try_to_keep_words: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_theme_from_config(config: &Config) -> TableTheme {
|
|
||||||
match config.table_mode.as_str() {
|
|
||||||
"basic" => TableTheme::basic(),
|
|
||||||
"thin" => TableTheme::thin(),
|
|
||||||
"light" => TableTheme::light(),
|
|
||||||
"compact" => TableTheme::compact(),
|
|
||||||
"with_love" => TableTheme::with_love(),
|
|
||||||
"compact_double" => TableTheme::compact_double(),
|
|
||||||
"rounded" => TableTheme::rounded(),
|
|
||||||
"reinforced" => TableTheme::reinforced(),
|
|
||||||
"heavy" => TableTheme::heavy(),
|
|
||||||
"none" => TableTheme::none(),
|
|
||||||
_ => TableTheme::rounded(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_table_config(config: &Config, comp: &StyleComputer, out: &TableOutput) -> TableConfig {
|
|
||||||
let theme = load_theme_from_config(config);
|
|
||||||
let footer = with_footer(config, out.with_header, out.table.count_rows());
|
|
||||||
let line_style = lookup_separator_color(comp);
|
|
||||||
let trim = config.trim_strategy.clone();
|
|
||||||
|
|
||||||
TableConfig::new()
|
|
||||||
.theme(theme)
|
|
||||||
.with_footer(footer)
|
|
||||||
.with_header(out.with_header)
|
|
||||||
.with_index(out.with_index)
|
|
||||||
.line_style(line_style)
|
|
||||||
.trim(trim)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup_separator_color(style_computer: &StyleComputer) -> nu_ansi_term::Style {
|
|
||||||
style_computer.compute("separator", &Value::nothing(Span::unknown()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_footer(config: &Config, with_header: bool, count_records: usize) -> bool {
|
|
||||||
with_header && need_footer(config, count_records as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn need_footer(config: &Config, count_records: u64) -> bool {
|
|
||||||
matches!(config.footer_mode, FooterMode::RowCount(limit) if count_records > limit)
|
|
||||||
|| matches!(config.footer_mode, FooterMode::Always)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_data_styles(table: &mut NuTable, styles: HashMap<(usize, usize), TextStyle>) {
|
|
||||||
for (pos, style) in styles {
|
|
||||||
table.set_cell_style(pos, style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_header_style(style_computer: &StyleComputer) -> TextStyle {
|
|
||||||
TextStyle::with_style(
|
|
||||||
Alignment::Center,
|
|
||||||
style_computer.compute("header", &Value::string("", Span::unknown())),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_index_style(style_computer: &StyleComputer) -> TextStyle {
|
|
||||||
TextStyle::with_style(
|
|
||||||
Alignment::Right,
|
|
||||||
style_computer.compute("row_index", &Value::string("", Span::unknown())),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_value_style(value: &Value, config: &Config, style_computer: &StyleComputer) -> NuText {
|
|
||||||
match value {
|
|
||||||
// Float precision is required here.
|
|
||||||
Value::Float { val, .. } => (
|
|
||||||
format!("{:.prec$}", val, prec = config.float_precision as usize),
|
|
||||||
style_computer.style_primitive(value),
|
|
||||||
),
|
|
||||||
_ => (
|
|
||||||
value.into_abbreviated_string(config),
|
|
||||||
style_computer.style_primitive(value),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_empty_style(style_computer: &StyleComputer) -> NuText {
|
|
||||||
(
|
|
||||||
String::from("❎"),
|
|
||||||
TextStyle::with_style(
|
|
||||||
Alignment::Right,
|
|
||||||
style_computer.compute("empty", &Value::nothing(Span::unknown())),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -42,3 +42,8 @@ pub fn string_truncate(text: &str, width: usize) -> String {
|
|||||||
|
|
||||||
Truncate::truncate_text(line, width).into_owned()
|
Truncate::truncate_text(line, width).into_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clean_charset(text: &str) -> String {
|
||||||
|
// todo: optimize, I bet it can be done in 1 path
|
||||||
|
text.replace('\t', " ").replace('\r', "")
|
||||||
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use nu_table::{string_width, NuTable, TableConfig};
|
use nu_table::{string_width, NuTable, NuTableConfig};
|
||||||
use tabled::grid::records::vec_records::CellInfo;
|
use tabled::grid::records::vec_records::CellInfo;
|
||||||
|
|
||||||
pub struct TestCase {
|
pub struct TestCase {
|
||||||
cfg: TableConfig,
|
cfg: NuTableConfig,
|
||||||
termwidth: usize,
|
termwidth: usize,
|
||||||
expected: Option<String>,
|
expected: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestCase {
|
impl TestCase {
|
||||||
pub fn new(cfg: TableConfig, termwidth: usize, expected: Option<String>) -> Self {
|
pub fn new(cfg: NuTableConfig, termwidth: usize, expected: Option<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cfg,
|
cfg,
|
||||||
termwidth,
|
termwidth,
|
||||||
@ -37,7 +37,7 @@ pub fn test_table<I: IntoIterator<Item = TestCase>>(data: Data, tests: I) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_table(data: Data, config: TableConfig, termwidth: usize) -> Option<String> {
|
pub fn create_table(data: Data, config: NuTableConfig, termwidth: usize) -> Option<String> {
|
||||||
let table = NuTable::from(data);
|
let table = NuTable::from(data);
|
||||||
table.draw(config, termwidth)
|
table.draw(config, termwidth)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use nu_protocol::TrimStrategy;
|
use nu_protocol::TrimStrategy;
|
||||||
use nu_table::{NuTable, TableConfig, TableTheme as theme};
|
use nu_table::{NuTable, NuTableConfig, TableTheme as theme};
|
||||||
|
|
||||||
use common::{create_row, test_table, TestCase};
|
use common::{create_row, test_table, TestCase};
|
||||||
use tabled::grid::records::vec_records::CellInfo;
|
use tabled::grid::records::vec_records::CellInfo;
|
||||||
@ -9,11 +9,13 @@ use tabled::grid::records::vec_records::CellInfo;
|
|||||||
#[test]
|
#[test]
|
||||||
fn data_and_header_has_different_size_doesnt_work() {
|
fn data_and_header_has_different_size_doesnt_work() {
|
||||||
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
|
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
|
||||||
|
let cfg = NuTableConfig {
|
||||||
|
theme: theme::heavy(),
|
||||||
|
with_header: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let table = table.draw(
|
let table = table.draw(cfg.clone(), usize::MAX);
|
||||||
TableConfig::new().theme(theme::heavy()).with_header(true),
|
|
||||||
usize::MAX,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
table.as_deref(),
|
table.as_deref(),
|
||||||
@ -29,10 +31,7 @@ fn data_and_header_has_different_size_doesnt_work() {
|
|||||||
|
|
||||||
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
|
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
|
||||||
|
|
||||||
let table = table.draw(
|
let table = table.draw(cfg, usize::MAX);
|
||||||
TableConfig::new().theme(theme::heavy()).with_header(true),
|
|
||||||
usize::MAX,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
table.as_deref(),
|
table.as_deref(),
|
||||||
@ -49,7 +48,7 @@ fn data_and_header_has_different_size_doesnt_work() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn termwidth_too_small() {
|
fn termwidth_too_small() {
|
||||||
let test_loop = |config: TableConfig| {
|
let test_loop = |config: NuTableConfig| {
|
||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
|
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
|
||||||
let table = table.draw(config.clone(), i);
|
let table = table.draw(config.clone(), i);
|
||||||
@ -58,29 +57,22 @@ fn termwidth_too_small() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let base_config = TableConfig::new().theme(theme::heavy()).with_header(true);
|
let mut cfg = NuTableConfig {
|
||||||
|
theme: theme::heavy(),
|
||||||
|
with_header: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let config = base_config.clone();
|
for case in [
|
||||||
test_loop(config);
|
TrimStrategy::truncate(None),
|
||||||
|
TrimStrategy::truncate(Some(String::from("**"))),
|
||||||
let config = base_config.clone().trim(TrimStrategy::truncate(None));
|
TrimStrategy::truncate(Some(String::from(""))),
|
||||||
test_loop(config);
|
TrimStrategy::wrap(false),
|
||||||
|
TrimStrategy::wrap(true),
|
||||||
let config = base_config
|
] {
|
||||||
.clone()
|
cfg.trim = case;
|
||||||
.trim(TrimStrategy::truncate(Some(String::from("**"))));
|
test_loop(cfg.clone());
|
||||||
test_loop(config);
|
}
|
||||||
|
|
||||||
let config = base_config
|
|
||||||
.clone()
|
|
||||||
.trim(TrimStrategy::truncate(Some(String::from(""))));
|
|
||||||
test_loop(config);
|
|
||||||
|
|
||||||
let config = base_config.clone().trim(TrimStrategy::wrap(false));
|
|
||||||
test_loop(config);
|
|
||||||
|
|
||||||
let config = base_config.trim(TrimStrategy::wrap(true));
|
|
||||||
test_loop(config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -204,11 +196,12 @@ fn width_control_test_0() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_width(data: Vec<Vec<CellInfo<String>>>, tests: &[(usize, &str)]) {
|
fn test_width(data: Vec<Vec<CellInfo<String>>>, tests: &[(usize, &str)]) {
|
||||||
let trim = TrimStrategy::truncate(Some(String::from("...")));
|
let config = NuTableConfig {
|
||||||
let config = TableConfig::new()
|
theme: theme::heavy(),
|
||||||
.theme(theme::heavy())
|
trim: TrimStrategy::truncate(Some(String::from("..."))),
|
||||||
.with_header(true)
|
with_header: true,
|
||||||
.trim(trim);
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let tests = tests.iter().map(|&(termwidth, expected)| {
|
let tests = tests.iter().map(|&(termwidth, expected)| {
|
||||||
TestCase::new(config.clone(), termwidth, Some(expected.to_owned()))
|
TestCase::new(config.clone(), termwidth, Some(expected.to_owned()))
|
||||||
@ -218,10 +211,13 @@ fn test_width(data: Vec<Vec<CellInfo<String>>>, tests: &[(usize, &str)]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_trim(tests: &[(usize, Option<&str>)], trim: TrimStrategy) {
|
fn test_trim(tests: &[(usize, Option<&str>)], trim: TrimStrategy) {
|
||||||
let config = TableConfig::new()
|
let config = NuTableConfig {
|
||||||
.theme(theme::heavy())
|
theme: theme::heavy(),
|
||||||
.with_header(true)
|
with_header: true,
|
||||||
.trim(trim);
|
trim,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let tests = tests.iter().map(|&(termwidth, expected)| {
|
let tests = tests.iter().map(|&(termwidth, expected)| {
|
||||||
TestCase::new(config.clone(), termwidth, expected.map(|s| s.to_string()))
|
TestCase::new(config.clone(), termwidth, expected.map(|s| s.to_string()))
|
||||||
});
|
});
|
||||||
|
@ -2,16 +2,18 @@ mod common;
|
|||||||
|
|
||||||
use common::{create_row, create_table};
|
use common::{create_row, create_table};
|
||||||
|
|
||||||
use nu_table::{TableConfig, TableTheme as theme};
|
use nu_table::{NuTableConfig, TableTheme as theme};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_expand() {
|
fn test_expand() {
|
||||||
let table = create_table(
|
let table = create_table(
|
||||||
vec![create_row(4); 3],
|
vec![create_row(4); 3],
|
||||||
TableConfig::new()
|
NuTableConfig {
|
||||||
.theme(theme::rounded())
|
theme: theme::rounded(),
|
||||||
.with_header(true)
|
with_header: true,
|
||||||
.expand(true),
|
expand: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
50,
|
50,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use common::create_row as row;
|
use common::create_row as row;
|
||||||
use nu_table::{NuTable, TableConfig, TableTheme as theme};
|
use nu_table::{NuTable, NuTableConfig, TableTheme as theme};
|
||||||
use tabled::grid::records::vec_records::CellInfo;
|
use tabled::grid::records::vec_records::CellInfo;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -476,10 +476,11 @@ fn test_with_love() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_table(data: Vec<Vec<CellInfo<String>>>, with_header: bool, theme: theme) -> String {
|
fn create_table(data: Vec<Vec<CellInfo<String>>>, with_header: bool, theme: theme) -> String {
|
||||||
let mut config = TableConfig::new().theme(theme);
|
let config = NuTableConfig {
|
||||||
if with_header {
|
theme,
|
||||||
config = config.with_header(true);
|
with_header,
|
||||||
}
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let out = common::create_table(data, config, usize::MAX);
|
let out = common::create_table(data, config, usize::MAX);
|
||||||
|
|
||||||
@ -491,10 +492,11 @@ fn create_table_with_size(
|
|||||||
with_header: bool,
|
with_header: bool,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut config = TableConfig::new().theme(theme);
|
let config = NuTableConfig {
|
||||||
if with_header {
|
theme,
|
||||||
config = config.with_header(true);
|
with_header,
|
||||||
}
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let table = NuTable::from(data);
|
let table = NuTable::from(data);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user