mirror of
https://github.com/nushell/nushell.git
synced 2025-06-20 09:58:15 +02:00
Bump tabled to 0.17 (#14415)
With this comes a new `unicode-width` as I remember there was some issue
with `ratatui`.
And a bit of refactorings which are ment to reduce code lines while not
breaking anything.
Not yet complete, I think I'll try to improve some more places,
just wanted to trigger CI 😄
And yessssssssss we have a new `unicode-width` but I sort of doubtful,
I mean the original issue with emojie.
I think it may require an additional "clean" call.
I am just saying I was not testing it with that case of complex emojies.
---------
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
This commit is contained in:
parent
5314b31b12
commit
4401924128
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -4680,15 +4680,15 @@ checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "papergrid"
|
name = "papergrid"
|
||||||
version = "0.12.0"
|
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 = "c7419ad52a7de9b60d33e11085a0fe3df1fbd5926aa3f93d3dd53afbc9e86725"
|
checksum = "d2b0f8def1f117e13c895f3eda65a7b5650688da29d6ad04635f61bc7b92eebd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-str",
|
"ansi-str",
|
||||||
"ansitok",
|
"ansitok",
|
||||||
"bytecount",
|
"bytecount",
|
||||||
"fnv",
|
"fnv",
|
||||||
"unicode-width 0.1.11",
|
"unicode-width 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -7233,9 +7233,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tabled"
|
name = "tabled"
|
||||||
version = "0.16.0"
|
version = "0.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77c9303ee60b9bedf722012ea29ae3711ba13a67c9b9ae28993838b63057cb1b"
|
checksum = "c6709222f3973137427ce50559cd564dc187a95b9cfe01613d2f4e93610e510a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-str",
|
"ansi-str",
|
||||||
"ansitok",
|
"ansitok",
|
||||||
|
@ -156,7 +156,7 @@ sha2 = "0.10"
|
|||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
syn = "2.0"
|
syn = "2.0"
|
||||||
sysinfo = "0.32"
|
sysinfo = "0.32"
|
||||||
tabled = { version = "0.16.0", default-features = false }
|
tabled = { version = "0.17.0", default-features = false }
|
||||||
tempfile = "3.14"
|
tempfile = "3.14"
|
||||||
terminal_size = "0.4"
|
terminal_size = "0.4"
|
||||||
titlecase = "3.0"
|
titlecase = "3.0"
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
use crate::debug::inspect_table::{
|
// note: Seems like could be simplified
|
||||||
global_horizontal_char::SetHorizontalChar, set_widths::SetWidths,
|
// IMHO: it shall not take 300+ lines :)
|
||||||
};
|
|
||||||
use nu_protocol::Value;
|
use nu_protocol::Value;
|
||||||
use nu_table::{string_width, string_wrap};
|
use nu_table::{string_width, string_wrap};
|
||||||
|
|
||||||
use tabled::{
|
use tabled::{
|
||||||
grid::config::ColoredConfig,
|
grid::config::ColoredConfig,
|
||||||
settings::{peaker::PriorityMax, width::Wrap, Settings, Style},
|
settings::{peaker::Priority, width::Wrap, Settings, Style},
|
||||||
Table,
|
Table,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use self::{global_horizontal_char::SetHorizontalChar, set_widths::SetWidths};
|
||||||
|
|
||||||
pub fn build_table(value: Value, description: String, termsize: usize) -> String {
|
pub fn build_table(value: Value, description: String, termsize: usize) -> String {
|
||||||
let (head, mut data) = util::collect_input(value);
|
let (head, mut data) = util::collect_input(value);
|
||||||
let count_columns = head.len();
|
let count_columns = head.len();
|
||||||
@ -57,7 +60,7 @@ pub fn build_table(value: Value, description: String, termsize: usize) -> String
|
|||||||
Settings::default()
|
Settings::default()
|
||||||
.with(Style::rounded().corner_top_left('├').corner_top_right('┤'))
|
.with(Style::rounded().corner_top_left('├').corner_top_right('┤'))
|
||||||
.with(SetWidths(widths))
|
.with(SetWidths(widths))
|
||||||
.with(Wrap::new(width).priority(PriorityMax))
|
.with(Wrap::new(width).priority(Priority::max(true)))
|
||||||
.with(SetHorizontalChar::new('┼', '┴', 11 + 2 + 1)),
|
.with(SetHorizontalChar::new('┼', '┴', 11 + 2 + 1)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2,7 +2,12 @@
|
|||||||
// overall reduce the redundant calls to StyleComputer etc.
|
// overall reduce the redundant calls to StyleComputer etc.
|
||||||
// the goal is to configure it once...
|
// the goal is to configure it once...
|
||||||
|
|
||||||
|
use std::{collections::VecDeque, io::IsTerminal, io::Read, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use lscolors::{LsColors, Style};
|
use lscolors::{LsColors, Style};
|
||||||
|
use url::Url;
|
||||||
|
use web_time::Instant;
|
||||||
|
|
||||||
use nu_color_config::{color_from_hex, StyleComputer, TextStyle};
|
use nu_color_config::{color_from_hex, StyleComputer, TextStyle};
|
||||||
use nu_engine::{command_prelude::*, env_to_string};
|
use nu_engine::{command_prelude::*, env_to_string};
|
||||||
use nu_path::form::Absolute;
|
use nu_path::form::Absolute;
|
||||||
@ -11,25 +16,16 @@ use nu_protocol::{
|
|||||||
ByteStream, Config, DataSource, ListStream, PipelineMetadata, Signals, TableMode, ValueIterator,
|
ByteStream, Config, DataSource, ListStream, PipelineMetadata, Signals, TableMode, ValueIterator,
|
||||||
};
|
};
|
||||||
use nu_table::{
|
use nu_table::{
|
||||||
common::create_nu_table_config, CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell,
|
common::configure_table, CollapsedTable, ExpandedTable, JustTable, NuRecordsValue, NuTable,
|
||||||
StringResult, TableOpts, TableOutput,
|
StringResult, TableOpts, TableOutput,
|
||||||
};
|
};
|
||||||
use nu_utils::{get_ls_colors, terminal_size};
|
use nu_utils::{get_ls_colors, terminal_size};
|
||||||
use std::{collections::VecDeque, io::Read, path::PathBuf, str::FromStr};
|
|
||||||
use url::Url;
|
type ShellResult<T> = Result<T, ShellError>;
|
||||||
use web_time::Instant;
|
type NuPathBuf = nu_path::PathBuf<Absolute>;
|
||||||
|
|
||||||
const STREAM_PAGE_SIZE: usize = 1000;
|
const STREAM_PAGE_SIZE: usize = 1000;
|
||||||
|
const DEFAULT_TABLE_WIDTH: usize = 80;
|
||||||
fn get_width_param(width_param: Option<i64>) -> usize {
|
|
||||||
if let Some(col) = width_param {
|
|
||||||
col as usize
|
|
||||||
} else if let Ok((w, _h)) = terminal_size() {
|
|
||||||
w as usize
|
|
||||||
} else {
|
|
||||||
80
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Table;
|
pub struct Table;
|
||||||
@ -113,17 +109,15 @@ impl Command for Table {
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> ShellResult<PipelineData> {
|
||||||
let list_themes: bool = call.has_flag(engine_state, stack, "list")?;
|
let list_themes: bool = call.has_flag(engine_state, stack, "list")?;
|
||||||
// if list argument is present we just need to return a list of supported table themes
|
// if list argument is present we just need to return a list of supported table themes
|
||||||
if list_themes {
|
if list_themes {
|
||||||
let val = Value::list(supported_table_modes(), Span::test_data());
|
let val = Value::list(supported_table_modes(), Span::test_data());
|
||||||
return Ok(val.into_pipeline_data());
|
return Ok(val.into_pipeline_data());
|
||||||
}
|
}
|
||||||
#[cfg(feature = "os")]
|
|
||||||
let cwd = engine_state.cwd(Some(stack))?;
|
let input = CmdInput::parse(engine_state, stack, call, input)?;
|
||||||
let cfg = parse_table_config(call, engine_state, stack)?;
|
|
||||||
let input = CmdInput::new(engine_state, stack, call, input);
|
|
||||||
|
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -131,12 +125,7 @@ impl Command for Table {
|
|||||||
let _ = nu_utils::enable_vt_processing();
|
let _ = nu_utils::enable_vt_processing();
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_table_command(
|
handle_table_command(input)
|
||||||
input,
|
|
||||||
cfg,
|
|
||||||
#[cfg(feature = "os")]
|
|
||||||
cwd,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -221,80 +210,127 @@ impl Command for Table {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct TableConfig {
|
struct TableConfig {
|
||||||
index: Option<usize>,
|
view: TableView,
|
||||||
table_view: TableView,
|
width: usize,
|
||||||
term_width: usize,
|
|
||||||
theme: TableMode,
|
theme: TableMode,
|
||||||
abbreviation: Option<usize>,
|
abbreviation: Option<usize>,
|
||||||
|
index: Option<usize>,
|
||||||
use_ansi_coloring: bool,
|
use_ansi_coloring: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableConfig {
|
impl TableConfig {
|
||||||
fn new(
|
fn new(
|
||||||
table_view: TableView,
|
view: TableView,
|
||||||
term_width: usize,
|
width: usize,
|
||||||
theme: TableMode,
|
theme: TableMode,
|
||||||
abbreviation: Option<usize>,
|
abbreviation: Option<usize>,
|
||||||
index: Option<usize>,
|
index: Option<usize>,
|
||||||
use_ansi_coloring: bool,
|
use_ansi_coloring: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
index,
|
view,
|
||||||
table_view,
|
width,
|
||||||
term_width,
|
|
||||||
abbreviation,
|
|
||||||
theme,
|
theme,
|
||||||
|
abbreviation,
|
||||||
|
index,
|
||||||
use_ansi_coloring,
|
use_ansi_coloring,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum TableView {
|
||||||
|
General,
|
||||||
|
Collapsed,
|
||||||
|
Expanded {
|
||||||
|
limit: Option<usize>,
|
||||||
|
flatten: bool,
|
||||||
|
flatten_separator: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CLIArgs {
|
||||||
|
width: Option<i64>,
|
||||||
|
abbrivation: Option<usize>,
|
||||||
|
theme: TableMode,
|
||||||
|
expand: bool,
|
||||||
|
expand_limit: Option<usize>,
|
||||||
|
expand_flatten: bool,
|
||||||
|
expand_flatten_separator: Option<String>,
|
||||||
|
collapse: bool,
|
||||||
|
index: Option<usize>,
|
||||||
|
use_ansi_coloring: bool,
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_table_config(
|
fn parse_table_config(
|
||||||
call: &Call,
|
call: &Call,
|
||||||
state: &EngineState,
|
state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) -> Result<TableConfig, ShellError> {
|
) -> ShellResult<TableConfig> {
|
||||||
let width_param: Option<i64> = call.get_flag(state, stack, "width")?;
|
let args = get_cli_args(call, state, stack)?;
|
||||||
let expand: bool = call.has_flag(state, stack, "expand")?;
|
let table_view = get_table_view(&args);
|
||||||
let expand_limit: Option<usize> = call.get_flag(state, stack, "expand-deep")?;
|
let term_width = get_table_width(args.width);
|
||||||
let collapse: bool = call.has_flag(state, stack, "collapse")?;
|
|
||||||
let flatten: bool = call.has_flag(state, stack, "flatten")?;
|
let cfg = TableConfig::new(
|
||||||
let flatten_separator: Option<String> = call.get_flag(state, stack, "flatten-separator")?;
|
table_view,
|
||||||
let abbrivation: Option<usize> = call
|
term_width,
|
||||||
.get_flag(state, stack, "abbreviated")?
|
args.theme,
|
||||||
.or_else(|| stack.get_config(state).table.abbreviated_row_count);
|
args.abbrivation,
|
||||||
let table_view = match (expand, collapse) {
|
args.index,
|
||||||
|
args.use_ansi_coloring,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_table_view(args: &CLIArgs) -> TableView {
|
||||||
|
match (args.expand, args.collapse) {
|
||||||
(false, false) => TableView::General,
|
(false, false) => TableView::General,
|
||||||
(_, true) => TableView::Collapsed,
|
(_, true) => TableView::Collapsed,
|
||||||
(true, _) => TableView::Expanded {
|
(true, _) => TableView::Expanded {
|
||||||
limit: expand_limit,
|
limit: args.expand_limit,
|
||||||
flatten,
|
flatten: args.expand_flatten,
|
||||||
flatten_separator,
|
flatten_separator: args.expand_flatten_separator.clone(),
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cli_args(call: &Call<'_>, state: &EngineState, stack: &mut Stack) -> ShellResult<CLIArgs> {
|
||||||
|
let width: Option<i64> = call.get_flag(state, stack, "width")?;
|
||||||
|
let expand: bool = call.has_flag(state, stack, "expand")?;
|
||||||
|
let expand_limit: Option<usize> = call.get_flag(state, stack, "expand-deep")?;
|
||||||
|
let expand_flatten: bool = call.has_flag(state, stack, "flatten")?;
|
||||||
|
let expand_flatten_separator: Option<String> =
|
||||||
|
call.get_flag(state, stack, "flatten-separator")?;
|
||||||
|
let collapse: bool = call.has_flag(state, stack, "collapse")?;
|
||||||
|
let abbrivation: Option<usize> = call
|
||||||
|
.get_flag(state, stack, "abbreviated")?
|
||||||
|
.or_else(|| stack.get_config(state).table.abbreviated_row_count);
|
||||||
let theme =
|
let theme =
|
||||||
get_theme_flag(call, state, stack)?.unwrap_or_else(|| stack.get_config(state).table.mode);
|
get_theme_flag(call, state, stack)?.unwrap_or_else(|| stack.get_config(state).table.mode);
|
||||||
let index = get_index_flag(call, state, stack)?;
|
let index = get_index_flag(call, state, stack)?;
|
||||||
|
|
||||||
let term_width = get_width_param(width_param);
|
|
||||||
|
|
||||||
let use_ansi_coloring = state.get_config().use_ansi_coloring.get(state);
|
let use_ansi_coloring = state.get_config().use_ansi_coloring.get(state);
|
||||||
|
|
||||||
Ok(TableConfig::new(
|
Ok(CLIArgs {
|
||||||
table_view,
|
|
||||||
term_width,
|
|
||||||
theme,
|
theme,
|
||||||
abbrivation,
|
abbrivation,
|
||||||
|
collapse,
|
||||||
|
expand,
|
||||||
|
expand_limit,
|
||||||
|
expand_flatten,
|
||||||
|
expand_flatten_separator,
|
||||||
|
width,
|
||||||
index,
|
index,
|
||||||
use_ansi_coloring,
|
use_ansi_coloring,
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_index_flag(
|
fn get_index_flag(
|
||||||
call: &Call,
|
call: &Call,
|
||||||
state: &EngineState,
|
state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) -> Result<Option<usize>, ShellError> {
|
) -> ShellResult<Option<usize>> {
|
||||||
let index: Option<Value> = call.get_flag(state, stack, "index")?;
|
let index: Option<Value> = call.get_flag(state, stack, "index")?;
|
||||||
let value = match index {
|
let value = match index {
|
||||||
Some(value) => value,
|
Some(value) => value,
|
||||||
@ -335,7 +371,7 @@ fn get_theme_flag(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
state: &EngineState,
|
state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) -> Result<Option<TableMode>, ShellError> {
|
) -> ShellResult<Option<TableMode>> {
|
||||||
call.get_flag(state, stack, "theme")?
|
call.get_flag(state, stack, "theme")?
|
||||||
.map(|theme: String| {
|
.map(|theme: String| {
|
||||||
TableMode::from_str(&theme).map_err(|err| ShellError::CantConvert {
|
TableMode::from_str(&theme).map_err(|err| ShellError::CantConvert {
|
||||||
@ -353,29 +389,36 @@ struct CmdInput<'a> {
|
|||||||
stack: &'a mut Stack,
|
stack: &'a mut Stack,
|
||||||
call: &'a Call<'a>,
|
call: &'a Call<'a>,
|
||||||
data: PipelineData,
|
data: PipelineData,
|
||||||
|
cfg: TableConfig,
|
||||||
|
cwd: Option<NuPathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CmdInput<'a> {
|
impl<'a> CmdInput<'a> {
|
||||||
fn new(
|
fn parse(
|
||||||
engine_state: &'a EngineState,
|
engine_state: &'a EngineState,
|
||||||
stack: &'a mut Stack,
|
stack: &'a mut Stack,
|
||||||
call: &'a Call<'a>,
|
call: &'a Call<'a>,
|
||||||
data: PipelineData,
|
data: PipelineData,
|
||||||
) -> Self {
|
) -> ShellResult<Self> {
|
||||||
Self {
|
let cfg = parse_table_config(call, engine_state, stack)?;
|
||||||
|
let cwd = get_cwd(engine_state, stack)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
call,
|
call,
|
||||||
data,
|
data,
|
||||||
|
cfg,
|
||||||
|
cwd,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_config(&self) -> std::sync::Arc<Config> {
|
||||||
|
self.stack.get_config(self.engine_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_table_command(
|
fn handle_table_command(mut input: CmdInput<'_>) -> ShellResult<PipelineData> {
|
||||||
mut input: CmdInput<'_>,
|
|
||||||
cfg: TableConfig,
|
|
||||||
#[cfg(feature = "os")] cwd: nu_path::PathBuf<Absolute>,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let span = input.data.span().unwrap_or(input.call.head);
|
let span = input.data.span().unwrap_or(input.call.head);
|
||||||
match input.data {
|
match input.data {
|
||||||
// Binary streams should behave as if they really are `binary` data, and printed as hex
|
// Binary streams should behave as if they really are `binary` data, and printed as hex
|
||||||
@ -397,29 +440,15 @@ fn handle_table_command(
|
|||||||
let stream = ListStream::new(vals.into_iter(), span, signals);
|
let stream = ListStream::new(vals.into_iter(), span, signals);
|
||||||
input.data = PipelineData::Empty;
|
input.data = PipelineData::Empty;
|
||||||
|
|
||||||
handle_row_stream(
|
handle_row_stream(input, stream, metadata)
|
||||||
input,
|
|
||||||
cfg,
|
|
||||||
stream,
|
|
||||||
metadata,
|
|
||||||
#[cfg(feature = "os")]
|
|
||||||
cwd,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
PipelineData::ListStream(stream, metadata) => {
|
PipelineData::ListStream(stream, metadata) => {
|
||||||
input.data = PipelineData::Empty;
|
input.data = PipelineData::Empty;
|
||||||
handle_row_stream(
|
handle_row_stream(input, stream, metadata)
|
||||||
input,
|
|
||||||
cfg,
|
|
||||||
stream,
|
|
||||||
metadata,
|
|
||||||
#[cfg(feature = "os")]
|
|
||||||
cwd,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
PipelineData::Value(Value::Record { val, .. }, ..) => {
|
PipelineData::Value(Value::Record { val, .. }, ..) => {
|
||||||
input.data = PipelineData::Empty;
|
input.data = PipelineData::Empty;
|
||||||
handle_record(input, cfg, val.into_owned())
|
handle_record(input, val.into_owned())
|
||||||
}
|
}
|
||||||
PipelineData::Value(Value::Error { error, .. }, ..) => {
|
PipelineData::Value(Value::Error { error, .. }, ..) => {
|
||||||
// Propagate this error outward, so that it goes to stderr
|
// Propagate this error outward, so that it goes to stderr
|
||||||
@ -435,14 +464,7 @@ fn handle_table_command(
|
|||||||
let stream =
|
let stream =
|
||||||
ListStream::new(val.into_range_iter(span, Signals::empty()), span, signals);
|
ListStream::new(val.into_range_iter(span, Signals::empty()), span, signals);
|
||||||
input.data = PipelineData::Empty;
|
input.data = PipelineData::Empty;
|
||||||
handle_row_stream(
|
handle_row_stream(input, stream, metadata)
|
||||||
input,
|
|
||||||
cfg,
|
|
||||||
stream,
|
|
||||||
metadata,
|
|
||||||
#[cfg(feature = "os")]
|
|
||||||
cwd,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
x => Ok(x),
|
x => Ok(x),
|
||||||
}
|
}
|
||||||
@ -517,60 +539,55 @@ fn pretty_hex_stream(stream: ByteStream, span: Span) -> ByteStream {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_record(
|
fn handle_record(input: CmdInput, mut record: Record) -> ShellResult<PipelineData> {
|
||||||
input: CmdInput,
|
|
||||||
cfg: TableConfig,
|
|
||||||
mut record: Record,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let config = {
|
|
||||||
let state = input.engine_state;
|
|
||||||
let stack: &Stack = input.stack;
|
|
||||||
stack.get_config(state)
|
|
||||||
};
|
|
||||||
let span = input.data.span().unwrap_or(input.call.head);
|
let span = input.data.span().unwrap_or(input.call.head);
|
||||||
let styles = &StyleComputer::from_config(input.engine_state, input.stack);
|
|
||||||
|
|
||||||
if record.is_empty() {
|
if record.is_empty() {
|
||||||
let value =
|
let value =
|
||||||
create_empty_placeholder("record", cfg.term_width, input.engine_state, input.stack);
|
create_empty_placeholder("record", input.cfg.width, input.engine_state, input.stack);
|
||||||
let value = Value::string(value, span);
|
let value = Value::string(value, span);
|
||||||
return Ok(value.into_pipeline_data());
|
return Ok(value.into_pipeline_data());
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(limit) = cfg.abbreviation {
|
if let Some(limit) = input.cfg.abbreviation {
|
||||||
let prev_len = record.len();
|
record = make_record_abbreviation(record, limit);
|
||||||
if record.len() > limit * 2 + 1 {
|
}
|
||||||
|
|
||||||
|
let config = input.get_config();
|
||||||
|
let opts = create_table_opts(
|
||||||
|
input.engine_state,
|
||||||
|
input.stack,
|
||||||
|
&config,
|
||||||
|
&input.cfg,
|
||||||
|
span,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
let result = build_table_kv(record, input.cfg.view.clone(), opts, span)?;
|
||||||
|
|
||||||
|
let result = match result {
|
||||||
|
Some(output) => maybe_strip_color(output, input.cfg.use_ansi_coloring),
|
||||||
|
None => report_unsuccessful_output(input.engine_state.signals(), input.cfg.width),
|
||||||
|
};
|
||||||
|
|
||||||
|
let val = Value::string(result, span);
|
||||||
|
let data = val.into_pipeline_data();
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_record_abbreviation(mut record: Record, limit: usize) -> Record {
|
||||||
|
if record.len() <= limit * 2 + 1 {
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: see if the following table builders would be happy with a simple iterator
|
// TODO: see if the following table builders would be happy with a simple iterator
|
||||||
|
let prev_len = record.len();
|
||||||
let mut record_iter = record.into_iter();
|
let mut record_iter = record.into_iter();
|
||||||
record = Record::with_capacity(limit * 2 + 1);
|
record = Record::with_capacity(limit * 2 + 1);
|
||||||
record.extend(record_iter.by_ref().take(limit));
|
record.extend(record_iter.by_ref().take(limit));
|
||||||
record.push(String::from("..."), Value::string("...", Span::unknown()));
|
record.push(String::from("..."), Value::string("...", Span::unknown()));
|
||||||
record.extend(record_iter.skip(prev_len - 2 * limit));
|
record.extend(record_iter.skip(prev_len - 2 * limit));
|
||||||
}
|
record
|
||||||
}
|
|
||||||
|
|
||||||
let indent = (config.table.padding.left, config.table.padding.right);
|
|
||||||
let opts = TableOpts::new(
|
|
||||||
&config,
|
|
||||||
styles,
|
|
||||||
input.engine_state.signals(),
|
|
||||||
span,
|
|
||||||
cfg.term_width,
|
|
||||||
indent,
|
|
||||||
cfg.theme,
|
|
||||||
cfg.index.unwrap_or(0),
|
|
||||||
cfg.index.is_none(),
|
|
||||||
);
|
|
||||||
let result = build_table_kv(record, cfg.table_view, opts, span)?;
|
|
||||||
|
|
||||||
let result = match result {
|
|
||||||
Some(output) => maybe_strip_color(output, cfg.use_ansi_coloring),
|
|
||||||
None => report_unsuccessful_output(input.engine_state.signals(), cfg.term_width),
|
|
||||||
};
|
|
||||||
|
|
||||||
let val = Value::string(result, span);
|
|
||||||
|
|
||||||
Ok(val.into_pipeline_data())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_unsuccessful_output(signals: &Signals, term_width: usize) -> String {
|
fn report_unsuccessful_output(signals: &Signals, term_width: usize) -> String {
|
||||||
@ -608,11 +625,11 @@ fn build_table_kv(
|
|||||||
|
|
||||||
fn build_table_batch(
|
fn build_table_batch(
|
||||||
vals: Vec<Value>,
|
vals: Vec<Value>,
|
||||||
table_view: TableView,
|
view: TableView,
|
||||||
opts: TableOpts<'_>,
|
opts: TableOpts<'_>,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> StringResult {
|
) -> StringResult {
|
||||||
match table_view {
|
match view {
|
||||||
TableView::General => JustTable::table(&vals, opts),
|
TableView::General => JustTable::table(&vals, opts),
|
||||||
TableView::Expanded {
|
TableView::Expanded {
|
||||||
limit,
|
limit,
|
||||||
@ -631,22 +648,17 @@ fn build_table_batch(
|
|||||||
|
|
||||||
fn handle_row_stream(
|
fn handle_row_stream(
|
||||||
input: CmdInput<'_>,
|
input: CmdInput<'_>,
|
||||||
cfg: TableConfig,
|
|
||||||
stream: ListStream,
|
stream: ListStream,
|
||||||
metadata: Option<PipelineMetadata>,
|
metadata: Option<PipelineMetadata>,
|
||||||
#[cfg(feature = "os")] cwd: nu_path::PathBuf<Absolute>,
|
) -> ShellResult<PipelineData> {
|
||||||
) -> Result<PipelineData, ShellError> {
|
let cfg = input.get_config();
|
||||||
let stream = match metadata.as_ref() {
|
let stream = match metadata.as_ref() {
|
||||||
// First, `ls` sources:
|
// First, `ls` sources:
|
||||||
Some(PipelineMetadata {
|
Some(PipelineMetadata {
|
||||||
data_source: DataSource::Ls,
|
data_source: DataSource::Ls,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
let config = {
|
let config = cfg.clone();
|
||||||
let state = input.engine_state;
|
|
||||||
let stack: &Stack = input.stack;
|
|
||||||
stack.get_config(state)
|
|
||||||
};
|
|
||||||
let ls_colors_env_str = match input.stack.get_env_var(input.engine_state, "LS_COLORS") {
|
let ls_colors_env_str = match input.stack.get_env_var(input.engine_state, "LS_COLORS") {
|
||||||
Some(v) => Some(env_to_string(
|
Some(v) => Some(env_to_string(
|
||||||
"LS_COLORS",
|
"LS_COLORS",
|
||||||
@ -664,14 +676,9 @@ fn handle_row_stream(
|
|||||||
if let Some(value) = record.to_mut().get_mut("name") {
|
if let Some(value) = record.to_mut().get_mut("name") {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
if let Value::String { val, .. } = value {
|
if let Value::String { val, .. } = value {
|
||||||
if let Some(val) = render_path_name(
|
if let Some(val) =
|
||||||
val,
|
render_path_name(val, &config, &ls_colors, input.cwd.clone(), span)
|
||||||
&config,
|
{
|
||||||
&ls_colors,
|
|
||||||
#[cfg(feature = "os")]
|
|
||||||
cwd.clone(),
|
|
||||||
span,
|
|
||||||
) {
|
|
||||||
*value = val;
|
*value = val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -726,6 +733,7 @@ fn handle_row_stream(
|
|||||||
// for the values it outputs. Because engine_state is passed in, config doesn't need to.
|
// for the values it outputs. Because engine_state is passed in, config doesn't need to.
|
||||||
input.engine_state.clone(),
|
input.engine_state.clone(),
|
||||||
input.stack.clone(),
|
input.stack.clone(),
|
||||||
|
input.cfg,
|
||||||
cfg,
|
cfg,
|
||||||
);
|
);
|
||||||
let stream = ByteStream::from_result_iter(
|
let stream = ByteStream::from_result_iter(
|
||||||
@ -787,8 +795,9 @@ struct PagingTableCreator {
|
|||||||
stack: Stack,
|
stack: Stack,
|
||||||
elements_displayed: usize,
|
elements_displayed: usize,
|
||||||
reached_end: bool,
|
reached_end: bool,
|
||||||
cfg: TableConfig,
|
table_config: TableConfig,
|
||||||
row_offset: usize,
|
row_offset: usize,
|
||||||
|
config: std::sync::Arc<Config>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PagingTableCreator {
|
impl PagingTableCreator {
|
||||||
@ -797,114 +806,51 @@ impl PagingTableCreator {
|
|||||||
stream: ListStream,
|
stream: ListStream,
|
||||||
engine_state: EngineState,
|
engine_state: EngineState,
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
cfg: TableConfig,
|
table_config: TableConfig,
|
||||||
|
config: std::sync::Arc<Config>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PagingTableCreator {
|
PagingTableCreator {
|
||||||
head,
|
head,
|
||||||
stream: stream.into_inner(),
|
stream: stream.into_inner(),
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
cfg,
|
config,
|
||||||
|
table_config,
|
||||||
elements_displayed: 0,
|
elements_displayed: 0,
|
||||||
reached_end: false,
|
reached_end: false,
|
||||||
row_offset: 0,
|
row_offset: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_extended(
|
fn build_table(&mut self, batch: Vec<Value>) -> ShellResult<Option<String>> {
|
||||||
&mut self,
|
|
||||||
batch: Vec<Value>,
|
|
||||||
limit: Option<usize>,
|
|
||||||
flatten: bool,
|
|
||||||
flatten_separator: Option<String>,
|
|
||||||
) -> StringResult {
|
|
||||||
if batch.is_empty() {
|
if batch.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let cfg = {
|
let opts = self.create_table_opts();
|
||||||
let state = &self.engine_state;
|
build_table_batch(batch, self.table_config.view.clone(), opts, self.head)
|
||||||
let stack = &self.stack;
|
|
||||||
stack.get_config(state)
|
|
||||||
};
|
|
||||||
let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack);
|
|
||||||
let opts = self.create_table_opts(&cfg, &style_comp);
|
|
||||||
let view = TableView::Expanded {
|
|
||||||
limit,
|
|
||||||
flatten,
|
|
||||||
flatten_separator,
|
|
||||||
};
|
|
||||||
|
|
||||||
build_table_batch(batch, view, opts, self.head)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_collapsed(&mut self, batch: Vec<Value>) -> StringResult {
|
fn create_table_opts(&self) -> TableOpts<'_> {
|
||||||
if batch.is_empty() {
|
create_table_opts(
|
||||||
return Ok(None);
|
&self.engine_state,
|
||||||
}
|
&self.stack,
|
||||||
|
&self.config,
|
||||||
let cfg = {
|
&self.table_config,
|
||||||
let state = &self.engine_state;
|
|
||||||
let stack = &self.stack;
|
|
||||||
stack.get_config(state)
|
|
||||||
};
|
|
||||||
let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack);
|
|
||||||
let opts = self.create_table_opts(&cfg, &style_comp);
|
|
||||||
|
|
||||||
build_table_batch(batch, TableView::Collapsed, opts, self.head)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_general(&mut self, batch: Vec<Value>) -> StringResult {
|
|
||||||
let cfg = {
|
|
||||||
let state = &self.engine_state;
|
|
||||||
let stack = &self.stack;
|
|
||||||
stack.get_config(state)
|
|
||||||
};
|
|
||||||
let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack);
|
|
||||||
let opts = self.create_table_opts(&cfg, &style_comp);
|
|
||||||
|
|
||||||
build_table_batch(batch, TableView::General, opts, self.head)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_table_opts<'a>(
|
|
||||||
&'a self,
|
|
||||||
cfg: &'a Config,
|
|
||||||
style_comp: &'a StyleComputer<'a>,
|
|
||||||
) -> TableOpts<'a> {
|
|
||||||
TableOpts::new(
|
|
||||||
cfg,
|
|
||||||
style_comp,
|
|
||||||
self.engine_state.signals(),
|
|
||||||
self.head,
|
self.head,
|
||||||
self.cfg.term_width,
|
self.row_offset,
|
||||||
(cfg.table.padding.left, cfg.table.padding.right),
|
|
||||||
self.cfg.theme,
|
|
||||||
self.cfg.index.unwrap_or(0) + self.row_offset,
|
|
||||||
self.cfg.index.is_none(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_table(&mut self, batch: Vec<Value>) -> Result<Option<String>, ShellError> {
|
|
||||||
match &self.cfg.table_view {
|
|
||||||
TableView::General => self.build_general(batch),
|
|
||||||
TableView::Collapsed => self.build_collapsed(batch),
|
|
||||||
TableView::Expanded {
|
|
||||||
limit,
|
|
||||||
flatten,
|
|
||||||
flatten_separator,
|
|
||||||
} => self.build_extended(batch, *limit, *flatten, flatten_separator.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for PagingTableCreator {
|
impl Iterator for PagingTableCreator {
|
||||||
type Item = Result<Vec<u8>, ShellError>;
|
type Item = ShellResult<Vec<u8>>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let batch;
|
let batch;
|
||||||
let end;
|
let end;
|
||||||
|
|
||||||
match self.cfg.abbreviation {
|
match self.table_config.abbreviation {
|
||||||
Some(abbr) => {
|
Some(abbr) => {
|
||||||
(batch, _, end) =
|
(batch, _, end) =
|
||||||
stream_collect_abbriviated(&mut self.stream, abbr, self.engine_state.signals());
|
stream_collect_abbriviated(&mut self.stream, abbr, self.engine_state.signals());
|
||||||
@ -934,7 +880,7 @@ impl Iterator for PagingTableCreator {
|
|||||||
self.elements_displayed = 1;
|
self.elements_displayed = 1;
|
||||||
let result = create_empty_placeholder(
|
let result = create_empty_placeholder(
|
||||||
"list",
|
"list",
|
||||||
self.cfg.term_width,
|
self.table_config.width,
|
||||||
&self.engine_state,
|
&self.engine_state,
|
||||||
&self.stack,
|
&self.stack,
|
||||||
);
|
);
|
||||||
@ -950,9 +896,9 @@ impl Iterator for PagingTableCreator {
|
|||||||
|
|
||||||
convert_table_to_output(
|
convert_table_to_output(
|
||||||
table,
|
table,
|
||||||
&self.cfg,
|
|
||||||
self.engine_state.signals(),
|
self.engine_state.signals(),
|
||||||
self.cfg.term_width,
|
self.table_config.width,
|
||||||
|
self.table_config.use_ansi_coloring,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1059,17 +1005,17 @@ fn render_path_name(
|
|||||||
path: &str,
|
path: &str,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
ls_colors: &LsColors,
|
ls_colors: &LsColors,
|
||||||
#[cfg(feature = "os")] cwd: nu_path::PathBuf<Absolute>,
|
cwd: Option<NuPathBuf>,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Option<Value> {
|
) -> Option<Value> {
|
||||||
if !config.ls.use_ls_colors {
|
if !config.ls.use_ls_colors {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "os")]
|
let fullpath = match cwd {
|
||||||
let fullpath = cwd.join(path);
|
Some(cwd) => PathBuf::from(cwd.join(path)),
|
||||||
#[cfg(not(feature = "os"))]
|
None => PathBuf::from(path),
|
||||||
let fullpath = path;
|
};
|
||||||
|
|
||||||
let stripped_path = nu_utils::strip_ansi_unlikely(path);
|
let stripped_path = nu_utils::strip_ansi_unlikely(path);
|
||||||
let metadata = std::fs::symlink_metadata(fullpath);
|
let metadata = std::fs::symlink_metadata(fullpath);
|
||||||
@ -1101,19 +1047,9 @@ fn render_path_name(
|
|||||||
Some(Value::string(val, span))
|
Some(Value::string(val, span))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
enum TableView {
|
|
||||||
General,
|
|
||||||
Collapsed,
|
|
||||||
Expanded {
|
|
||||||
limit: Option<usize>,
|
|
||||||
flatten: bool,
|
|
||||||
flatten_separator: Option<String>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fn maybe_strip_color(output: String, use_ansi_coloring: bool) -> String {
|
fn maybe_strip_color(output: String, use_ansi_coloring: bool) -> String {
|
||||||
if !use_ansi_coloring {
|
// the terminal is for when people do ls from vim, there should be no coloring there
|
||||||
|
if !use_ansi_coloring || !std::io::stdout().is_terminal() {
|
||||||
// Draw the table without ansi colors
|
// Draw the table without ansi colors
|
||||||
nu_utils::strip_ansi_string_likely(output)
|
nu_utils::strip_ansi_string_likely(output)
|
||||||
} else {
|
} else {
|
||||||
@ -1133,29 +1069,29 @@ fn create_empty_placeholder(
|
|||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let cell = NuTableCell::new(format!("empty {}", value_type_name));
|
let cell = NuRecordsValue::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_data_style(TextStyle::default().dimmed());
|
table.set_data_style(TextStyle::default().dimmed());
|
||||||
let out = TableOutput::new(table, false, false, 1);
|
let mut out = TableOutput::from_table(table, false, false);
|
||||||
|
|
||||||
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
||||||
let config = create_nu_table_config(&config, style_computer, &out, false, TableMode::default());
|
configure_table(&mut out, &config, style_computer, TableMode::default());
|
||||||
|
|
||||||
out.table
|
out.table
|
||||||
.draw(config, termwidth)
|
.draw(termwidth)
|
||||||
.expect("Could not create empty table placeholder")
|
.expect("Could not create empty table placeholder")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_table_to_output(
|
fn convert_table_to_output(
|
||||||
table: Result<Option<String>, ShellError>,
|
table: ShellResult<Option<String>>,
|
||||||
cfg: &TableConfig,
|
|
||||||
signals: &Signals,
|
signals: &Signals,
|
||||||
term_width: usize,
|
term_width: usize,
|
||||||
) -> Option<Result<Vec<u8>, ShellError>> {
|
use_ansi_coloring: bool,
|
||||||
|
) -> Option<ShellResult<Vec<u8>>> {
|
||||||
match table {
|
match table {
|
||||||
Ok(Some(table)) => {
|
Ok(Some(table)) => {
|
||||||
let table = maybe_strip_color(table, cfg.use_ansi_coloring);
|
let table = maybe_strip_color(table, use_ansi_coloring);
|
||||||
|
|
||||||
let mut bytes = table.as_bytes().to_vec();
|
let mut bytes = table.as_bytes().to_vec();
|
||||||
bytes.push(b'\n'); // nu-table tables don't come with a newline on the end
|
bytes.push(b'\n'); // nu-table tables don't come with a newline on the end
|
||||||
@ -1198,3 +1134,41 @@ fn supported_table_modes() -> Vec<Value> {
|
|||||||
Value::test_string("basic_compact"),
|
Value::test_string("basic_compact"),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_table_opts<'a>(
|
||||||
|
engine_state: &'a EngineState,
|
||||||
|
stack: &'a Stack,
|
||||||
|
cfg: &'a Config,
|
||||||
|
table_cfg: &'a TableConfig,
|
||||||
|
span: Span,
|
||||||
|
offset: usize,
|
||||||
|
) -> TableOpts<'a> {
|
||||||
|
let comp = StyleComputer::from_config(engine_state, stack);
|
||||||
|
let signals = engine_state.signals();
|
||||||
|
let offset = table_cfg.index.unwrap_or(0) + offset;
|
||||||
|
let index = table_cfg.index.is_none();
|
||||||
|
let width = table_cfg.width;
|
||||||
|
let theme = table_cfg.theme;
|
||||||
|
|
||||||
|
TableOpts::new(cfg, comp, signals, span, width, theme, offset, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cwd(engine_state: &EngineState, stack: &mut Stack) -> ShellResult<Option<NuPathBuf>> {
|
||||||
|
#[cfg(feature = "os")]
|
||||||
|
let cwd = engine_state.cwd(Some(stack)).map(Some)?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "os"))]
|
||||||
|
let cwd = None;
|
||||||
|
|
||||||
|
Ok(cwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_table_width(width_param: Option<i64>) -> usize {
|
||||||
|
if let Some(col) = width_param {
|
||||||
|
col as usize
|
||||||
|
} else if let Ok((w, _h)) = terminal_size() {
|
||||||
|
w as usize
|
||||||
|
} else {
|
||||||
|
DEFAULT_TABLE_WIDTH
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2867,13 +2867,52 @@ fn table_index_arg() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn table_expand_index_arg() {
|
fn table_expand_index_arg() {
|
||||||
let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i false");
|
let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i false");
|
||||||
assert_eq!(actual.out, "+---+-------+| a | b |+---+-------+| 1 | 2 |+---+-------+| 2 | +---+ || | | 4 | || | +---+ || | | 4 | || | +---+ |+---+-------+");
|
assert_eq!(
|
||||||
|
actual.out,
|
||||||
|
"+---+-------+\
|
||||||
|
| a | b |\
|
||||||
|
+---+-------+\
|
||||||
|
| 1 | 2 |\
|
||||||
|
+---+-------+\
|
||||||
|
| 2 | +---+ |\
|
||||||
|
| | | 4 | |\
|
||||||
|
| | +---+ |\
|
||||||
|
| | | 4 | |\
|
||||||
|
| | +---+ |\
|
||||||
|
+---+-------+"
|
||||||
|
);
|
||||||
|
|
||||||
let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i true");
|
let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i true");
|
||||||
assert_eq!(actual.out, "+---+---+-----------+| # | a | b |+---+---+-----------+| 0 | 1 | 2 |+---+---+-----------+| 1 | 2 | +---+---+ || | | | 0 | 4 | || | | +---+---+ || | | | 1 | 4 | || | | +---+---+ |+---+---+-----------+");
|
assert_eq!(
|
||||||
|
actual.out,
|
||||||
|
"+---+---+-----------+\
|
||||||
|
| # | a | b |\
|
||||||
|
+---+---+-----------+\
|
||||||
|
| 0 | 1 | 2 |\
|
||||||
|
+---+---+-----------+\
|
||||||
|
| 1 | 2 | +---+---+ |\
|
||||||
|
| | | | 0 | 4 | |\
|
||||||
|
| | | +---+---+ |\
|
||||||
|
| | | | 1 | 4 | |\
|
||||||
|
| | | +---+---+ |\
|
||||||
|
+---+---+-----------+"
|
||||||
|
);
|
||||||
|
|
||||||
let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i 10");
|
let actual = nu!("[[a b]; [1 2] [2 [4 4]]] | table --width=80 --theme basic --expand -i 10");
|
||||||
assert_eq!(actual.out, "+----+---+-----------+| # | a | b |+----+---+-----------+| 10 | 1 | 2 |+----+---+-----------+| 11 | 2 | +---+---+ || | | | 0 | 4 | || | | +---+---+ || | | | 1 | 4 | || | | +---+---+ |+----+---+-----------+");
|
assert_eq!(
|
||||||
|
actual.out,
|
||||||
|
"+----+---+-----------+\
|
||||||
|
| # | a | b |\
|
||||||
|
+----+---+-----------+\
|
||||||
|
| 10 | 1 | 2 |\
|
||||||
|
+----+---+-----------+\
|
||||||
|
| 11 | 2 | +---+---+ |\
|
||||||
|
| | | | 0 | 4 | |\
|
||||||
|
| | | +---+---+ |\
|
||||||
|
| | | | 1 | 4 | |\
|
||||||
|
| | | +---+---+ |\
|
||||||
|
+----+---+-----------+"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -69,12 +69,9 @@ fn convert_value_to_string(
|
|||||||
} else {
|
} else {
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||||
|
let table =
|
||||||
|
nu_common::try_build_table(value, engine_state.signals(), config, style_computer);
|
||||||
|
|
||||||
Ok(nu_common::try_build_table(
|
Ok(table)
|
||||||
engine_state.signals(),
|
|
||||||
config,
|
|
||||||
&style_computer,
|
|
||||||
value,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,80 +1,57 @@
|
|||||||
use crate::nu_common::NuConfig;
|
use crate::nu_common::NuConfig;
|
||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_protocol::{Record, Signals, Span, Value};
|
use nu_protocol::{Record, Signals, Value};
|
||||||
use nu_table::{
|
use nu_table::{
|
||||||
common::{nu_value_to_string, nu_value_to_string_clean},
|
common::{nu_value_to_string, nu_value_to_string_clean},
|
||||||
ExpandedTable, TableOpts,
|
ExpandedTable, TableOpts,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn try_build_table(
|
pub fn try_build_table(
|
||||||
|
value: Value,
|
||||||
signals: &Signals,
|
signals: &Signals,
|
||||||
config: &NuConfig,
|
config: &NuConfig,
|
||||||
style_computer: &StyleComputer,
|
style_computer: StyleComputer,
|
||||||
value: Value,
|
|
||||||
) -> String {
|
) -> String {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
match value {
|
|
||||||
Value::List { vals, .. } => try_build_list(vals, signals, config, span, style_computer),
|
|
||||||
Value::Record { val, .. } => try_build_map(&val, span, style_computer, signals, config),
|
|
||||||
val if matches!(val, Value::String { .. }) => {
|
|
||||||
nu_value_to_string_clean(&val, config, style_computer).0
|
|
||||||
}
|
|
||||||
val => nu_value_to_string(&val, config, style_computer).0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_build_map(
|
|
||||||
record: &Record,
|
|
||||||
span: Span,
|
|
||||||
style_computer: &StyleComputer,
|
|
||||||
signals: &Signals,
|
|
||||||
config: &NuConfig,
|
|
||||||
) -> String {
|
|
||||||
let opts = TableOpts::new(
|
let opts = TableOpts::new(
|
||||||
config,
|
config,
|
||||||
style_computer,
|
style_computer,
|
||||||
signals,
|
signals,
|
||||||
Span::unknown(),
|
span,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
(config.table.padding.left, config.table.padding.right),
|
|
||||||
config.table.mode,
|
config.table.mode,
|
||||||
0,
|
0,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
let result = ExpandedTable::new(None, false, String::new()).build_map(record, opts);
|
match value {
|
||||||
|
Value::List { vals, .. } => try_build_list(vals, opts),
|
||||||
|
Value::Record { val, .. } => try_build_map(&val, opts),
|
||||||
|
val if matches!(val, Value::String { .. }) => {
|
||||||
|
nu_value_to_string_clean(&val, config, &opts.style_computer).0
|
||||||
|
}
|
||||||
|
val => nu_value_to_string(&val, config, &opts.style_computer).0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_build_map(record: &Record, opts: TableOpts<'_>) -> String {
|
||||||
|
let result = ExpandedTable::new(None, false, String::new()).build_map(record, opts.clone());
|
||||||
match result {
|
match result {
|
||||||
Ok(Some(result)) => result,
|
Ok(Some(result)) => result,
|
||||||
Ok(None) | Err(_) => {
|
_ => {
|
||||||
nu_value_to_string(&Value::record(record.clone(), span), config, style_computer).0
|
let value = Value::record(record.clone(), opts.span);
|
||||||
|
nu_value_to_string(&value, opts.config, &opts.style_computer).0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_build_list(
|
fn try_build_list(vals: Vec<Value>, opts: TableOpts<'_>) -> String {
|
||||||
vals: Vec<Value>,
|
let result = ExpandedTable::new(None, false, String::new()).build_list(&vals, opts.clone());
|
||||||
signals: &Signals,
|
|
||||||
config: &NuConfig,
|
|
||||||
span: Span,
|
|
||||||
style_computer: &StyleComputer,
|
|
||||||
) -> String {
|
|
||||||
let opts = TableOpts::new(
|
|
||||||
config,
|
|
||||||
style_computer,
|
|
||||||
signals,
|
|
||||||
Span::unknown(),
|
|
||||||
usize::MAX,
|
|
||||||
(config.table.padding.left, config.table.padding.right),
|
|
||||||
config.table.mode,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
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(_) => {
|
_ => {
|
||||||
// it means that the list is empty
|
// it means that the list is empty
|
||||||
nu_value_to_string(&Value::list(vals, span), config, style_computer).0
|
let value = Value::list(vals, opts.span);
|
||||||
|
nu_value_to_string(&value, opts.config, &opts.style_computer).0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ pub use plugin_gc::{PluginGcConfig, PluginGcConfigs};
|
|||||||
pub use reedline::{CursorShapeConfig, EditBindings, NuCursorShape, ParsedKeybinding, ParsedMenu};
|
pub use reedline::{CursorShapeConfig, EditBindings, NuCursorShape, ParsedKeybinding, ParsedMenu};
|
||||||
pub use rm::RmConfig;
|
pub use rm::RmConfig;
|
||||||
pub use shell_integration::ShellIntegrationConfig;
|
pub use shell_integration::ShellIntegrationConfig;
|
||||||
pub use table::{FooterMode, TableConfig, TableIndexMode, TableMode, TrimStrategy};
|
pub use table::{FooterMode, TableConfig, TableIndent, TableIndexMode, TableMode, TrimStrategy};
|
||||||
|
|
||||||
mod ansi_coloring;
|
mod ansi_coloring;
|
||||||
mod completions;
|
mod completions;
|
||||||
|
@ -277,6 +277,12 @@ pub struct TableIndent {
|
|||||||
pub right: usize,
|
pub right: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TableIndent {
|
||||||
|
pub fn new(left: usize, right: usize) -> Self {
|
||||||
|
Self { left, right }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoValue for TableIndent {
|
impl IntoValue for TableIndent {
|
||||||
fn into_value(self, span: Span) -> Value {
|
fn into_value(self, span: Span) -> Value {
|
||||||
record! {
|
record! {
|
||||||
|
@ -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, NuTableConfig, TableTheme};
|
use nu_table::{NuTable, TableTheme};
|
||||||
use tabled::grid::records::vec_records::Text;
|
use tabled::grid::records::vec_records::Text;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -28,15 +28,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)));
|
||||||
|
table.set_theme(TableTheme::rounded());
|
||||||
let table_cfg = NuTableConfig {
|
table.set_structure(false, true, false);
|
||||||
theme: TableTheme::rounded(),
|
|
||||||
with_header: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let output_table = table
|
let output_table = table
|
||||||
.draw(table_cfg, width)
|
.draw(width)
|
||||||
.unwrap_or_else(|| format!("Couldn't fit table into {width} columns!"));
|
.unwrap_or_else(|| format!("Couldn't fit table into {width} columns!"));
|
||||||
|
|
||||||
println!("{output_table}")
|
println!("{output_table}")
|
||||||
|
@ -1,51 +1,57 @@
|
|||||||
use crate::{
|
|
||||||
clean_charset, colorize_space_str, string_wrap, NuTableConfig, TableOutput, TableTheme,
|
|
||||||
};
|
|
||||||
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
||||||
use nu_protocol::{Config, FooterMode, ShellError, Span, TableMode, TrimStrategy, Value};
|
use nu_protocol::{Config, FooterMode, ShellError, Span, TableMode, TrimStrategy, Value};
|
||||||
|
|
||||||
use terminal_size::{terminal_size, Height, Width};
|
use terminal_size::{terminal_size, Height, Width};
|
||||||
|
|
||||||
|
use crate::{clean_charset, colorize_space_str, string_wrap, TableOutput, TableTheme};
|
||||||
|
|
||||||
pub type NuText = (String, TextStyle);
|
pub type NuText = (String, TextStyle);
|
||||||
pub type TableResult = Result<Option<TableOutput>, ShellError>;
|
pub type TableResult = Result<Option<TableOutput>, ShellError>;
|
||||||
pub type StringResult = Result<Option<String>, ShellError>;
|
pub type StringResult = Result<Option<String>, ShellError>;
|
||||||
|
|
||||||
pub const INDEX_COLUMN_NAME: &str = "index";
|
pub const INDEX_COLUMN_NAME: &str = "index";
|
||||||
|
|
||||||
pub fn create_nu_table_config(
|
pub fn configure_table(
|
||||||
|
out: &mut TableOutput,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
comp: &StyleComputer,
|
comp: &StyleComputer,
|
||||||
out: &TableOutput,
|
|
||||||
expand: bool,
|
|
||||||
mode: TableMode,
|
mode: TableMode,
|
||||||
) -> NuTableConfig {
|
) {
|
||||||
|
let with_footer = is_footer_needed(config, out);
|
||||||
|
let theme = load_theme(mode);
|
||||||
|
|
||||||
|
out.table.set_theme(theme);
|
||||||
|
out.table
|
||||||
|
.set_structure(out.with_index, out.with_header, with_footer);
|
||||||
|
out.table.set_trim(config.table.trim.clone());
|
||||||
|
out.table
|
||||||
|
.set_border_header(config.table.header_on_separator);
|
||||||
|
out.table.set_border_color(lookup_separator_color(comp));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_footer_needed(config: &Config, out: &TableOutput) -> bool {
|
||||||
let mut count_rows = out.table.count_rows();
|
let mut count_rows = out.table.count_rows();
|
||||||
if config.table.footer_inheritance {
|
if config.table.footer_inheritance {
|
||||||
count_rows = out.count_rows;
|
count_rows = out.count_rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
let with_footer = with_footer(config, out.with_header, count_rows);
|
with_footer(config, out.with_header, count_rows)
|
||||||
|
|
||||||
NuTableConfig {
|
|
||||||
theme: load_theme(mode),
|
|
||||||
with_footer,
|
|
||||||
with_index: out.with_index,
|
|
||||||
with_header: out.with_header,
|
|
||||||
split_color: Some(lookup_separator_color(comp)),
|
|
||||||
trim: config.table.trim.clone(),
|
|
||||||
header_on_border: config.table.header_on_separator,
|
|
||||||
expand,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nu_value_to_string_colored(val: &Value, cfg: &Config, style: &StyleComputer) -> String {
|
pub fn nu_value_to_string_colored(val: &Value, cfg: &Config, comp: &StyleComputer) -> String {
|
||||||
let (mut text, value_style) = nu_value_to_string(val, cfg, style);
|
let (mut text, style) = nu_value_to_string(val, cfg, comp);
|
||||||
if let Some(color) = value_style.color_style {
|
|
||||||
|
let is_string = matches!(val, Value::String { .. });
|
||||||
|
if is_string {
|
||||||
|
text = clean_charset(&text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(color) = style.color_style {
|
||||||
text = color.paint(text).to_string();
|
text = color.paint(text).to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(val, Value::String { .. }) {
|
if is_string {
|
||||||
text = clean_charset(&text);
|
colorize_space_str(&mut text, comp);
|
||||||
colorize_space_str(&mut text, style);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text
|
text
|
||||||
@ -54,9 +60,11 @@ pub fn nu_value_to_string_colored(val: &Value, cfg: &Config, style: &StyleComput
|
|||||||
pub fn nu_value_to_string(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
|
pub fn nu_value_to_string(val: &Value, cfg: &Config, style: &StyleComputer) -> NuText {
|
||||||
let float_precision = cfg.float_precision as usize;
|
let float_precision = cfg.float_precision as usize;
|
||||||
let text = val.to_abbreviated_string(cfg);
|
let text = val.to_abbreviated_string(cfg);
|
||||||
make_styled_string(style, text, Some(val), float_precision)
|
make_styled_value(text, val, float_precision, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: Expose a method which returns just style
|
||||||
|
|
||||||
pub fn nu_value_to_string_clean(val: &Value, cfg: &Config, style_comp: &StyleComputer) -> NuText {
|
pub fn nu_value_to_string_clean(val: &Value, cfg: &Config, style_comp: &StyleComputer) -> NuText {
|
||||||
let (text, style) = nu_value_to_string(val, cfg, style_comp);
|
let (text, style) = nu_value_to_string(val, cfg, style_comp);
|
||||||
let mut text = clean_charset(&text);
|
let mut text = clean_charset(&text);
|
||||||
@ -66,7 +74,11 @@ pub fn nu_value_to_string_clean(val: &Value, cfg: &Config, style_comp: &StyleCom
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn error_sign(style_computer: &StyleComputer) -> (String, TextStyle) {
|
pub fn error_sign(style_computer: &StyleComputer) -> (String, TextStyle) {
|
||||||
make_styled_string(style_computer, String::from("❎"), None, 0)
|
// Though holes are not the same as null, the closure for "empty" is passed a null anyway.
|
||||||
|
|
||||||
|
let text = String::from("❎");
|
||||||
|
let style = style_computer.compute("empty", &Value::nothing(Span::unknown()));
|
||||||
|
(text, TextStyle::with_style(Alignment::Center, style))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wrap_text(text: &str, width: usize, config: &Config) -> String {
|
pub fn wrap_text(text: &str, width: usize, config: &Config) -> String {
|
||||||
@ -122,14 +134,12 @@ pub fn get_empty_style(style_computer: &StyleComputer) -> NuText {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_styled_string(
|
fn make_styled_value(
|
||||||
style_computer: &StyleComputer,
|
|
||||||
text: String,
|
text: String,
|
||||||
value: Option<&Value>, // None represents table holes.
|
value: &Value,
|
||||||
float_precision: usize,
|
float_precision: usize,
|
||||||
|
style_computer: &StyleComputer,
|
||||||
) -> NuText {
|
) -> NuText {
|
||||||
match value {
|
|
||||||
Some(value) => {
|
|
||||||
match value {
|
match value {
|
||||||
Value::Float { .. } => {
|
Value::Float { .. } => {
|
||||||
// set dynamic precision from config
|
// set dynamic precision from config
|
||||||
@ -137,22 +147,11 @@ fn make_styled_string(
|
|||||||
Ok(num) => num,
|
Ok(num) => num,
|
||||||
Err(e) => e.to_string(),
|
Err(e) => e.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
(precise_number, style_computer.style_primitive(value))
|
(precise_number, style_computer.style_primitive(value))
|
||||||
}
|
}
|
||||||
_ => (text, 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> {
|
fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellError> {
|
||||||
@ -220,3 +219,10 @@ fn need_footer(config: &Config, count_records: u64) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_value(value: &Value) -> Result<(), ShellError> {
|
||||||
|
match value {
|
||||||
|
Value::Error { error, .. } => Err(*error.clone()),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
mod table;
|
mod table;
|
||||||
mod table_theme;
|
mod table_theme;
|
||||||
mod types;
|
mod types;
|
||||||
@ -9,7 +10,7 @@ pub mod common;
|
|||||||
|
|
||||||
pub use common::{StringResult, TableResult};
|
pub use common::{StringResult, TableResult};
|
||||||
pub use nu_color_config::TextStyle;
|
pub use nu_color_config::TextStyle;
|
||||||
pub use table::{NuTable, NuTableCell, NuTableConfig};
|
pub use table::{NuRecordsValue, NuTable};
|
||||||
pub use table_theme::TableTheme;
|
pub use table_theme::TableTheme;
|
||||||
pub use types::{CollapsedTable, ExpandedTable, JustTable, TableOpts, TableOutput};
|
pub use types::{CollapsedTable, ExpandedTable, JustTable, TableOpts, TableOutput};
|
||||||
pub use unstructured_table::UnstructuredTable;
|
pub use unstructured_table::UnstructuredTable;
|
||||||
|
@ -1,59 +1,46 @@
|
|||||||
use crate::{convert_style, table_theme::TableTheme};
|
use std::{cmp::min, collections::HashMap};
|
||||||
|
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_color_config::TextStyle;
|
use nu_color_config::TextStyle;
|
||||||
use nu_protocol::TrimStrategy;
|
use nu_protocol::{TableIndent, TrimStrategy};
|
||||||
use nu_utils::strip_ansi_unlikely;
|
use nu_utils::strip_ansi_unlikely;
|
||||||
use std::{cmp::min, collections::HashMap};
|
|
||||||
use tabled::{
|
use tabled::{
|
||||||
builder::Builder,
|
builder::Builder,
|
||||||
grid::{
|
grid::{
|
||||||
ansi::ANSIBuf,
|
ansi::ANSIBuf,
|
||||||
colors::Colors,
|
colors::Colors,
|
||||||
config::{AlignmentHorizontal, ColoredConfig, Entity, EntityMap, Position},
|
config::{AlignmentHorizontal, ColoredConfig, Entity, Position},
|
||||||
dimension::CompleteDimensionVecRecords,
|
dimension::CompleteDimensionVecRecords,
|
||||||
records::{
|
records::{
|
||||||
vec_records::{Cell, Text, VecRecords},
|
vec_records::{Cell, Text, VecRecords},
|
||||||
ExactRecords, PeekableRecords, Records, Resizable,
|
ExactRecords, Records, Resizable,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
settings::{
|
settings::{
|
||||||
|
format::FormatContent,
|
||||||
formatting::AlignmentStrategy,
|
formatting::AlignmentStrategy,
|
||||||
object::{Columns, Segment},
|
object::{Columns, Row, Rows},
|
||||||
peaker::Peaker,
|
peaker::Priority,
|
||||||
themes::ColumnNames,
|
themes::ColumnNames,
|
||||||
width::Truncate,
|
width::Truncate,
|
||||||
Alignment, Color, Modify, Padding, Settings, TableOption, Width,
|
Alignment, Color, Format, Modify, ModifyList, Padding, Settings, TableOption, Width,
|
||||||
},
|
},
|
||||||
Table,
|
Table,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{convert_style, is_color_empty, table_theme::TableTheme};
|
||||||
|
|
||||||
|
pub type NuRecords = VecRecords<NuRecordsValue>;
|
||||||
|
pub type NuRecordsValue = Text<String>;
|
||||||
|
|
||||||
/// NuTable is a table rendering implementation.
|
/// NuTable is a table rendering implementation.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NuTable {
|
pub struct NuTable {
|
||||||
data: NuRecords,
|
data: NuRecords,
|
||||||
styles: Styles,
|
styles: Styles,
|
||||||
alignments: Alignments,
|
alignments: Alignments,
|
||||||
indent: (usize, usize),
|
config: TableConfig,
|
||||||
}
|
|
||||||
|
|
||||||
pub type NuRecords = VecRecords<NuTableCell>;
|
|
||||||
pub type NuTableCell = Text<String>;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
struct Styles {
|
|
||||||
index: ANSIBuf,
|
|
||||||
header: ANSIBuf,
|
|
||||||
data: EntityMap<ANSIBuf>,
|
|
||||||
data_is_set: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct Alignments {
|
|
||||||
data: AlignmentHorizontal,
|
|
||||||
index: AlignmentHorizontal,
|
|
||||||
header: AlignmentHorizontal,
|
|
||||||
columns: HashMap<usize, AlignmentHorizontal>,
|
|
||||||
cells: HashMap<Position, AlignmentHorizontal>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NuTable {
|
impl NuTable {
|
||||||
@ -62,7 +49,6 @@ impl NuTable {
|
|||||||
Self {
|
Self {
|
||||||
data: VecRecords::new(vec![vec![Text::default(); count_columns]; count_rows]),
|
data: VecRecords::new(vec![vec![Text::default(); count_columns]; count_rows]),
|
||||||
styles: Styles::default(),
|
styles: Styles::default(),
|
||||||
indent: (1, 1),
|
|
||||||
alignments: Alignments {
|
alignments: Alignments {
|
||||||
data: AlignmentHorizontal::Left,
|
data: AlignmentHorizontal::Left,
|
||||||
index: AlignmentHorizontal::Right,
|
index: AlignmentHorizontal::Right,
|
||||||
@ -70,6 +56,15 @@ impl NuTable {
|
|||||||
columns: HashMap::default(),
|
columns: HashMap::default(),
|
||||||
cells: HashMap::default(),
|
cells: HashMap::default(),
|
||||||
},
|
},
|
||||||
|
config: TableConfig {
|
||||||
|
theme: TableTheme::basic(),
|
||||||
|
trim: TrimStrategy::truncate(None),
|
||||||
|
structure: TableStructure::new(false, false, false),
|
||||||
|
indent: TableIndent::new(1, 1),
|
||||||
|
header_on_border: false,
|
||||||
|
expand: false,
|
||||||
|
border_color: None,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,11 +82,23 @@ impl NuTable {
|
|||||||
self.data[pos.0][pos.1] = Text::new(text);
|
self.data[pos.0][pos.1] = Text::new(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_row(&mut self, index: usize, row: Vec<String>) {
|
||||||
|
let data = &mut self.data[index];
|
||||||
|
|
||||||
|
for (col, text) in row.into_iter().enumerate() {
|
||||||
|
data[col] = Text::new(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_row(&mut self, index: usize, row: Vec<NuRecordsValue>) {
|
||||||
|
assert_eq!(self.data[index].len(), row.len());
|
||||||
|
self.data[index] = row;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_column_style(&mut self, column: usize, style: TextStyle) {
|
pub fn set_column_style(&mut self, column: usize, style: TextStyle) {
|
||||||
if let Some(style) = style.color_style {
|
if let Some(style) = style.color_style {
|
||||||
let style = ANSIBuf::from(convert_style(style));
|
let style = convert_style(style);
|
||||||
self.styles.data.insert(Entity::Column(column), style);
|
self.styles.columns.insert(column, style);
|
||||||
self.styles.data_is_set = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let alignment = convert_alignment(style.alignment);
|
let alignment = convert_alignment(style.alignment);
|
||||||
@ -102,9 +109,8 @@ impl NuTable {
|
|||||||
|
|
||||||
pub fn insert_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 = ANSIBuf::from(convert_style(style));
|
let style = convert_style(style);
|
||||||
self.styles.data.insert(Entity::Cell(pos.0, pos.1), style);
|
self.styles.cells.insert(pos, style);
|
||||||
self.styles.data_is_set = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let alignment = convert_alignment(style.alignment);
|
let alignment = convert_alignment(style.alignment);
|
||||||
@ -115,7 +121,7 @@ impl NuTable {
|
|||||||
|
|
||||||
pub fn set_header_style(&mut self, style: TextStyle) {
|
pub fn set_header_style(&mut self, style: TextStyle) {
|
||||||
if let Some(style) = style.color_style {
|
if let Some(style) = style.color_style {
|
||||||
let style = ANSIBuf::from(convert_style(style));
|
let style = convert_style(style);
|
||||||
self.styles.header = style;
|
self.styles.header = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +130,7 @@ impl NuTable {
|
|||||||
|
|
||||||
pub fn set_index_style(&mut self, style: TextStyle) {
|
pub fn set_index_style(&mut self, style: TextStyle) {
|
||||||
if let Some(style) = style.color_style {
|
if let Some(style) = style.color_style {
|
||||||
let style = ANSIBuf::from(convert_style(style));
|
let style = convert_style(style);
|
||||||
self.styles.index = style;
|
self.styles.index = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,16 +139,39 @@ impl NuTable {
|
|||||||
|
|
||||||
pub fn set_data_style(&mut self, style: TextStyle) {
|
pub fn set_data_style(&mut self, style: TextStyle) {
|
||||||
if let Some(style) = style.color_style {
|
if let Some(style) = style.color_style {
|
||||||
let style = ANSIBuf::from(convert_style(style));
|
let style = convert_style(style);
|
||||||
self.styles.data.insert(Entity::Global, style);
|
self.styles.data = style;
|
||||||
self.styles.data_is_set = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.alignments.data = convert_alignment(style.alignment);
|
self.alignments.data = convert_alignment(style.alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_indent(&mut self, left: usize, right: usize) {
|
pub fn set_indent(&mut self, indent: TableIndent) {
|
||||||
self.indent = (left, right);
|
self.config.indent = indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_theme(&mut self, theme: TableTheme) {
|
||||||
|
self.config.theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_structure(&mut self, index: bool, header: bool, footer: bool) {
|
||||||
|
self.config.structure = TableStructure::new(index, header, footer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_border_header(&mut self, on: bool) {
|
||||||
|
self.config.header_on_border = on;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_trim(&mut self, strategy: TrimStrategy) {
|
||||||
|
self.config.trim = strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_strategy(&mut self, expand: bool) {
|
||||||
|
self.config.expand = expand;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_border_color(&mut self, color: Style) {
|
||||||
|
self.config.border_color = (!color.is_plain()).then_some(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_records_mut(&mut self) -> &mut NuRecords {
|
pub fn get_records_mut(&mut self) -> &mut NuRecords {
|
||||||
@ -152,21 +181,15 @@ 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: NuTableConfig, termwidth: usize) -> Option<String> {
|
pub fn draw(self, termwidth: usize) -> Option<String> {
|
||||||
build_table(
|
build_table(self, termwidth)
|
||||||
self.data,
|
|
||||||
config,
|
|
||||||
self.alignments,
|
|
||||||
self.styles,
|
|
||||||
termwidth,
|
|
||||||
self.indent,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a total table width.
|
/// Return a total table width.
|
||||||
pub fn total_width(&self, config: &NuTableConfig) -> usize {
|
pub fn total_width(&self) -> usize {
|
||||||
let config = get_config(&config.theme, false, None);
|
let config = create_config(&self.config.theme, false, None);
|
||||||
let widths = build_width(&self.data, self.indent.0 + self.indent.1);
|
let pad = indent_sum(self.config.indent);
|
||||||
|
let widths = build_width(&self.data, pad);
|
||||||
get_total_width2(&widths, &config)
|
get_total_width2(&widths, &config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,104 +203,144 @@ impl From<Vec<Vec<Text<String>>>> for NuTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Alignments = CellConfiguration<AlignmentHorizontal>;
|
||||||
|
|
||||||
|
type Styles = CellConfiguration<Color>;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
struct CellConfiguration<Value> {
|
||||||
|
data: Value,
|
||||||
|
index: Value,
|
||||||
|
header: Value,
|
||||||
|
columns: HashMap<usize, Value>,
|
||||||
|
cells: HashMap<Position, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NuTableConfig {
|
pub struct TableConfig {
|
||||||
pub theme: TableTheme,
|
theme: TableTheme,
|
||||||
pub trim: TrimStrategy,
|
trim: TrimStrategy,
|
||||||
pub split_color: Option<Style>,
|
border_color: Option<Style>,
|
||||||
pub expand: bool,
|
expand: bool,
|
||||||
pub with_index: bool,
|
structure: TableStructure,
|
||||||
pub with_header: bool,
|
header_on_border: bool,
|
||||||
pub with_footer: bool,
|
indent: TableIndent,
|
||||||
pub header_on_border: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NuTableConfig {
|
#[derive(Debug, Clone)]
|
||||||
fn default() -> Self {
|
struct TableStructure {
|
||||||
|
with_index: bool,
|
||||||
|
with_header: bool,
|
||||||
|
with_footer: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableStructure {
|
||||||
|
fn new(with_index: bool, with_header: bool, with_footer: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
theme: TableTheme::basic(),
|
with_index,
|
||||||
trim: TrimStrategy::truncate(None),
|
with_header,
|
||||||
with_header: false,
|
with_footer,
|
||||||
with_index: false,
|
|
||||||
with_footer: false,
|
|
||||||
expand: false,
|
|
||||||
split_color: None,
|
|
||||||
header_on_border: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_table(
|
fn build_table(mut t: NuTable, termwidth: usize) -> Option<String> {
|
||||||
mut data: NuRecords,
|
if t.count_columns() == 0 || t.count_rows() == 0 {
|
||||||
cfg: NuTableConfig,
|
|
||||||
alignments: Alignments,
|
|
||||||
styles: Styles,
|
|
||||||
termwidth: usize,
|
|
||||||
indent: (usize, usize),
|
|
||||||
) -> Option<String> {
|
|
||||||
if data.count_columns() == 0 || data.count_rows() == 0 {
|
|
||||||
return Some(String::new());
|
return Some(String::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
let pad = indent.0 + indent.1;
|
let widths = table_truncate(&mut t, termwidth)?;
|
||||||
let widths = maybe_truncate_columns(&mut data, &cfg, termwidth, pad);
|
table_insert_footer(&mut t);
|
||||||
|
draw_table(t, widths, termwidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn table_insert_footer(t: &mut NuTable) {
|
||||||
|
if t.config.structure.with_header && t.config.structure.with_footer {
|
||||||
|
duplicate_row(&mut t.data, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn table_truncate(t: &mut NuTable, termwidth: usize) -> Option<Vec<usize>> {
|
||||||
|
let pad = t.config.indent.left + t.config.indent.right;
|
||||||
|
let widths = maybe_truncate_columns(&mut t.data, &t.config, termwidth, pad);
|
||||||
if widths.is_empty() {
|
if widths.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.with_header && cfg.with_footer {
|
Some(widths)
|
||||||
duplicate_row(&mut data, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_table(data, alignments, styles, widths, cfg, termwidth, indent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_table(
|
fn draw_table(t: NuTable, widths: Vec<usize>, termwidth: usize) -> Option<String> {
|
||||||
data: NuRecords,
|
let structure = get_table_structure(&t.data, &t.config);
|
||||||
alignments: Alignments,
|
let sep_color = t.config.border_color;
|
||||||
styles: Styles,
|
let border_header = structure.with_header && t.config.header_on_border;
|
||||||
widths: Vec<usize>,
|
|
||||||
cfg: NuTableConfig,
|
|
||||||
termwidth: usize,
|
|
||||||
indent: (usize, usize),
|
|
||||||
) -> Option<String> {
|
|
||||||
let with_index = cfg.with_index;
|
|
||||||
let with_header = cfg.with_header && data.count_rows() > 1;
|
|
||||||
let with_footer = with_header && cfg.with_footer;
|
|
||||||
let sep_color = cfg.split_color;
|
|
||||||
let border_header = cfg.header_on_border;
|
|
||||||
|
|
||||||
let data: Vec<Vec<_>> = data.into();
|
let data: Vec<Vec<_>> = t.data.into();
|
||||||
let mut table = Builder::from(data).build();
|
let mut table = Builder::from_vec(data).build();
|
||||||
|
|
||||||
set_indent(&mut table, indent.0, indent.1);
|
set_indent(&mut table, t.config.indent);
|
||||||
load_theme(&mut table, &cfg.theme, with_footer, with_header, sep_color);
|
load_theme(&mut table, &t.config.theme, &structure, sep_color);
|
||||||
align_table(&mut table, alignments, with_index, with_header, with_footer);
|
align_table(&mut table, t.alignments, &structure);
|
||||||
colorize_table(&mut table, styles, with_index, with_header, with_footer);
|
colorize_table(&mut table, t.styles, &structure);
|
||||||
|
|
||||||
let pad = indent.0 + indent.1;
|
let pad = indent_sum(t.config.indent);
|
||||||
let width_ctrl = TableWidthCtrl::new(widths, cfg, termwidth, pad);
|
let width_ctrl = WidthCtrl::new(widths, t.config, termwidth, pad);
|
||||||
|
|
||||||
if with_header && border_header {
|
adjust_table(&mut table, width_ctrl, border_header, structure.with_footer);
|
||||||
set_border_head(&mut table, with_footer, width_ctrl);
|
|
||||||
} else {
|
|
||||||
table.with(width_ctrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
table_to_string(table, termwidth)
|
table_to_string(table, termwidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_indent(table: &mut Table, left: usize, right: usize) {
|
fn indent_sum(indent: TableIndent) -> usize {
|
||||||
table.with(Padding::new(left, right, 0, 0));
|
indent.left + indent.right
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_border_head(table: &mut Table, with_footer: bool, wctrl: TableWidthCtrl) {
|
fn get_table_structure(data: &VecRecords<Text<String>>, cfg: &TableConfig) -> TableStructure {
|
||||||
|
let with_index = cfg.structure.with_index;
|
||||||
|
let with_header = cfg.structure.with_header && data.count_rows() > 1;
|
||||||
|
let with_footer = with_header && cfg.structure.with_footer;
|
||||||
|
|
||||||
|
TableStructure::new(with_index, with_header, with_footer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adjust_table(table: &mut Table, width_ctrl: WidthCtrl, border_header: bool, with_footer: bool) {
|
||||||
|
if border_header {
|
||||||
if with_footer {
|
if with_footer {
|
||||||
|
set_border_head_with_footer(table, width_ctrl);
|
||||||
|
} else {
|
||||||
|
set_border_head(table, width_ctrl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
table.with(width_ctrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_indent(table: &mut Table, indent: TableIndent) {
|
||||||
|
table.with(Padding::new(indent.left, indent.right, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_border_head(table: &mut Table, wctrl: WidthCtrl) {
|
||||||
|
let mut row = GetRow(0, Vec::new());
|
||||||
|
let mut row_opts = GetRowSettings(0, AlignmentHorizontal::Left, None);
|
||||||
|
|
||||||
|
table.with(&mut row);
|
||||||
|
table.with(&mut row_opts);
|
||||||
|
|
||||||
|
table.with(
|
||||||
|
Settings::default()
|
||||||
|
.with(strip_color_from_row(0))
|
||||||
|
.with(wctrl)
|
||||||
|
.with(MoveRowNext::new(0, 0))
|
||||||
|
.with(SetLineHeaders::new(0, row.1, row_opts.1, row_opts.2)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_border_head_with_footer(table: &mut Table, wctrl: WidthCtrl) {
|
||||||
|
// note: funnily last and row must be equal at this point but we do not rely on it just in case.
|
||||||
|
|
||||||
let count_rows = table.count_rows();
|
let count_rows = table.count_rows();
|
||||||
let last_row_index = count_rows - 1;
|
let last_row_index = count_rows - 1;
|
||||||
|
|
||||||
// note: funnily last and row must be equal at this point but we do not rely on it just in case.
|
|
||||||
|
|
||||||
let mut first_row = GetRow(0, Vec::new());
|
let mut first_row = GetRow(0, Vec::new());
|
||||||
let mut head_settings = GetRowSettings(0, AlignmentHorizontal::Left, None);
|
let mut head_settings = GetRowSettings(0, AlignmentHorizontal::Left, None);
|
||||||
let mut last_row = GetRow(last_row_index, Vec::new());
|
let mut last_row = GetRow(last_row_index, Vec::new());
|
||||||
@ -294,8 +357,8 @@ fn set_border_head(table: &mut Table, with_footer: bool, wctrl: TableWidthCtrl)
|
|||||||
|
|
||||||
table.with(
|
table.with(
|
||||||
Settings::default()
|
Settings::default()
|
||||||
.with(StripColorFromRow(0))
|
.with(strip_color_from_row(0))
|
||||||
.with(StripColorFromRow(count_rows - 1))
|
.with(strip_color_from_row(count_rows - 1))
|
||||||
.with(wctrl)
|
.with(wctrl)
|
||||||
.with(MoveRowNext::new(0, 0))
|
.with(MoveRowNext::new(0, 0))
|
||||||
.with(MoveRowPrev::new(last_row_index - 1, last_row_index))
|
.with(MoveRowPrev::new(last_row_index - 1, last_row_index))
|
||||||
@ -307,21 +370,6 @@ fn set_border_head(table: &mut Table, with_footer: bool, wctrl: TableWidthCtrl)
|
|||||||
footer_color,
|
footer_color,
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
let mut row = GetRow(0, Vec::new());
|
|
||||||
let mut row_opts = GetRowSettings(0, AlignmentHorizontal::Left, None);
|
|
||||||
|
|
||||||
table.with(&mut row);
|
|
||||||
table.with(&mut row_opts);
|
|
||||||
|
|
||||||
table.with(
|
|
||||||
Settings::default()
|
|
||||||
.with(StripColorFromRow(0))
|
|
||||||
.with(wctrl)
|
|
||||||
.with(MoveRowNext::new(0, 0))
|
|
||||||
.with(SetLineHeaders::new(0, row.1, row_opts.1, row_opts.2)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_to_string(table: Table, termwidth: usize) -> Option<String> {
|
fn table_to_string(table: Table, termwidth: usize) -> Option<String> {
|
||||||
@ -335,15 +383,15 @@ fn table_to_string(table: Table, termwidth: usize) -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TableWidthCtrl {
|
struct WidthCtrl {
|
||||||
width: Vec<usize>,
|
width: Vec<usize>,
|
||||||
cfg: NuTableConfig,
|
cfg: TableConfig,
|
||||||
width_max: usize,
|
width_max: usize,
|
||||||
pad: usize,
|
pad: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableWidthCtrl {
|
impl WidthCtrl {
|
||||||
fn new(width: Vec<usize>, cfg: NuTableConfig, max: usize, pad: usize) -> Self {
|
fn new(width: Vec<usize>, cfg: TableConfig, max: usize, pad: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
width,
|
width,
|
||||||
cfg,
|
cfg,
|
||||||
@ -353,7 +401,7 @@ impl TableWidthCtrl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableOption<NuRecords, ColoredConfig, CompleteDimensionVecRecords<'_>> for TableWidthCtrl {
|
impl TableOption<NuRecords, ColoredConfig, CompleteDimensionVecRecords<'_>> for WidthCtrl {
|
||||||
fn change(
|
fn change(
|
||||||
self,
|
self,
|
||||||
rec: &mut NuRecords,
|
rec: &mut NuRecords,
|
||||||
@ -362,24 +410,24 @@ impl TableOption<NuRecords, ColoredConfig, CompleteDimensionVecRecords<'_>> for
|
|||||||
) {
|
) {
|
||||||
let total_width = get_total_width2(&self.width, cfg);
|
let total_width = get_total_width2(&self.width, cfg);
|
||||||
|
|
||||||
if total_width > self.width_max {
|
let need_truncation = total_width > self.width_max;
|
||||||
let has_header = self.cfg.with_header && rec.count_rows() > 1;
|
if need_truncation {
|
||||||
let trim_as_head = has_header && self.cfg.header_on_border;
|
let has_header = self.cfg.structure.with_header && rec.count_rows() > 1;
|
||||||
|
let as_head = has_header && self.cfg.header_on_border;
|
||||||
|
|
||||||
TableTrim::new(
|
let trim = TableTrim::new(self.width, self.width_max, self.cfg.trim, as_head, self.pad);
|
||||||
self.width,
|
trim.change(rec, cfg, dim);
|
||||||
self.width_max,
|
return;
|
||||||
self.cfg.trim,
|
|
||||||
trim_as_head,
|
|
||||||
self.pad,
|
|
||||||
)
|
|
||||||
.change(rec, cfg, dim);
|
|
||||||
} else if self.cfg.expand && self.width_max > total_width {
|
|
||||||
let opt = (SetDimensions(self.width), Width::increase(self.width_max));
|
|
||||||
TableOption::<VecRecords<_>, _, _>::change(opt, rec, cfg, dim)
|
|
||||||
} else {
|
|
||||||
SetDimensions(self.width).change(rec, cfg, dim);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let need_expansion = self.cfg.expand && self.width_max > total_width;
|
||||||
|
if need_expansion {
|
||||||
|
let opt = (SetDimensions(self.width), Width::increase(self.width_max));
|
||||||
|
TableOption::<VecRecords<_>, _, _>::change(opt, rec, cfg, dim);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDimensions(self.width).change(rec, cfg, dim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,13 +475,13 @@ impl TableOption<NuRecords, ColoredConfig, CompleteDimensionVecRecords<'_>> for
|
|||||||
TrimStrategy::Wrap { try_to_keep_words } => {
|
TrimStrategy::Wrap { try_to_keep_words } => {
|
||||||
let wrap = Width::wrap(self.width_max)
|
let wrap = Width::wrap(self.width_max)
|
||||||
.keep_words(try_to_keep_words)
|
.keep_words(try_to_keep_words)
|
||||||
.priority(PriorityMax);
|
.priority(Priority::max(false));
|
||||||
|
|
||||||
let opt = (SetDimensions(self.width), wrap);
|
let opt = (SetDimensions(self.width), wrap);
|
||||||
TableOption::<NuRecords, _, _>::change(opt, recs, cfg, dims);
|
TableOption::<NuRecords, _, _>::change(opt, recs, cfg, dims);
|
||||||
}
|
}
|
||||||
TrimStrategy::Truncate { suffix } => {
|
TrimStrategy::Truncate { suffix } => {
|
||||||
let mut truncate = Width::truncate(self.width_max).priority(PriorityMax);
|
let mut truncate = Width::truncate(self.width_max).priority(Priority::max(false));
|
||||||
if let Some(suffix) = suffix {
|
if let Some(suffix) = suffix {
|
||||||
truncate = truncate.suffix(suffix).suffix_try_color(true);
|
truncate = truncate.suffix(suffix).suffix_try_color(true);
|
||||||
}
|
}
|
||||||
@ -515,7 +563,7 @@ fn trim_as_header(
|
|||||||
let wrap = Width::wrap(width).keep_words(*try_to_keep_words);
|
let wrap = Width::wrap(width).keep_words(*try_to_keep_words);
|
||||||
|
|
||||||
let opt = Modify::new(Columns::single(i)).with(wrap);
|
let opt = Modify::new(Columns::single(i)).with(wrap);
|
||||||
TableOption::<VecRecords<Text<String>>, _, _>::change(opt, recs, cfg, dims);
|
TableOption::<VecRecords<_>, _, _>::change(opt, recs, cfg, dims);
|
||||||
}
|
}
|
||||||
TrimStrategy::Truncate { suffix } => {
|
TrimStrategy::Truncate { suffix } => {
|
||||||
let mut truncate = Width::truncate(width);
|
let mut truncate = Width::truncate(width);
|
||||||
@ -524,7 +572,7 @@ fn trim_as_header(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let opt = Modify::new(Columns::single(i)).with(truncate);
|
let opt = Modify::new(Columns::single(i)).with(truncate);
|
||||||
TableOption::<VecRecords<Text<String>>, _, _>::change(opt, recs, cfg, dims);
|
TableOption::<VecRecords<_>, _, _>::change(opt, recs, cfg, dims);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,82 +580,75 @@ fn trim_as_header(
|
|||||||
TableOption::change(SetDimensions(widths), recs, cfg, dims);
|
TableOption::change(SetDimensions(widths), recs, cfg, dims);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn align_table(
|
fn align_table(table: &mut Table, alignments: Alignments, structure: &TableStructure) {
|
||||||
table: &mut Table,
|
table.with(AlignmentStrategy::PerLine);
|
||||||
alignments: Alignments,
|
table.with(Alignment::from(alignments.data));
|
||||||
with_index: bool,
|
|
||||||
with_header: bool,
|
|
||||||
with_footer: bool,
|
|
||||||
) {
|
|
||||||
table
|
|
||||||
.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine))
|
|
||||||
.with(SetAlignment(alignments.data, Entity::Global));
|
|
||||||
|
|
||||||
for (column, alignment) in alignments.columns {
|
for (column, alignment) in alignments.columns {
|
||||||
table.with(SetAlignment(alignment, Entity::Column(column)));
|
table.modify(Columns::single(column), Alignment::from(alignment));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (pos, alignment) in alignments.cells {
|
for (pos, alignment) in alignments.cells {
|
||||||
table.with(SetAlignment(alignment, Entity::Cell(pos.0, pos.1)));
|
table.modify(pos, Alignment::from(alignment));
|
||||||
}
|
}
|
||||||
|
|
||||||
if with_header {
|
if structure.with_header {
|
||||||
table.with(SetAlignment(alignments.header, Entity::Row(0)));
|
table.modify(Rows::first(), Alignment::from(alignments.header));
|
||||||
|
|
||||||
if with_footer {
|
if structure.with_footer {
|
||||||
table.with(SetAlignment(
|
table.modify(Rows::last(), Alignment::from(alignments.header));
|
||||||
alignments.header,
|
|
||||||
Entity::Row(table.count_rows() - 1),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if with_index {
|
if structure.with_index {
|
||||||
table.with(SetAlignment(alignments.index, Entity::Column(0)));
|
table.modify(Columns::first(), Alignment::from(alignments.index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn colorize_table(
|
fn colorize_table(table: &mut Table, styles: Styles, structure: &TableStructure) {
|
||||||
table: &mut Table,
|
if !is_color_empty(&styles.data) {
|
||||||
mut styles: Styles,
|
table.with(styles.data);
|
||||||
with_index: bool,
|
|
||||||
with_header: bool,
|
|
||||||
with_footer: bool,
|
|
||||||
) {
|
|
||||||
if with_index {
|
|
||||||
styles.data.insert(Entity::Column(0), styles.index);
|
|
||||||
styles.data_is_set = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if with_header {
|
for (column, color) in styles.columns {
|
||||||
styles.data.insert(Entity::Row(0), styles.header.clone());
|
if !is_color_empty(&color) {
|
||||||
styles.data_is_set = true;
|
table.modify(Columns::single(column), color);
|
||||||
|
|
||||||
if with_footer {
|
|
||||||
let count_rows = table.count_rows();
|
|
||||||
if count_rows > 1 {
|
|
||||||
let last_row = count_rows - 1;
|
|
||||||
styles.data.insert(Entity::Row(last_row), styles.header);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if styles.data_is_set {
|
for (pos, color) in styles.cells {
|
||||||
table.get_config_mut().set_colors(styles.data);
|
if !is_color_empty(&color) {
|
||||||
|
table.modify(pos, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if structure.with_index && !is_color_empty(&styles.index) {
|
||||||
|
table.modify(Columns::first(), styles.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if structure.with_header && !is_color_empty(&styles.header) {
|
||||||
|
table.modify(Rows::first(), styles.header.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if structure.with_footer && !is_color_empty(&styles.header) {
|
||||||
|
table.modify(Rows::last(), styles.header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_theme(
|
fn load_theme(
|
||||||
table: &mut Table,
|
table: &mut Table,
|
||||||
theme: &TableTheme,
|
theme: &TableTheme,
|
||||||
with_footer: bool,
|
structure: &TableStructure,
|
||||||
with_header: bool,
|
|
||||||
sep_color: Option<Style>,
|
sep_color: Option<Style>,
|
||||||
) {
|
) {
|
||||||
let mut theme = theme.get_theme();
|
let mut theme = theme.as_base().clone();
|
||||||
|
|
||||||
if !with_header {
|
if !structure.with_header {
|
||||||
theme.set_horizontal_lines(Default::default());
|
let borders = *theme.get_borders();
|
||||||
|
theme.remove_horizontal_lines();
|
||||||
|
theme.set_borders(borders);
|
||||||
|
} else if structure.with_footer {
|
||||||
|
theme_copy_horizontal_line(&mut theme, 1, table.count_rows() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.with(theme);
|
table.with(theme);
|
||||||
@ -615,29 +656,20 @@ fn load_theme(
|
|||||||
if let Some(style) = sep_color {
|
if let Some(style) = sep_color {
|
||||||
let color = convert_style(style);
|
let color = convert_style(style);
|
||||||
let color = ANSIBuf::from(color);
|
let color = ANSIBuf::from(color);
|
||||||
// todo: use .modify(Segment::all(), color) --> it has this optimization
|
|
||||||
table.get_config_mut().set_border_color_default(color);
|
table.get_config_mut().set_border_color_default(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !with_header {
|
|
||||||
// todo: remove and use theme.remove_horizontal_lines();
|
|
||||||
table.with(RemoveHorizontalLine);
|
|
||||||
} else if with_footer {
|
|
||||||
// todo: remove and set it on theme rather then here...
|
|
||||||
table.with(CopyFirstHorizontalLineAtLast);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_truncate_columns(
|
fn maybe_truncate_columns(
|
||||||
data: &mut NuRecords,
|
data: &mut NuRecords,
|
||||||
cfg: &NuTableConfig,
|
cfg: &TableConfig,
|
||||||
termwidth: usize,
|
termwidth: usize,
|
||||||
pad: usize,
|
pad: usize,
|
||||||
) -> Vec<usize> {
|
) -> Vec<usize> {
|
||||||
const TERMWIDTH_THRESHOLD: usize = 120;
|
const TERMWIDTH_THRESHOLD: usize = 120;
|
||||||
|
|
||||||
let preserve_content = termwidth > TERMWIDTH_THRESHOLD;
|
let preserve_content = termwidth > TERMWIDTH_THRESHOLD;
|
||||||
let has_header = cfg.with_header && data.count_rows() > 1;
|
let has_header = cfg.structure.with_header && data.count_rows() > 1;
|
||||||
let is_header_on_border = has_header && cfg.header_on_border;
|
let is_header_on_border = has_header && cfg.header_on_border;
|
||||||
|
|
||||||
let truncate = if is_header_on_border {
|
let truncate = if is_header_on_border {
|
||||||
@ -661,7 +693,7 @@ fn truncate_columns_by_content(
|
|||||||
const MIN_ACCEPTABLE_WIDTH: usize = 3;
|
const MIN_ACCEPTABLE_WIDTH: usize = 3;
|
||||||
const TRAILING_COLUMN_WIDTH: usize = 5;
|
const TRAILING_COLUMN_WIDTH: usize = 5;
|
||||||
|
|
||||||
let config = get_config(theme, false, None);
|
let config = create_config(theme, false, None);
|
||||||
let mut widths = build_width(&*data, pad);
|
let mut widths = build_width(&*data, pad);
|
||||||
let total_width = get_total_width2(&widths, &config);
|
let total_width = get_total_width2(&widths, &config);
|
||||||
if total_width <= termwidth {
|
if total_width <= termwidth {
|
||||||
@ -741,7 +773,7 @@ fn truncate_columns_by_columns(
|
|||||||
let acceptable_width = 10 + pad;
|
let acceptable_width = 10 + pad;
|
||||||
let trailing_column_width = 3 + pad;
|
let trailing_column_width = 3 + pad;
|
||||||
|
|
||||||
let config = get_config(theme, false, None);
|
let config = create_config(theme, false, None);
|
||||||
let mut widths = build_width(&*data, pad);
|
let mut widths = build_width(&*data, pad);
|
||||||
let total_width = get_total_width2(&widths, &config);
|
let total_width = get_total_width2(&widths, &config);
|
||||||
if total_width <= termwidth {
|
if total_width <= termwidth {
|
||||||
@ -812,7 +844,7 @@ fn truncate_columns_by_head(
|
|||||||
) -> Vec<usize> {
|
) -> Vec<usize> {
|
||||||
const TRAILING_COLUMN_WIDTH: usize = 5;
|
const TRAILING_COLUMN_WIDTH: usize = 5;
|
||||||
|
|
||||||
let config = get_config(theme, false, None);
|
let config = create_config(theme, false, None);
|
||||||
let mut widths = build_width(&*data, pad);
|
let mut widths = build_width(&*data, pad);
|
||||||
let total_width = get_total_width2(&widths, &config);
|
let total_width = get_total_width2(&widths, &config);
|
||||||
if total_width <= termwidth {
|
if total_width <= termwidth {
|
||||||
@ -880,18 +912,6 @@ fn truncate_columns_by_head(
|
|||||||
widths
|
widths
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The same as [`tabled::settings::peaker::PriorityMax`] but prioritizes left columns first in case
|
|
||||||
/// of equal width.
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct PriorityMax;
|
|
||||||
|
|
||||||
impl Peaker for PriorityMax {
|
|
||||||
fn peak(&mut self, _: &[usize], widths: &[usize]) -> Option<usize> {
|
|
||||||
let col = (0..widths.len()).rev().max_by_key(|&i| widths[i]);
|
|
||||||
col.filter(|&col| widths[col] != 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_total_width2(widths: &[usize], cfg: &ColoredConfig) -> usize {
|
fn get_total_width2(widths: &[usize], cfg: &ColoredConfig) -> usize {
|
||||||
let total = widths.iter().sum::<usize>();
|
let total = widths.iter().sum::<usize>();
|
||||||
let countv = cfg.count_vertical(widths.len());
|
let countv = cfg.count_vertical(widths.len());
|
||||||
@ -900,9 +920,10 @@ fn get_total_width2(widths: &[usize], cfg: &ColoredConfig) -> usize {
|
|||||||
total + countv + margin.left.size + margin.right.size
|
total + countv + margin.left.size + margin.right.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config(theme: &TableTheme, with_header: bool, color: Option<Style>) -> ColoredConfig {
|
fn create_config(theme: &TableTheme, with_header: bool, color: Option<Style>) -> ColoredConfig {
|
||||||
|
let structure = TableStructure::new(false, with_header, false);
|
||||||
let mut table = Table::new([[""]]);
|
let mut table = Table::new([[""]]);
|
||||||
load_theme(&mut table, theme, false, with_header, color);
|
load_theme(&mut table, theme, &structure, color);
|
||||||
table.get_config().clone()
|
table.get_config().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -947,14 +968,6 @@ fn convert_alignment(alignment: nu_color_config::Alignment) -> AlignmentHorizont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SetAlignment(AlignmentHorizontal, Entity);
|
|
||||||
|
|
||||||
impl<R, D> TableOption<R, ColoredConfig, D> for SetAlignment {
|
|
||||||
fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
|
|
||||||
cfg.set_alignment_horizontal(self.1, self.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SetDimensions(Vec<usize>);
|
struct SetDimensions(Vec<usize>);
|
||||||
|
|
||||||
impl<R> TableOption<R, ColoredConfig, CompleteDimensionVecRecords<'_>> for SetDimensions {
|
impl<R> TableOption<R, ColoredConfig, CompleteDimensionVecRecords<'_>> for SetDimensions {
|
||||||
@ -1045,16 +1058,8 @@ impl TableOption<NuRecords, ColoredConfig, CompleteDimensionVecRecords<'_>> for
|
|||||||
cfg: &mut ColoredConfig,
|
cfg: &mut ColoredConfig,
|
||||||
dims: &mut CompleteDimensionVecRecords<'_>,
|
dims: &mut CompleteDimensionVecRecords<'_>,
|
||||||
) {
|
) {
|
||||||
let mut columns = self.columns;
|
let widths = match dims.get_widths() {
|
||||||
|
Some(widths) => widths,
|
||||||
match dims.get_widths() {
|
|
||||||
Some(widths) => {
|
|
||||||
columns = columns
|
|
||||||
.into_iter()
|
|
||||||
.zip(widths.iter().cloned()) // it must be always safe to do
|
|
||||||
.map(|(s, width)| Truncate::truncate(&s, width).into_owned())
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
// we don't have widths cached; which means that NO width adjustments were done
|
// we don't have widths cached; which means that NO width adjustments were done
|
||||||
// which means we are OK to leave columns as they are.
|
// which means we are OK to leave columns as they are.
|
||||||
@ -1065,15 +1070,21 @@ impl TableOption<NuRecords, ColoredConfig, CompleteDimensionVecRecords<'_>> for
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
set_column_names(
|
let columns: Vec<_> = self
|
||||||
recs,
|
.columns
|
||||||
cfg,
|
.into_iter()
|
||||||
dims,
|
.zip(widths.iter().cloned()) // it must be always safe to do
|
||||||
columns,
|
.map(|(s, width)| Truncate::truncate(&s, width).into_owned())
|
||||||
self.line,
|
.collect();
|
||||||
self.alignment,
|
|
||||||
self.color,
|
let mut names = ColumnNames::new(columns)
|
||||||
)
|
.line(self.line)
|
||||||
|
.alignment(Alignment::from(self.alignment));
|
||||||
|
if let Some(color) = self.color {
|
||||||
|
names = names.color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
names.change(recs, cfg, dims);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hint_change(&self) -> Option<Entity> {
|
fn hint_change(&self) -> Option<Entity> {
|
||||||
@ -1142,26 +1153,23 @@ fn row_shift_next(recs: &mut NuRecords, cfg: &mut ColoredConfig, row: usize, lin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !has_line {
|
recs.remove_row(row);
|
||||||
let _ = remove_row(recs, row);
|
|
||||||
let count_rows = recs.count_rows();
|
let count_rows = recs.count_rows();
|
||||||
|
|
||||||
shift_alignments_down(cfg, row, count_rows, count_columns);
|
shift_alignments_down(cfg, row, count_rows, count_columns);
|
||||||
shift_colors_down(cfg, row, count_rows, count_columns);
|
shift_colors_down(cfg, row, count_rows, count_columns);
|
||||||
|
|
||||||
|
if !has_line {
|
||||||
shift_lines_up(cfg, count_rows, &[line + 1]);
|
shift_lines_up(cfg, count_rows, &[line + 1]);
|
||||||
shift_lines_up(cfg, count_rows, &[count_rows]);
|
} else {
|
||||||
return;
|
remove_lines(cfg, count_rows, &[line + 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = remove_row(recs, row);
|
|
||||||
let count_rows = recs.count_rows();
|
|
||||||
shift_alignments_down(cfg, row, count_rows, count_columns);
|
|
||||||
shift_colors_down(cfg, row, count_rows, count_columns);
|
|
||||||
remove_lines(cfg, count_rows, &[line + 1]);
|
|
||||||
shift_lines_up(cfg, count_rows, &[count_rows]);
|
shift_lines_up(cfg, count_rows, &[count_rows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn row_shift_prev(recs: &mut NuRecords, cfg: &mut ColoredConfig, row: usize, line: usize) {
|
fn row_shift_prev(recs: &mut NuRecords, cfg: &mut ColoredConfig, row: usize, line: usize) {
|
||||||
let count_rows = recs.count_rows();
|
let mut count_rows = recs.count_rows();
|
||||||
let count_columns = recs.count_columns();
|
let count_columns = recs.count_columns();
|
||||||
let has_line = cfg.has_horizontal(line, count_rows);
|
let has_line = cfg.has_horizontal(line, count_rows);
|
||||||
let has_prev_line = cfg.has_horizontal(line - 1, count_rows);
|
let has_prev_line = cfg.has_horizontal(line - 1, count_rows);
|
||||||
@ -1169,14 +1177,14 @@ fn row_shift_prev(recs: &mut NuRecords, cfg: &mut ColoredConfig, row: usize, lin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recs.remove_row(row);
|
||||||
|
|
||||||
if !has_line {
|
if !has_line {
|
||||||
let _ = remove_row(recs, row);
|
|
||||||
// shift_lines_down(table, &[line - 1]);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = remove_row(recs, row);
|
count_rows -= 1;
|
||||||
let count_rows = count_rows - 1;
|
|
||||||
shift_alignments_down(cfg, row, count_rows, count_columns);
|
shift_alignments_down(cfg, row, count_rows, count_columns);
|
||||||
shift_colors_down(cfg, row, count_rows, count_columns);
|
shift_colors_down(cfg, row, count_rows, count_columns);
|
||||||
remove_lines(cfg, count_rows, &[line - 1]);
|
remove_lines(cfg, count_rows, &[line - 1]);
|
||||||
@ -1230,73 +1238,17 @@ fn shift_lines_up(cfg: &mut ColoredConfig, count_rows: usize, lines: &[usize]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_column_names(
|
fn theme_copy_horizontal_line(theme: &mut tabled::settings::Theme, from: usize, to: usize) {
|
||||||
records: &mut NuRecords,
|
if let Some(line) = theme.get_horizontal_line(from) {
|
||||||
cfg: &mut ColoredConfig,
|
theme.insert_horizontal_line(to, *line);
|
||||||
dims: &mut CompleteDimensionVecRecords<'_>,
|
|
||||||
head: Vec<String>,
|
|
||||||
line: usize,
|
|
||||||
align: AlignmentHorizontal,
|
|
||||||
color: Option<Color>,
|
|
||||||
) {
|
|
||||||
let mut names = ColumnNames::new(head)
|
|
||||||
.line(line)
|
|
||||||
.alignment(Alignment::from(align));
|
|
||||||
if let Some(color) = color {
|
|
||||||
names = names.color(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnNames::change(names, records, cfg, dims)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_row(recs: &mut NuRecords, row: usize) -> Vec<String> {
|
|
||||||
let count_columns = recs.count_columns();
|
|
||||||
let columns = (0..count_columns)
|
|
||||||
.map(|column| recs.get_text((row, column)).to_owned())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
recs.remove_row(row);
|
|
||||||
|
|
||||||
columns
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo; use Format?
|
|
||||||
struct StripColorFromRow(usize);
|
|
||||||
|
|
||||||
impl TableOption<NuRecords, ColoredConfig, CompleteDimensionVecRecords<'_>> for StripColorFromRow {
|
|
||||||
fn change(
|
|
||||||
self,
|
|
||||||
recs: &mut NuRecords,
|
|
||||||
_: &mut ColoredConfig,
|
|
||||||
_: &mut CompleteDimensionVecRecords<'_>,
|
|
||||||
) {
|
|
||||||
for cell in &mut recs[self.0] {
|
|
||||||
*cell = Text::new(strip_ansi_unlikely(cell.as_ref()).into_owned());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RemoveHorizontalLine;
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn strip_color_from_row(row: usize) -> ModifyList<Row, FormatContent<fn(&str) -> String>> {
|
||||||
impl<D> TableOption<NuRecords, ColoredConfig, D> for RemoveHorizontalLine {
|
fn foo(s: &str) -> String {
|
||||||
fn change(self, recs: &mut NuRecords, cfg: &mut ColoredConfig, _: &mut D) {
|
strip_ansi_unlikely(s).into_owned()
|
||||||
cfg.remove_horizontal_line(1, recs.count_rows());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CopyFirstHorizontalLineAtLast;
|
|
||||||
|
|
||||||
impl<D> TableOption<NuRecords, ColoredConfig, D> for CopyFirstHorizontalLineAtLast {
|
|
||||||
fn change(self, recs: &mut NuRecords, cfg: &mut ColoredConfig, _: &mut D) {
|
|
||||||
if recs.count_rows() <= 2 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let line = match cfg.get_horizontal_line(1) {
|
|
||||||
Some(line) => *line,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
cfg.insert_horizontal_line(recs.count_rows() - 1, line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Modify::new(Rows::single(row)).with(Format::content(foo))
|
||||||
}
|
}
|
||||||
|
@ -1,67 +1,63 @@
|
|||||||
use tabled::settings::style::{HorizontalLine, Style};
|
use tabled::settings::{
|
||||||
use tabled::settings::themes::Theme;
|
style::{HorizontalLine, Style},
|
||||||
|
themes::Theme,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TableTheme {
|
pub struct TableTheme {
|
||||||
theme: Theme,
|
base: Theme,
|
||||||
full_theme: Theme,
|
full: Theme,
|
||||||
has_inner: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableTheme {
|
impl TableTheme {
|
||||||
pub fn new(theme: impl Into<Theme>, full_theme: impl Into<Theme>, has_inner: bool) -> Self {
|
fn new(base: impl Into<Theme>, full: impl Into<Theme>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
theme: theme.into(),
|
base: base.into(),
|
||||||
full_theme: full_theme.into(),
|
full: full.into(),
|
||||||
has_inner,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn basic() -> TableTheme {
|
pub fn basic() -> TableTheme {
|
||||||
Self::new(Style::ascii(), Style::ascii(), true)
|
Self::new(Style::ascii(), Style::ascii())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thin() -> TableTheme {
|
pub fn thin() -> TableTheme {
|
||||||
Self::new(Style::modern(), Style::modern(), true)
|
Self::new(Style::modern(), Style::modern())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn light() -> TableTheme {
|
pub fn light() -> TableTheme {
|
||||||
let mut theme = Theme::from_style(Style::blank());
|
let mut theme = Theme::from_style(Style::blank());
|
||||||
theme.insert_horizontal_line(1, HorizontalLine::new('─').intersection('─'));
|
theme.insert_horizontal_line(1, HorizontalLine::new('─').intersection('─'));
|
||||||
|
|
||||||
Self::new(theme, Style::modern(), true)
|
Self::new(theme, Style::modern())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn psql() -> TableTheme {
|
pub fn psql() -> TableTheme {
|
||||||
Self::new(Style::psql(), Style::psql(), true)
|
Self::new(Style::psql(), Style::psql())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn markdown() -> TableTheme {
|
pub fn markdown() -> TableTheme {
|
||||||
Self::new(Style::markdown(), Style::markdown(), true)
|
Self::new(Style::markdown(), Style::markdown())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dots() -> TableTheme {
|
pub fn dots() -> TableTheme {
|
||||||
let theme = Style::dots().remove_horizontal();
|
let theme = Style::dots().remove_horizontal();
|
||||||
|
|
||||||
Self::new(theme, Style::dots(), true)
|
Self::new(theme, Style::dots())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restructured() -> TableTheme {
|
pub fn restructured() -> TableTheme {
|
||||||
Self::new(
|
Self::new(Style::re_structured_text(), Style::re_structured_text())
|
||||||
Style::re_structured_text(),
|
|
||||||
Style::re_structured_text(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ascii_rounded() -> TableTheme {
|
pub fn ascii_rounded() -> TableTheme {
|
||||||
Self::new(Style::ascii_rounded(), Style::ascii_rounded(), true)
|
Self::new(Style::ascii_rounded(), Style::ascii_rounded())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn basic_compact() -> TableTheme {
|
pub fn basic_compact() -> TableTheme {
|
||||||
let theme = Style::ascii().remove_horizontal();
|
let theme = Style::ascii().remove_horizontal();
|
||||||
|
|
||||||
Self::new(theme, Style::ascii(), true)
|
Self::new(theme, Style::ascii())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compact() -> TableTheme {
|
pub fn compact() -> TableTheme {
|
||||||
@ -72,7 +68,7 @@ impl TableTheme {
|
|||||||
.remove_horizontal()
|
.remove_horizontal()
|
||||||
.horizontals([(1, hline)]);
|
.horizontals([(1, hline)]);
|
||||||
|
|
||||||
Self::new(theme, Style::modern(), true)
|
Self::new(theme, Style::modern())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_love() -> TableTheme {
|
pub fn with_love() -> TableTheme {
|
||||||
@ -99,7 +95,7 @@ impl TableTheme {
|
|||||||
.intersection_left('❤')
|
.intersection_left('❤')
|
||||||
.intersection('❤');
|
.intersection('❤');
|
||||||
|
|
||||||
Self::new(theme, full_theme, true)
|
Self::new(theme, full_theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compact_double() -> TableTheme {
|
pub fn compact_double() -> TableTheme {
|
||||||
@ -112,7 +108,7 @@ impl TableTheme {
|
|||||||
.remove_horizontal()
|
.remove_horizontal()
|
||||||
.horizontals([(1, hline)]);
|
.horizontals([(1, hline)]);
|
||||||
|
|
||||||
Self::new(theme, Style::extended(), true)
|
Self::new(theme, Style::extended())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rounded() -> TableTheme {
|
pub fn rounded() -> TableTheme {
|
||||||
@ -122,7 +118,7 @@ impl TableTheme {
|
|||||||
.corner_bottom_left('╰')
|
.corner_bottom_left('╰')
|
||||||
.corner_bottom_right('╯');
|
.corner_bottom_right('╯');
|
||||||
|
|
||||||
Self::new(Style::rounded(), full, true)
|
Self::new(Style::rounded(), full)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reinforced() -> TableTheme {
|
pub fn reinforced() -> TableTheme {
|
||||||
@ -132,7 +128,7 @@ impl TableTheme {
|
|||||||
.corner_bottom_left('┗')
|
.corner_bottom_left('┗')
|
||||||
.corner_bottom_right('┛');
|
.corner_bottom_right('┛');
|
||||||
|
|
||||||
Self::new(full.clone().remove_horizontal(), full, true)
|
Self::new(full.clone().remove_horizontal(), full)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn heavy() -> TableTheme {
|
pub fn heavy() -> TableTheme {
|
||||||
@ -157,38 +153,18 @@ impl TableTheme {
|
|||||||
.intersection_right('┫')
|
.intersection_right('┫')
|
||||||
.intersection('╋');
|
.intersection('╋');
|
||||||
|
|
||||||
Self::new(theme, full, true)
|
Self::new(theme, full)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn none() -> TableTheme {
|
pub fn none() -> TableTheme {
|
||||||
Self::new(Style::blank(), Style::blank(), true)
|
Self::new(Style::blank(), Style::blank())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_top(&self) -> bool {
|
pub fn as_full(&self) -> &Theme {
|
||||||
self.theme.borders_has_top()
|
&self.full
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_left(&self) -> bool {
|
pub fn as_base(&self) -> &Theme {
|
||||||
self.theme.borders_has_left()
|
&self.base
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_right(&self) -> bool {
|
|
||||||
self.theme.borders_has_right()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_inner(&self) -> bool {
|
|
||||||
self.has_inner
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_horizontals(&self) -> bool {
|
|
||||||
self.full_theme.get_borders().has_horizontal()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_theme_full(&self) -> Theme {
|
|
||||||
self.full_theme.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_theme(&self) -> Theme {
|
|
||||||
self.theme.clone()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,48 +1,40 @@
|
|||||||
|
use nu_ansi_term::Style;
|
||||||
|
use nu_color_config::StyleComputer;
|
||||||
|
use nu_protocol::{Config, Value};
|
||||||
|
use nu_utils::SharedCow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{get_index_style, load_theme, nu_value_to_string_clean},
|
common::{get_index_style, load_theme, nu_value_to_string_clean},
|
||||||
StringResult, TableOpts, UnstructuredTable,
|
StringResult, TableOpts, UnstructuredTable,
|
||||||
};
|
};
|
||||||
use nu_color_config::StyleComputer;
|
|
||||||
use nu_protocol::{Config, Record, TableMode, Value};
|
|
||||||
use nu_utils::SharedCow;
|
|
||||||
|
|
||||||
pub struct CollapsedTable;
|
pub struct CollapsedTable;
|
||||||
|
|
||||||
impl CollapsedTable {
|
impl CollapsedTable {
|
||||||
pub fn build(value: Value, opts: TableOpts<'_>) -> StringResult {
|
pub fn build(value: Value, opts: TableOpts<'_>) -> StringResult {
|
||||||
collapsed_table(
|
collapsed_table(value, opts)
|
||||||
value,
|
|
||||||
opts.config,
|
|
||||||
opts.width,
|
|
||||||
opts.style_computer,
|
|
||||||
opts.mode,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collapsed_table(
|
fn collapsed_table(mut value: Value, opts: TableOpts<'_>) -> StringResult {
|
||||||
mut value: Value,
|
colorize_value(&mut value, opts.config, &opts.style_computer);
|
||||||
config: &Config,
|
|
||||||
term_width: usize,
|
|
||||||
style_computer: &StyleComputer,
|
|
||||||
mode: TableMode,
|
|
||||||
) -> StringResult {
|
|
||||||
colorize_value(&mut value, config, style_computer);
|
|
||||||
|
|
||||||
let theme = load_theme(mode);
|
let mut table = UnstructuredTable::new(value, opts.config);
|
||||||
let mut table = UnstructuredTable::new(value, config);
|
|
||||||
let is_empty = table.truncate(&theme, term_width);
|
let theme = load_theme(opts.mode);
|
||||||
|
let is_empty = table.truncate(&theme, opts.width);
|
||||||
if is_empty {
|
if is_empty {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let indent = (config.table.padding.left, config.table.padding.right);
|
let table = table.draw(&theme, opts.config.table.padding, &opts.style_computer);
|
||||||
let table = table.draw(style_computer, &theme, indent);
|
|
||||||
|
|
||||||
Ok(Some(table))
|
Ok(Some(table))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn colorize_value(value: &mut Value, config: &Config, style_computer: &StyleComputer) {
|
fn colorize_value(value: &mut Value, config: &Config, style_computer: &StyleComputer) {
|
||||||
|
// todo: Remove recursion?
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
Value::Record { ref mut val, .. } => {
|
Value::Record { ref mut val, .. } => {
|
||||||
let style = get_index_style(style_computer);
|
let style = get_index_style(style_computer);
|
||||||
@ -55,13 +47,11 @@ fn colorize_value(value: &mut Value, config: &Config, style_computer: &StyleComp
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(mut header, mut val)| {
|
.map(|(mut header, mut val)| {
|
||||||
colorize_value(&mut val, config, style_computer);
|
colorize_value(&mut val, config, style_computer);
|
||||||
|
header = colorize_text(&header, style.color_style).unwrap_or(header);
|
||||||
|
|
||||||
if let Some(color) = style.color_style {
|
|
||||||
header = color.paint(header).to_string();
|
|
||||||
}
|
|
||||||
(header, val)
|
(header, val)
|
||||||
})
|
})
|
||||||
.collect::<Record>(),
|
.collect(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
@ -71,11 +61,19 @@ fn colorize_value(value: &mut Value, config: &Config, style_computer: &StyleComp
|
|||||||
}
|
}
|
||||||
value => {
|
value => {
|
||||||
let (text, style) = nu_value_to_string_clean(value, config, style_computer);
|
let (text, style) = nu_value_to_string_clean(value, config, style_computer);
|
||||||
if let Some(color) = style.color_style {
|
if let Some(text) = colorize_text(&text, style.color_style) {
|
||||||
let text = color.paint(text).to_string();
|
*value = Value::string(text, value.span());
|
||||||
let span = value.span();
|
|
||||||
*value = Value::string(text, span);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn colorize_text(text: &str, color: Option<Style>) -> Option<String> {
|
||||||
|
if let Some(color) = color {
|
||||||
|
if !color.is_plain() {
|
||||||
|
return Some(color.paint(text).to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
|
use std::{cmp::max, collections::HashMap};
|
||||||
|
|
||||||
|
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
||||||
|
use nu_engine::column::get_columns;
|
||||||
|
use nu_protocol::{Config, Record, ShellError, Span, Value};
|
||||||
|
|
||||||
|
use tabled::grid::config::Position;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{
|
common::{
|
||||||
create_nu_table_config, error_sign, get_header_style, get_index_style, load_theme,
|
check_value, configure_table, error_sign, get_header_style, get_index_style, load_theme,
|
||||||
nu_value_to_string, nu_value_to_string_clean, nu_value_to_string_colored, wrap_text,
|
nu_value_to_string, nu_value_to_string_clean, nu_value_to_string_colored, wrap_text,
|
||||||
NuText, StringResult, TableResult, INDEX_COLUMN_NAME,
|
NuText, StringResult, TableResult, INDEX_COLUMN_NAME,
|
||||||
},
|
},
|
||||||
string_width,
|
string_width,
|
||||||
types::has_index,
|
types::has_index,
|
||||||
NuTable, NuTableCell, TableOpts, TableOutput,
|
NuRecordsValue, NuTable, TableOpts, TableOutput,
|
||||||
};
|
};
|
||||||
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
|
||||||
use nu_engine::column::get_columns;
|
|
||||||
use nu_protocol::{Config, Record, ShellError, Span, Value};
|
|
||||||
use std::{cmp::max, collections::HashMap};
|
|
||||||
use tabled::grid::config::Position;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ExpandedTable {
|
pub struct ExpandedTable {
|
||||||
@ -31,25 +34,32 @@ impl ExpandedTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_value(self, item: &Value, opts: TableOpts<'_>) -> NuText {
|
pub fn build_value(self, item: &Value, opts: TableOpts<'_>) -> NuText {
|
||||||
let cell = expanded_table_entry2(item, Cfg { opts, format: self });
|
let cfg = Cfg { opts, format: self };
|
||||||
|
let cell = expand_entry(item, cfg);
|
||||||
(cell.text, cell.style)
|
(cell.text, cell.style)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_map(self, record: &Record, opts: TableOpts<'_>) -> StringResult {
|
pub fn build_map(self, record: &Record, opts: TableOpts<'_>) -> StringResult {
|
||||||
expanded_table_kv(record, Cfg { opts, format: self }).map(|cell| cell.map(|cell| cell.text))
|
let cfg = Cfg { opts, format: self };
|
||||||
|
expanded_table_kv(record, cfg).map(|cell| cell.map(|cell| cell.text))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_list(self, vals: &[Value], opts: TableOpts<'_>) -> StringResult {
|
pub fn build_list(self, vals: &[Value], opts: TableOpts<'_>) -> StringResult {
|
||||||
let cfg = Cfg {
|
let cfg = Cfg { opts, format: self };
|
||||||
opts: opts.clone(),
|
let output = expand_list(vals, cfg.clone())?;
|
||||||
format: self,
|
let mut output = match output {
|
||||||
};
|
|
||||||
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.width, &opts)
|
configure_table(
|
||||||
|
&mut output,
|
||||||
|
cfg.opts.config,
|
||||||
|
&cfg.opts.style_computer,
|
||||||
|
cfg.opts.mode,
|
||||||
|
);
|
||||||
|
|
||||||
|
maybe_expand_table(output, cfg.opts.width)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +102,7 @@ impl CellOutput {
|
|||||||
|
|
||||||
type CellResult = Result<Option<CellOutput>, ShellError>;
|
type CellResult = Result<Option<CellOutput>, ShellError>;
|
||||||
|
|
||||||
fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||||
const PADDING_SPACE: usize = 2;
|
const PADDING_SPACE: usize = 2;
|
||||||
const SPLIT_LINE_SPACE: usize = 1;
|
const SPLIT_LINE_SPACE: usize = 1;
|
||||||
const ADDITIONAL_CELL_SPACE: usize = PADDING_SPACE + SPLIT_LINE_SPACE;
|
const ADDITIONAL_CELL_SPACE: usize = PADDING_SPACE + SPLIT_LINE_SPACE;
|
||||||
@ -133,15 +143,12 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
|
|
||||||
if with_index {
|
if with_index {
|
||||||
if with_header {
|
if with_header {
|
||||||
data[0].push(NuTableCell::exact(String::from("#"), 1, vec![]));
|
data[0].push(NuRecordsValue::exact(String::from("#"), 1, vec![]));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (row, item) in input.iter().enumerate() {
|
for (row, item) in input.iter().enumerate() {
|
||||||
cfg.opts.signals.check(cfg.opts.span)?;
|
cfg.opts.signals.check(cfg.opts.span)?;
|
||||||
|
check_value(item)?;
|
||||||
if let Value::Error { error, .. } = item {
|
|
||||||
return Err(*error.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = row + row_offset;
|
let index = row + row_offset;
|
||||||
let text = item
|
let text = item
|
||||||
@ -152,7 +159,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
.unwrap_or_else(|| index.to_string());
|
.unwrap_or_else(|| index.to_string());
|
||||||
|
|
||||||
let row = row + with_header as usize;
|
let row = row + with_header as usize;
|
||||||
let value = NuTableCell::new(text);
|
let value = NuRecordsValue::new(text);
|
||||||
data[row].push(value);
|
data[row].push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,13 +184,10 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
|
|
||||||
for (row, item) in input.iter().enumerate() {
|
for (row, item) in input.iter().enumerate() {
|
||||||
cfg.opts.signals.check(cfg.opts.span)?;
|
cfg.opts.signals.check(cfg.opts.span)?;
|
||||||
|
check_value(item)?;
|
||||||
|
|
||||||
if let Value::Error { error, .. } = item {
|
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available_width);
|
||||||
return Err(*error.clone());
|
let mut cell = expand_entry(item, inner_cfg);
|
||||||
}
|
|
||||||
|
|
||||||
let inner_cfg = update_config(cfg.clone(), available_width);
|
|
||||||
let mut cell = expanded_table_entry2(item, inner_cfg);
|
|
||||||
|
|
||||||
let value_width = string_width(&cell.text);
|
let value_width = string_width(&cell.text);
|
||||||
if value_width > available_width {
|
if value_width > available_width {
|
||||||
@ -195,7 +199,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
cell.text = wrap_text(&cell.text, available_width, cfg.opts.config);
|
cell.text = wrap_text(&cell.text, available_width, cfg.opts.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = NuTableCell::new(cell.text);
|
let value = NuRecordsValue::new(cell.text);
|
||||||
data[row].push(value);
|
data[row].push(value);
|
||||||
data_styles.insert((row, with_index as usize), cell.style);
|
data_styles.insert((row, with_index as usize), cell.style);
|
||||||
|
|
||||||
@ -203,8 +207,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1);
|
table.set_indent(cfg.opts.config.table.padding);
|
||||||
table.set_index_style(get_index_style(cfg.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, rows_count)));
|
return Ok(Some(TableOutput::new(table, false, with_index, rows_count)));
|
||||||
@ -266,13 +270,10 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
|
|
||||||
for (row, item) in input.iter().enumerate() {
|
for (row, item) in input.iter().enumerate() {
|
||||||
cfg.opts.signals.check(cfg.opts.span)?;
|
cfg.opts.signals.check(cfg.opts.span)?;
|
||||||
|
check_value(item)?;
|
||||||
|
|
||||||
if let Value::Error { error, .. } = item {
|
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available);
|
||||||
return Err(*error.clone());
|
let mut cell = expand_entry_with_header(item, &header, inner_cfg);
|
||||||
}
|
|
||||||
|
|
||||||
let inner_cfg = update_config(cfg.clone(), available);
|
|
||||||
let mut cell = expanded_table_entry(item, header.as_str(), inner_cfg);
|
|
||||||
|
|
||||||
let mut value_width = string_width(&cell.text);
|
let mut value_width = string_width(&cell.text);
|
||||||
if value_width > available {
|
if value_width > available {
|
||||||
@ -285,14 +286,14 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
|
|
||||||
column_width = max(column_width, value_width);
|
column_width = max(column_width, value_width);
|
||||||
|
|
||||||
let value = NuTableCell::new(cell.text);
|
let value = NuRecordsValue::new(cell.text);
|
||||||
data[row + 1].push(value);
|
data[row + 1].push(value);
|
||||||
data_styles.insert((row + 1, col + with_index as usize), cell.style);
|
data_styles.insert((row + 1, col + with_index as usize), cell.style);
|
||||||
|
|
||||||
column_rows = column_rows.saturating_add(cell.size);
|
column_rows = column_rows.saturating_add(cell.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
let head_cell = NuTableCell::new(header);
|
let head_cell = NuRecordsValue::new(header);
|
||||||
data[0].push(head_cell);
|
data[0].push(head_cell);
|
||||||
|
|
||||||
if column_width > available {
|
if column_width > available {
|
||||||
@ -354,7 +355,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
|
|
||||||
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 = NuTableCell::exact(String::from("..."), 3, vec![]);
|
let shift = NuRecordsValue::exact(String::from("..."), 3, vec![]);
|
||||||
for row in &mut data {
|
for row in &mut data {
|
||||||
row.push(shift.clone());
|
row.push(shift.clone());
|
||||||
}
|
}
|
||||||
@ -364,9 +365,9 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.set_index_style(get_index_style(cfg.opts.style_computer));
|
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||||
table.set_header_style(get_header_style(cfg.opts.style_computer));
|
table.set_header_style(get_header_style(&cfg.opts.style_computer));
|
||||||
table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1);
|
table.set_indent(cfg.opts.config.table.padding);
|
||||||
set_data_styles(&mut table, data_styles);
|
set_data_styles(&mut table, data_styles);
|
||||||
|
|
||||||
Ok(Some(TableOutput::new(table, true, with_index, rows_count)))
|
Ok(Some(TableOutput::new(table, true, with_index, rows_count)))
|
||||||
@ -374,13 +375,15 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
|||||||
|
|
||||||
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
||||||
let theme = load_theme(cfg.opts.mode);
|
let theme = load_theme(cfg.opts.mode);
|
||||||
|
let theme = theme.as_base();
|
||||||
let key_width = record
|
let key_width = record
|
||||||
.columns()
|
.columns()
|
||||||
.map(|col| string_width(col))
|
.map(|col| string_width(col))
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let count_borders =
|
let count_borders = theme.borders_has_vertical() as usize
|
||||||
theme.has_inner() as usize + theme.has_right() as usize + theme.has_left() as usize;
|
+ theme.borders_has_right() as usize
|
||||||
|
+ theme.borders_has_left() as usize;
|
||||||
let padding = 2;
|
let padding = 2;
|
||||||
if key_width + count_borders + padding + padding > cfg.opts.width {
|
if key_width + count_borders + padding + padding > cfg.opts.width {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@ -394,7 +397,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
|||||||
for (key, value) in record {
|
for (key, value) in record {
|
||||||
cfg.opts.signals.check(cfg.opts.span)?;
|
cfg.opts.signals.check(cfg.opts.span)?;
|
||||||
|
|
||||||
let cell = match expand_table_value(value, value_width, &cfg)? {
|
let cell = match expand_value(value, value_width, &cfg)? {
|
||||||
Some(val) => val,
|
Some(val) => val,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
@ -403,12 +406,13 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
|||||||
// we could use Padding for it but,
|
// we could use Padding for it but,
|
||||||
// the easiest way to do so is just push a new_line char before
|
// the easiest way to do so is just push a new_line char before
|
||||||
let mut key = key.to_owned();
|
let mut key = key.to_owned();
|
||||||
if !key.is_empty() && cell.is_expanded && theme.has_top() {
|
let is_key_on_next_line = !key.is_empty() && cell.is_expanded && theme.borders_has_top();
|
||||||
|
if is_key_on_next_line {
|
||||||
key.insert(0, '\n');
|
key.insert(0, '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = NuTableCell::new(key);
|
let key = NuRecordsValue::new(key);
|
||||||
let val = NuTableCell::new(cell.text);
|
let val = NuRecordsValue::new(cell.text);
|
||||||
let row = vec![key, val];
|
let row = vec![key, val];
|
||||||
|
|
||||||
data.push(row);
|
data.push(row);
|
||||||
@ -418,35 +422,38 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
|||||||
|
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.set_index_style(get_key_style(&cfg));
|
table.set_index_style(get_key_style(&cfg));
|
||||||
table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1);
|
table.set_indent(cfg.opts.config.table.padding);
|
||||||
|
|
||||||
let out = TableOutput::new(table, false, true, count_rows);
|
let mut out = TableOutput::new(table, false, true, count_rows);
|
||||||
|
|
||||||
maybe_expand_table(out, cfg.opts.width, &cfg.opts)
|
configure_table(
|
||||||
|
&mut out,
|
||||||
|
cfg.opts.config,
|
||||||
|
&cfg.opts.style_computer,
|
||||||
|
cfg.opts.mode,
|
||||||
|
);
|
||||||
|
|
||||||
|
maybe_expand_table(out, cfg.opts.width)
|
||||||
.map(|value| value.map(|value| CellOutput::clean(value, count_rows, false)))
|
.map(|value| value.map(|value| CellOutput::clean(value, count_rows, false)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// the flag is used as an optimization to not do `value.lines().count()` search.
|
// the flag is used as an optimization to not do `value.lines().count()` search.
|
||||||
fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellResult {
|
fn expand_value(value: &Value, width: usize, cfg: &Cfg<'_>) -> CellResult {
|
||||||
let is_limited = matches!(cfg.format.expand_limit, Some(0));
|
if is_limit_reached(cfg) {
|
||||||
if is_limited {
|
let value = value_to_string_clean(value, cfg);
|
||||||
return Ok(Some(CellOutput::clean(
|
return Ok(Some(CellOutput::clean(value, 1, false)));
|
||||||
value_to_string_clean(value, cfg),
|
|
||||||
1,
|
|
||||||
false,
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
match value {
|
match value {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
let inner_cfg = update_config(dive_options(cfg, span), value_width);
|
let inner_cfg = cfg_expand_reset_table(cfg_expand_next_level(cfg.clone(), span), width);
|
||||||
let table = expanded_table_list(vals, inner_cfg)?;
|
let table = expand_list(vals, inner_cfg)?;
|
||||||
|
|
||||||
match table {
|
match table {
|
||||||
Some(out) => {
|
Some(mut out) => {
|
||||||
let cfg = create_table_cfg(cfg, &out);
|
table_apply_config(&mut out, cfg);
|
||||||
let value = out.table.draw(cfg, value_width);
|
let value = out.table.draw(width);
|
||||||
match value {
|
match value {
|
||||||
Some(value) => Ok(Some(CellOutput::clean(value, out.count_rows, true))),
|
Some(value) => Ok(Some(CellOutput::clean(value, out.count_rows, true))),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@ -454,132 +461,117 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// it means that the list is empty
|
// it means that the list is empty
|
||||||
Ok(Some(CellOutput::text(value_to_wrapped_string(
|
let value = value_to_wrapped_string(value, cfg, width);
|
||||||
value,
|
Ok(Some(CellOutput::text(value)))
|
||||||
cfg,
|
|
||||||
value_width,
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
if record.is_empty() {
|
if record.is_empty() {
|
||||||
// Like list case return styled string instead of empty value
|
// Like list case return styled string instead of empty value
|
||||||
return Ok(Some(CellOutput::text(value_to_wrapped_string(
|
let value = value_to_wrapped_string(value, cfg, width);
|
||||||
value,
|
return Ok(Some(CellOutput::text(value)));
|
||||||
cfg,
|
|
||||||
value_width,
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let inner_cfg = update_config(dive_options(cfg, span), value_width);
|
let inner_cfg = cfg_expand_reset_table(cfg_expand_next_level(cfg.clone(), span), width);
|
||||||
let result = expanded_table_kv(record, inner_cfg)?;
|
let result = expanded_table_kv(record, inner_cfg)?;
|
||||||
match result {
|
match result {
|
||||||
Some(result) => Ok(Some(CellOutput::clean(result.text, result.size, true))),
|
Some(result) => Ok(Some(CellOutput::clean(result.text, result.size, true))),
|
||||||
None => Ok(Some(CellOutput::text(value_to_wrapped_string(
|
None => {
|
||||||
value,
|
let value = value_to_wrapped_string(value, cfg, width);
|
||||||
cfg,
|
Ok(Some(CellOutput::text(value)))
|
||||||
value_width,
|
|
||||||
)))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Ok(Some(CellOutput::text(value_to_wrapped_string_clean(
|
}
|
||||||
value,
|
_ => {
|
||||||
cfg,
|
let value = value_to_wrapped_string_clean(value, cfg, width);
|
||||||
value_width,
|
Ok(Some(CellOutput::text(value)))
|
||||||
)))),
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_key_style(cfg: &Cfg<'_>) -> TextStyle {
|
fn get_key_style(cfg: &Cfg<'_>) -> TextStyle {
|
||||||
get_header_style(cfg.opts.style_computer).alignment(Alignment::Left)
|
get_header_style(&cfg.opts.style_computer).alignment(Alignment::Left)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expanded_table_entry(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOutput {
|
fn expand_entry_with_header(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOutput {
|
||||||
match item {
|
match item {
|
||||||
Value::Record { val, .. } => match val.get(header) {
|
Value::Record { val, .. } => match val.get(header) {
|
||||||
Some(val) => expanded_table_entry2(val, cfg),
|
Some(val) => expand_entry(val, cfg),
|
||||||
None => CellOutput::styled(error_sign(cfg.opts.style_computer)),
|
None => CellOutput::styled(error_sign(&cfg.opts.style_computer)),
|
||||||
},
|
},
|
||||||
_ => expanded_table_entry2(item, cfg),
|
_ => expand_entry(item, cfg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expanded_table_entry2(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||||
let is_limit_reached = matches!(cfg.format.expand_limit, Some(0));
|
if is_limit_reached(&cfg) {
|
||||||
if is_limit_reached {
|
let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||||
return CellOutput::styled(nu_value_to_string_clean(
|
return CellOutput::styled(value);
|
||||||
item,
|
|
||||||
cfg.opts.config,
|
|
||||||
cfg.opts.style_computer,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = item.span();
|
let span = item.span();
|
||||||
match &item {
|
match &item {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
if record.is_empty() {
|
if record.is_empty() {
|
||||||
return CellOutput::styled(nu_value_to_string(
|
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||||
item,
|
return CellOutput::styled(value);
|
||||||
cfg.opts.config,
|
|
||||||
cfg.opts.style_computer,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// we verify what is the structure of a Record cause it might represent
|
// we verify what is the structure of a Record cause it might represent
|
||||||
let inner_cfg = dive_options(&cfg, span);
|
let inner_cfg = cfg_expand_next_level(cfg.clone(), span);
|
||||||
let table = expanded_table_kv(record, inner_cfg);
|
let table = expanded_table_kv(record, inner_cfg);
|
||||||
|
|
||||||
match table {
|
match table {
|
||||||
Ok(Some(table)) => table,
|
Ok(Some(table)) => table,
|
||||||
_ => CellOutput::styled(nu_value_to_string(
|
_ => {
|
||||||
item,
|
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||||
cfg.opts.config,
|
CellOutput::styled(value)
|
||||||
cfg.opts.style_computer,
|
}
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
if cfg.format.flatten && is_simple_list(vals) {
|
if cfg.format.flatten && is_simple_list(vals) {
|
||||||
return CellOutput::styled(value_list_to_string(
|
let value = list_to_string(
|
||||||
vals,
|
vals,
|
||||||
cfg.opts.config,
|
cfg.opts.config,
|
||||||
cfg.opts.style_computer,
|
&cfg.opts.style_computer,
|
||||||
&cfg.format.flatten_sep,
|
&cfg.format.flatten_sep,
|
||||||
));
|
);
|
||||||
|
return CellOutput::text(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
let inner_cfg = dive_options(&cfg, span);
|
let inner_cfg = cfg_expand_next_level(cfg.clone(), span);
|
||||||
let table = expanded_table_list(vals, inner_cfg);
|
let table = expand_list(vals, inner_cfg);
|
||||||
|
|
||||||
let out = match table {
|
let mut out = match table {
|
||||||
Ok(Some(out)) => out,
|
Ok(Some(out)) => out,
|
||||||
_ => {
|
_ => {
|
||||||
return CellOutput::styled(nu_value_to_string(
|
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||||
item,
|
return CellOutput::styled(value);
|
||||||
cfg.opts.config,
|
|
||||||
cfg.opts.style_computer,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let table_config = create_table_cfg(&cfg, &out);
|
table_apply_config(&mut out, &cfg);
|
||||||
let table = out.table.draw(table_config, usize::MAX);
|
|
||||||
|
let table = out.table.draw(usize::MAX);
|
||||||
match table {
|
match table {
|
||||||
Some(table) => CellOutput::clean(table, out.count_rows, false),
|
Some(table) => CellOutput::clean(table, out.count_rows, false),
|
||||||
None => CellOutput::styled(nu_value_to_string(
|
None => {
|
||||||
item,
|
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||||
cfg.opts.config,
|
CellOutput::styled(value)
|
||||||
cfg.opts.style_computer,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => CellOutput::styled(nu_value_to_string_clean(
|
|
||||||
item,
|
|
||||||
cfg.opts.config,
|
|
||||||
cfg.opts.style_computer,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||||
|
CellOutput::styled(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_limit_reached(cfg: &Cfg<'_>) -> bool {
|
||||||
|
matches!(cfg.format.expand_limit, Some(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_simple_list(vals: &[Value]) -> bool {
|
fn is_simple_list(vals: &[Value]) -> bool {
|
||||||
@ -587,49 +579,39 @@ fn is_simple_list(vals: &[Value]) -> bool {
|
|||||||
.all(|v| !matches!(v, Value::Record { .. } | Value::List { .. }))
|
.all(|v| !matches!(v, Value::Record { .. } | Value::List { .. }))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_list_to_string(
|
fn list_to_string(
|
||||||
vals: &[Value],
|
vals: &[Value],
|
||||||
config: &Config,
|
config: &Config,
|
||||||
style_computer: &StyleComputer,
|
style_computer: &StyleComputer,
|
||||||
flatten_sep: &str,
|
sep: &str,
|
||||||
) -> NuText {
|
) -> String {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
for (i, value) in vals.iter().enumerate() {
|
for (i, value) in vals.iter().enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
buf.push_str(flatten_sep);
|
buf.push_str(sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = nu_value_to_string_clean(value, config, style_computer).0;
|
let (text, _) = nu_value_to_string_clean(value, config, style_computer);
|
||||||
buf.push_str(&text);
|
buf.push_str(&text);
|
||||||
}
|
}
|
||||||
|
|
||||||
(buf, TextStyle::default())
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dive_options<'b>(cfg: &Cfg<'b>, span: Span) -> Cfg<'b> {
|
fn maybe_expand_table(mut out: TableOutput, term_width: usize) -> StringResult {
|
||||||
let mut cfg = cfg.clone();
|
let total_width = out.table.total_width();
|
||||||
cfg.opts.span = span;
|
|
||||||
if let Some(deep) = cfg.format.expand_limit.as_mut() {
|
|
||||||
*deep -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
fn maybe_expand_table(out: TableOutput, term_width: usize, opts: &TableOpts<'_>) -> StringResult {
|
|
||||||
let mut table_config =
|
|
||||||
create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode);
|
|
||||||
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.expand = true;
|
out.table.set_strategy(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(out.table.draw(table_config, term_width))
|
let table = out.table.draw(term_width);
|
||||||
|
|
||||||
|
Ok(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
|
fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
|
||||||
@ -638,22 +620,21 @@ fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_table_cfg(cfg: &Cfg<'_>, out: &TableOutput) -> crate::NuTableConfig {
|
fn table_apply_config(out: &mut TableOutput, cfg: &Cfg<'_>) {
|
||||||
create_nu_table_config(
|
configure_table(
|
||||||
cfg.opts.config,
|
|
||||||
cfg.opts.style_computer,
|
|
||||||
out,
|
out,
|
||||||
false,
|
cfg.opts.config,
|
||||||
|
&cfg.opts.style_computer,
|
||||||
cfg.opts.mode,
|
cfg.opts.mode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_to_string(value: &Value, cfg: &Cfg<'_>) -> String {
|
fn value_to_string(value: &Value, cfg: &Cfg<'_>) -> String {
|
||||||
nu_value_to_string(value, cfg.opts.config, cfg.opts.style_computer).0
|
nu_value_to_string(value, cfg.opts.config, &cfg.opts.style_computer).0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_to_string_clean(value: &Value, cfg: &Cfg<'_>) -> String {
|
fn value_to_string_clean(value: &Value, cfg: &Cfg<'_>) -> String {
|
||||||
nu_value_to_string_clean(value, cfg.opts.config, cfg.opts.style_computer).0
|
nu_value_to_string_clean(value, cfg.opts.config, &cfg.opts.style_computer).0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_to_wrapped_string(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> String {
|
fn value_to_wrapped_string(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> String {
|
||||||
@ -661,13 +642,21 @@ fn value_to_wrapped_string(value: &Value, cfg: &Cfg<'_>, value_width: usize) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn value_to_wrapped_string_clean(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> String {
|
fn value_to_wrapped_string_clean(value: &Value, cfg: &Cfg<'_>, value_width: usize) -> String {
|
||||||
let text = nu_value_to_string_colored(value, cfg.opts.config, cfg.opts.style_computer);
|
let text = nu_value_to_string_colored(value, cfg.opts.config, &cfg.opts.style_computer);
|
||||||
wrap_text(&text, value_width, cfg.opts.config)
|
wrap_text(&text, value_width, cfg.opts.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_config(cfg: Cfg<'_>, width: usize) -> Cfg<'_> {
|
fn cfg_expand_next_level(mut cfg: Cfg<'_>, span: Span) -> Cfg<'_> {
|
||||||
let mut inner_cfg = cfg.clone();
|
cfg.opts.span = span;
|
||||||
inner_cfg.opts.width = width;
|
if let Some(deep) = cfg.format.expand_limit.as_mut() {
|
||||||
inner_cfg.opts.index_offset = 0;
|
*deep -= 1
|
||||||
inner_cfg
|
}
|
||||||
|
|
||||||
|
cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cfg_expand_reset_table(mut cfg: Cfg<'_>, width: usize) -> Cfg<'_> {
|
||||||
|
cfg.opts.width = width;
|
||||||
|
cfg.opts.index_offset = 0;
|
||||||
|
cfg
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
use super::has_index;
|
|
||||||
use crate::{
|
|
||||||
clean_charset, colorize_space,
|
|
||||||
common::{
|
|
||||||
create_nu_table_config, get_empty_style, get_header_style, get_index_style,
|
|
||||||
get_value_style, nu_value_to_string_colored, NuText, INDEX_COLUMN_NAME,
|
|
||||||
},
|
|
||||||
NuTable, NuTableCell, StringResult, TableOpts, TableOutput, TableResult,
|
|
||||||
};
|
|
||||||
use nu_color_config::TextStyle;
|
use nu_color_config::TextStyle;
|
||||||
use nu_engine::column::get_columns;
|
use nu_engine::column::get_columns;
|
||||||
use nu_protocol::{Config, Record, ShellError, Value};
|
use nu_protocol::{Config, Record, ShellError, Value};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
clean_charset, colorize_space,
|
||||||
|
common::{
|
||||||
|
check_value, configure_table, get_empty_style, get_header_style, get_index_style,
|
||||||
|
get_value_style, nu_value_to_string_colored, NuText, INDEX_COLUMN_NAME,
|
||||||
|
},
|
||||||
|
types::has_index,
|
||||||
|
NuRecordsValue, NuTable, StringResult, TableOpts, TableOutput, TableResult,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct JustTable;
|
pub struct JustTable;
|
||||||
|
|
||||||
impl JustTable {
|
impl JustTable {
|
||||||
pub fn table(input: &[Value], opts: TableOpts<'_>) -> StringResult {
|
pub fn table(input: &[Value], opts: TableOpts<'_>) -> StringResult {
|
||||||
create_table(input, opts)
|
list_table(input, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
pub fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
||||||
@ -23,32 +24,32 @@ impl JustTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
|
fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
|
||||||
match table(input, &opts)? {
|
let mut out = match create_table(input, &opts)? {
|
||||||
Some(mut out) => {
|
Some(out) => out,
|
||||||
let left = opts.config.table.padding.left;
|
None => return Ok(None),
|
||||||
let right = opts.config.table.padding.right;
|
};
|
||||||
out.table.set_indent(left, right);
|
|
||||||
|
|
||||||
colorize_space(out.table.get_records_mut(), opts.style_computer);
|
out.table.set_indent(opts.config.table.padding);
|
||||||
|
|
||||||
let table_config =
|
colorize_space(out.table.get_records_mut(), &opts.style_computer);
|
||||||
create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode);
|
|
||||||
Ok(out.table.draw(table_config, opts.width))
|
configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
|
||||||
}
|
let table = out.table.draw(opts.width);
|
||||||
None => Ok(None),
|
|
||||||
}
|
Ok(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
||||||
let mut data = vec![Vec::with_capacity(2); record.len()];
|
let mut data = vec![Vec::with_capacity(2); record.len()];
|
||||||
|
|
||||||
for ((column, value), row) in record.iter().zip(data.iter_mut()) {
|
for ((column, value), row) in record.iter().zip(data.iter_mut()) {
|
||||||
opts.signals.check(opts.span)?;
|
opts.signals.check(opts.span)?;
|
||||||
|
|
||||||
let value = nu_value_to_string_colored(value, opts.config, opts.style_computer);
|
let key = NuRecordsValue::new(column.to_string());
|
||||||
|
|
||||||
let key = NuTableCell::new(column.to_string());
|
let value = nu_value_to_string_colored(value, opts.config, &opts.style_computer);
|
||||||
let value = NuTableCell::new(value);
|
let value = NuRecordsValue::new(value);
|
||||||
|
|
||||||
row.push(key);
|
row.push(key);
|
||||||
row.push(value);
|
row.push(value);
|
||||||
@ -56,125 +57,144 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
|||||||
|
|
||||||
let mut table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.set_index_style(TextStyle::default_field());
|
table.set_index_style(TextStyle::default_field());
|
||||||
let count_rows = table.count_rows();
|
table.set_indent(opts.config.table.padding);
|
||||||
|
|
||||||
let mut out = TableOutput::new(table, false, true, count_rows);
|
let mut out = TableOutput::from_table(table, false, true);
|
||||||
|
configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
|
||||||
let left = opts.config.table.padding.left;
|
let table = out.table.draw(opts.width);
|
||||||
let right = opts.config.table.padding.right;
|
|
||||||
out.table.set_indent(left, right);
|
|
||||||
|
|
||||||
let table_config =
|
|
||||||
create_nu_table_config(opts.config, opts.style_computer, &out, false, opts.mode);
|
|
||||||
let table = out.table.draw(table_config, opts.width);
|
|
||||||
|
|
||||||
Ok(table)
|
Ok(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
|
fn create_table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut headers = get_columns(input);
|
let headers = get_columns(input);
|
||||||
let with_index = has_index(opts, &headers);
|
let with_index = has_index(opts, &headers);
|
||||||
|
let with_header = !headers.is_empty();
|
||||||
let row_offset = opts.index_offset;
|
let row_offset = opts.index_offset;
|
||||||
|
|
||||||
let with_header = !headers.is_empty();
|
let table = match (with_header, with_index) {
|
||||||
if !with_header {
|
(true, true) => create_table_with_header_and_index(input, headers, row_offset, opts)?,
|
||||||
let table = to_table_with_no_header(input, with_index, row_offset, opts)?;
|
(true, false) => create_table_with_header(input, headers, opts)?,
|
||||||
let table = table.map(|table| {
|
(false, true) => create_table_with_no_header_and_index(input, row_offset, opts)?,
|
||||||
let count_rows = table.count_rows();
|
(false, false) => create_table_with_no_header(input, opts)?,
|
||||||
TableOutput::new(table, false, with_index, count_rows)
|
};
|
||||||
});
|
|
||||||
return Ok(table);
|
|
||||||
}
|
|
||||||
|
|
||||||
if with_header && with_index {
|
let table = table.map(|table| TableOutput::from_table(table, with_header, with_index));
|
||||||
headers.insert(0, "#".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// The header with the INDEX is removed from the table headers since
|
|
||||||
// it is added to the natural table index
|
|
||||||
let headers: Vec<_> = headers
|
|
||||||
.into_iter()
|
|
||||||
.filter(|header| header != INDEX_COLUMN_NAME)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let table = to_table_with_header(input, &headers, with_index, row_offset, opts)?;
|
|
||||||
let table = table.map(|table| {
|
|
||||||
let count_rows = table.count_rows();
|
|
||||||
TableOutput::new(table, true, with_index, count_rows)
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(table)
|
Ok(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_table_with_header(
|
fn create_table_with_header(
|
||||||
input: &[Value],
|
input: &[Value],
|
||||||
headers: &[String],
|
headers: Vec<String>,
|
||||||
with_index: bool,
|
|
||||||
row_offset: usize,
|
|
||||||
opts: &TableOpts<'_>,
|
opts: &TableOpts<'_>,
|
||||||
) -> Result<Option<NuTable>, ShellError> {
|
) -> Result<Option<NuTable>, ShellError> {
|
||||||
|
let headers = collect_headers(headers, false);
|
||||||
|
|
||||||
let count_rows = input.len() + 1;
|
let count_rows = input.len() + 1;
|
||||||
let count_columns = headers.len();
|
let count_columns = headers.len();
|
||||||
let mut table = NuTable::new(count_rows, count_columns);
|
let mut table = NuTable::new(count_rows, count_columns);
|
||||||
table.set_header_style(get_header_style(opts.style_computer));
|
|
||||||
table.set_index_style(get_index_style(opts.style_computer));
|
|
||||||
|
|
||||||
for (i, text) in headers.iter().enumerate() {
|
table.set_header_style(get_header_style(&opts.style_computer));
|
||||||
table.insert((0, i), text.to_owned());
|
table.set_index_style(get_index_style(&opts.style_computer));
|
||||||
}
|
|
||||||
|
table.set_row(0, headers.clone());
|
||||||
|
|
||||||
for (row, item) in input.iter().enumerate() {
|
for (row, item) in input.iter().enumerate() {
|
||||||
opts.signals.check(opts.span)?;
|
opts.signals.check(opts.span)?;
|
||||||
|
check_value(item)?;
|
||||||
|
|
||||||
if let Value::Error { error, .. } = item {
|
for (col, header) in headers.iter().enumerate() {
|
||||||
return Err(*error.clone());
|
let (text, style) = get_string_value_with_header(item, header.as_ref(), opts);
|
||||||
}
|
|
||||||
|
|
||||||
if with_index {
|
let pos = (row + 1, col);
|
||||||
let text = get_table_row_index(item, opts.config, row, row_offset);
|
table.insert(pos, text);
|
||||||
table.insert((row + 1, 0), text);
|
table.insert_style(pos, style);
|
||||||
}
|
|
||||||
|
|
||||||
let skip_index = usize::from(with_index);
|
|
||||||
for (col, header) in headers.iter().enumerate().skip(skip_index) {
|
|
||||||
let (text, style) = get_string_value_with_header(item, header, opts);
|
|
||||||
|
|
||||||
table.insert((row + 1, col), text);
|
|
||||||
table.insert_style((row + 1, col), style);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(table))
|
Ok(Some(table))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_table_with_no_header(
|
fn create_table_with_header_and_index(
|
||||||
input: &[Value],
|
input: &[Value],
|
||||||
with_index: bool,
|
headers: Vec<String>,
|
||||||
row_offset: usize,
|
row_offset: usize,
|
||||||
opts: &TableOpts<'_>,
|
opts: &TableOpts<'_>,
|
||||||
) -> Result<Option<NuTable>, ShellError> {
|
) -> Result<Option<NuTable>, ShellError> {
|
||||||
let mut table = NuTable::new(input.len(), with_index as usize + 1);
|
let headers = collect_headers(headers, true);
|
||||||
table.set_index_style(get_index_style(opts.style_computer));
|
|
||||||
|
let count_rows = input.len() + 1;
|
||||||
|
let count_columns = headers.len();
|
||||||
|
let mut table = NuTable::new(count_rows, count_columns);
|
||||||
|
|
||||||
|
table.set_header_style(get_header_style(&opts.style_computer));
|
||||||
|
table.set_index_style(get_index_style(&opts.style_computer));
|
||||||
|
|
||||||
|
table.set_row(0, headers.clone());
|
||||||
|
|
||||||
for (row, item) in input.iter().enumerate() {
|
for (row, item) in input.iter().enumerate() {
|
||||||
opts.signals.check(opts.span)?;
|
opts.signals.check(opts.span)?;
|
||||||
|
check_value(item)?;
|
||||||
|
|
||||||
if let Value::Error { error, .. } = item {
|
|
||||||
return Err(*error.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if with_index {
|
|
||||||
let text = get_table_row_index(item, opts.config, row, row_offset);
|
let text = get_table_row_index(item, opts.config, row, row_offset);
|
||||||
table.insert((row, 0), text);
|
table.insert((row + 1, 0), text);
|
||||||
|
|
||||||
|
for (col, header) in headers.iter().enumerate().skip(1) {
|
||||||
|
let (text, style) = get_string_value_with_header(item, header.as_ref(), opts);
|
||||||
|
|
||||||
|
let pos = (row + 1, col);
|
||||||
|
table.insert(pos, text);
|
||||||
|
table.insert_style(pos, style);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(table))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_table_with_no_header(
|
||||||
|
input: &[Value],
|
||||||
|
opts: &TableOpts<'_>,
|
||||||
|
) -> Result<Option<NuTable>, ShellError> {
|
||||||
|
let mut table = NuTable::new(input.len(), 1);
|
||||||
|
table.set_index_style(get_index_style(&opts.style_computer));
|
||||||
|
|
||||||
|
for (row, item) in input.iter().enumerate() {
|
||||||
|
opts.signals.check(opts.span)?;
|
||||||
|
check_value(item)?;
|
||||||
|
|
||||||
let (text, style) = get_string_value(item, opts);
|
let (text, style) = get_string_value(item, opts);
|
||||||
|
|
||||||
let pos = (row, with_index as usize);
|
let pos = (row, 0);
|
||||||
|
table.insert(pos, text);
|
||||||
|
table.insert_style(pos, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(table))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_table_with_no_header_and_index(
|
||||||
|
input: &[Value],
|
||||||
|
row_offset: usize,
|
||||||
|
opts: &TableOpts<'_>,
|
||||||
|
) -> Result<Option<NuTable>, ShellError> {
|
||||||
|
let mut table = NuTable::new(input.len(), 1 + 1);
|
||||||
|
table.set_index_style(get_index_style(&opts.style_computer));
|
||||||
|
|
||||||
|
for (row, item) in input.iter().enumerate() {
|
||||||
|
opts.signals.check(opts.span)?;
|
||||||
|
check_value(item)?;
|
||||||
|
|
||||||
|
let text = get_table_row_index(item, opts.config, row, row_offset);
|
||||||
|
table.insert((row, 0), text);
|
||||||
|
|
||||||
|
let (text, style) = get_string_value(item, opts);
|
||||||
|
|
||||||
|
let pos = (row, 1);
|
||||||
table.insert(pos, text);
|
table.insert(pos, text);
|
||||||
table.insert_style(pos, style);
|
table.insert_style(pos, style);
|
||||||
}
|
}
|
||||||
@ -186,16 +206,17 @@ fn get_string_value_with_header(item: &Value, header: &str, opts: &TableOpts) ->
|
|||||||
match item {
|
match item {
|
||||||
Value::Record { val, .. } => match val.get(header) {
|
Value::Record { val, .. } => match val.get(header) {
|
||||||
Some(value) => get_string_value(value, opts),
|
Some(value) => get_string_value(value, opts),
|
||||||
None => get_empty_style(opts.style_computer),
|
None => get_empty_style(&opts.style_computer),
|
||||||
},
|
},
|
||||||
value => get_string_value(value, opts),
|
value => get_string_value(value, opts),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_string_value(item: &Value, opts: &TableOpts) -> 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 { .. });
|
|
||||||
if is_string_value {
|
let is_string = matches!(item, Value::String { .. });
|
||||||
|
if is_string {
|
||||||
text = clean_charset(&text);
|
text = clean_charset(&text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,3 +232,24 @@ fn get_table_row_index(item: &Value, config: &Config, row: usize, offset: usize)
|
|||||||
_ => (row + offset).to_string(),
|
_ => (row + offset).to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn collect_headers(headers: Vec<String>, index: bool) -> Vec<NuRecordsValue> {
|
||||||
|
// The header with the INDEX is removed from the table headers since
|
||||||
|
// it is added to the natural table index
|
||||||
|
|
||||||
|
if !index {
|
||||||
|
headers
|
||||||
|
.into_iter()
|
||||||
|
.filter(|header| header != INDEX_COLUMN_NAME)
|
||||||
|
.map(NuRecordsValue::new)
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
let mut v = Vec::with_capacity(headers.len() + 1);
|
||||||
|
v.insert(0, NuRecordsValue::new("#".into()));
|
||||||
|
for text in headers {
|
||||||
|
v.push(NuRecordsValue::new(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,9 +12,14 @@ pub use expanded::ExpandedTable;
|
|||||||
pub use general::JustTable;
|
pub use general::JustTable;
|
||||||
|
|
||||||
pub struct TableOutput {
|
pub struct TableOutput {
|
||||||
|
/// A table structure.
|
||||||
pub table: NuTable,
|
pub table: NuTable,
|
||||||
|
/// A flag whether a header was present in the table.
|
||||||
pub with_header: bool,
|
pub with_header: bool,
|
||||||
|
/// A flag whether a index was present in the table.
|
||||||
pub with_index: bool,
|
pub with_index: bool,
|
||||||
|
/// The value may be bigger then table.count_rows(),
|
||||||
|
/// Specifically in case of expanded table we collect the whole structure size here.
|
||||||
pub count_rows: usize,
|
pub count_rows: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,40 +32,43 @@ impl TableOutput {
|
|||||||
count_rows,
|
count_rows,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn from_table(table: NuTable, with_header: bool, with_index: bool) -> Self {
|
||||||
|
let count_rows = table.count_rows();
|
||||||
|
Self::new(table, with_header, with_index, count_rows)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TableOpts<'a> {
|
pub struct TableOpts<'a> {
|
||||||
signals: &'a Signals,
|
pub signals: &'a Signals,
|
||||||
config: &'a Config,
|
pub config: &'a Config,
|
||||||
style_computer: &'a StyleComputer<'a>,
|
pub style_computer: std::rc::Rc<StyleComputer<'a>>,
|
||||||
span: Span,
|
pub span: Span,
|
||||||
width: usize,
|
pub width: usize,
|
||||||
indent: (usize, usize),
|
pub mode: TableMode,
|
||||||
mode: TableMode,
|
pub index_offset: usize,
|
||||||
index_offset: usize,
|
pub index_remove: bool,
|
||||||
index_remove: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TableOpts<'a> {
|
impl<'a> TableOpts<'a> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
style_computer: &'a StyleComputer<'a>,
|
style_computer: StyleComputer<'a>,
|
||||||
signals: &'a Signals,
|
signals: &'a Signals,
|
||||||
span: Span,
|
span: Span,
|
||||||
width: usize,
|
width: usize,
|
||||||
indent: (usize, usize),
|
|
||||||
mode: TableMode,
|
mode: TableMode,
|
||||||
index_offset: usize,
|
index_offset: usize,
|
||||||
index_remove: bool,
|
index_remove: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let style_computer = std::rc::Rc::new(style_computer);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
signals,
|
signals,
|
||||||
config,
|
config,
|
||||||
style_computer,
|
style_computer,
|
||||||
span,
|
span,
|
||||||
indent,
|
|
||||||
width,
|
width,
|
||||||
mode,
|
mode,
|
||||||
index_offset,
|
index_offset,
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
use crate::{string_width, string_wrap, TableTheme};
|
|
||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_protocol::{Config, Record, Span, Value};
|
use nu_protocol::{Config, Record, Span, TableIndent, Value};
|
||||||
|
|
||||||
use tabled::{
|
use tabled::{
|
||||||
grid::{
|
grid::{
|
||||||
ansi::{ANSIBuf, ANSIStr},
|
ansi::ANSIStr,
|
||||||
config::{AlignmentHorizontal, Borders, CompactMultilineConfig},
|
config::{Borders, CompactMultilineConfig},
|
||||||
dimension::{DimensionPriority, PoolTableDimension},
|
dimension::{DimensionPriority, PoolTableDimension},
|
||||||
},
|
},
|
||||||
settings::{Color, Padding, TableOption, Theme},
|
settings::{Alignment, Color, Padding, TableOption},
|
||||||
tables::{PoolTable, TableValue},
|
tables::{PoolTable, TableValue},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{is_color_empty, string_width, string_wrap, TableTheme};
|
||||||
|
|
||||||
/// UnstructuredTable has a recursive table representation of nu_protocol::Value.
|
/// UnstructuredTable has a recursive table representation of nu_protocol::Value.
|
||||||
///
|
///
|
||||||
/// It doesn't support alignment and a proper width control.
|
/// It doesn't support alignment and a proper width control (although it's possible to achieve).
|
||||||
pub struct UnstructuredTable {
|
pub struct UnstructuredTable {
|
||||||
value: TableValue,
|
value: TableValue,
|
||||||
}
|
}
|
||||||
@ -26,7 +28,7 @@ impl UnstructuredTable {
|
|||||||
|
|
||||||
pub fn truncate(&mut self, theme: &TableTheme, width: usize) -> bool {
|
pub fn truncate(&mut self, theme: &TableTheme, width: usize) -> bool {
|
||||||
let mut available = width;
|
let mut available = width;
|
||||||
let has_vertical = theme.has_left();
|
let has_vertical = theme.as_full().borders_has_left();
|
||||||
if has_vertical {
|
if has_vertical {
|
||||||
available = available.saturating_sub(2);
|
available = available.saturating_sub(2);
|
||||||
}
|
}
|
||||||
@ -34,55 +36,34 @@ impl UnstructuredTable {
|
|||||||
truncate_table_value(&mut self.value, has_vertical, available).is_none()
|
truncate_table_value(&mut self.value, has_vertical, available).is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(
|
pub fn draw(self, theme: &TableTheme, indent: TableIndent, style: &StyleComputer) -> String {
|
||||||
self,
|
build_table(self.value, style, theme, indent)
|
||||||
style_computer: &StyleComputer,
|
|
||||||
theme: &TableTheme,
|
|
||||||
indent: (usize, usize),
|
|
||||||
) -> String {
|
|
||||||
build_table(self.value, style_computer, theme, indent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_table(
|
fn build_table(
|
||||||
val: TableValue,
|
val: TableValue,
|
||||||
style_computer: &StyleComputer,
|
style: &StyleComputer,
|
||||||
theme: &TableTheme,
|
theme: &TableTheme,
|
||||||
indent: (usize, usize),
|
indent: TableIndent,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut table = PoolTable::from(val);
|
let mut table = PoolTable::from(val);
|
||||||
|
|
||||||
let mut theme = theme.get_theme_full();
|
let mut theme = theme.as_full().clone();
|
||||||
theme.set_horizontal_lines(Default::default());
|
theme.set_horizontal_lines(Default::default());
|
||||||
|
|
||||||
table.with(Padding::new(indent.0, indent.1, 0, 0));
|
table.with(Padding::new(indent.left, indent.right, 0, 0));
|
||||||
table.with(SetRawStyle(theme));
|
table.with(*theme.get_borders());
|
||||||
table.with(SetAlignment(AlignmentHorizontal::Left));
|
table.with(Alignment::left());
|
||||||
table.with(PoolTableDimension::new(
|
table.with(PoolTableDimension::new(
|
||||||
DimensionPriority::Last,
|
DimensionPriority::Last,
|
||||||
DimensionPriority::Last,
|
DimensionPriority::Last,
|
||||||
));
|
));
|
||||||
|
|
||||||
// color_config closures for "separator" are just given a null.
|
if let Some(color) = get_border_color(style) {
|
||||||
let color = style_computer.compute("separator", &Value::nothing(Span::unknown()));
|
if !is_color_empty(&color) {
|
||||||
let color = color.paint(" ").to_string();
|
return build_table_with_border_color(table, color);
|
||||||
if let Ok(color) = Color::try_from(color) {
|
}
|
||||||
// # SAFETY
|
|
||||||
//
|
|
||||||
// It's perfectly save to do cause table does not store the reference internally.
|
|
||||||
// We just need this unsafe section to cope with some limitations of [`PoolTable`].
|
|
||||||
// Mitigation of this is definitely on a todo list.
|
|
||||||
|
|
||||||
let color: ANSIBuf = color.into();
|
|
||||||
let prefix = color.get_prefix();
|
|
||||||
let suffix = color.get_suffix();
|
|
||||||
let prefix: &'static str = unsafe { std::mem::transmute(prefix) };
|
|
||||||
let suffix: &'static str = unsafe { std::mem::transmute(suffix) };
|
|
||||||
|
|
||||||
table.with(SetBorderColor(ANSIStr::new(prefix, suffix)));
|
|
||||||
let table = table.to_string();
|
|
||||||
|
|
||||||
return table;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table.to_string()
|
table.to_string()
|
||||||
@ -99,76 +80,74 @@ fn convert_nu_value_to_table_value(value: Value, config: &Config) -> TableValue
|
|||||||
build_vertical_array(vals, config)
|
build_vertical_array(vals, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value => {
|
value => build_string_value(value, config),
|
||||||
let mut text = value.to_abbreviated_string(config);
|
|
||||||
if string_width(&text) > 50 {
|
|
||||||
text = string_wrap(&text, 30, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
TableValue::Cell(text)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_vertical_map(record: Record, config: &Config) -> TableValue {
|
fn build_string_value(value: Value, config: &Config) -> TableValue {
|
||||||
let mut rows = Vec::with_capacity(record.len());
|
const MAX_STRING_WIDTH: usize = 50;
|
||||||
for (key, value) in record {
|
const WRAP_STRING_WIDTH: usize = 30;
|
||||||
let val = convert_nu_value_to_table_value(value, config);
|
|
||||||
let row = TableValue::Row(vec![TableValue::Cell(key), val]);
|
let mut text = value.to_abbreviated_string(config);
|
||||||
rows.push(row);
|
if string_width(&text) > MAX_STRING_WIDTH {
|
||||||
|
text = string_wrap(&text, WRAP_STRING_WIDTH, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_key_width = rows
|
TableValue::Cell(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_vertical_map(record: Record, config: &Config) -> TableValue {
|
||||||
|
let max_key_width = record
|
||||||
.iter()
|
.iter()
|
||||||
.map(|row| match row {
|
.map(|(k, _)| string_width(k))
|
||||||
TableValue::Row(list) => match &list[0] {
|
|
||||||
TableValue::Cell(key) => string_width(key),
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
_ => unreachable!(),
|
|
||||||
})
|
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
rows.iter_mut().for_each(|row| {
|
let mut rows = Vec::with_capacity(record.len());
|
||||||
match row {
|
for (mut key, value) in record {
|
||||||
TableValue::Row(list) => match &mut list[0] {
|
string_append_to_width(&mut key, max_key_width);
|
||||||
TableValue::Cell(key) => {
|
|
||||||
let width = string_width(key);
|
let value = convert_nu_value_to_table_value(value, config);
|
||||||
let rest = max_key_width - width;
|
|
||||||
key.extend(std::iter::repeat(' ').take(rest));
|
let row = TableValue::Row(vec![TableValue::Cell(key), value]);
|
||||||
|
rows.push(row);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
TableValue::Column(rows)
|
TableValue::Column(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn string_append_to_width(key: &mut String, max: usize) {
|
||||||
|
let width = string_width(key);
|
||||||
|
let rest = max - width;
|
||||||
|
key.extend(std::iter::repeat(' ').take(rest));
|
||||||
|
}
|
||||||
|
|
||||||
fn build_vertical_array(vals: Vec<Value>, config: &Config) -> TableValue {
|
fn build_vertical_array(vals: Vec<Value>, config: &Config) -> TableValue {
|
||||||
let map = vals
|
let map = vals
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|val| convert_nu_value_to_table_value(val, config))
|
.map(|val| convert_nu_value_to_table_value(val, config))
|
||||||
.collect::<Vec<_>>();
|
.collect();
|
||||||
|
|
||||||
TableValue::Column(map)
|
TableValue::Column(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_valid_record(vals: &[Value]) -> bool {
|
fn is_valid_record(vals: &[Value]) -> bool {
|
||||||
let mut first_record: Option<&Record> = None;
|
if vals.is_empty() {
|
||||||
for val in vals {
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_value = match &vals[0] {
|
||||||
|
Value::Record { val, .. } => val,
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
for val in &vals[1..] {
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
if let Some(known) = first_record {
|
let equal = val.columns().eq(first_value.columns());
|
||||||
let equal = known.columns().eq(val.columns());
|
|
||||||
if !equal {
|
if !equal {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
first_record = Some(val)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
}
|
||||||
@ -185,30 +164,41 @@ fn count_columns_in_record(vals: &[Value]) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_map_from_record(vals: Vec<Value>, config: &Config) -> TableValue {
|
fn build_map_from_record(vals: Vec<Value>, config: &Config) -> TableValue {
|
||||||
let mut list = vec![];
|
// assumes that we have a valid record structure (checked by is_valid_record)
|
||||||
|
|
||||||
let head = get_columns_in_record(&vals);
|
let head = get_columns_in_record(&vals);
|
||||||
let count_columns = head.len();
|
let mut list = Vec::with_capacity(head.len());
|
||||||
for col in head {
|
for col in head {
|
||||||
list.push(vec![TableValue::Cell(col)]);
|
list.push(TableValue::Column(vec![TableValue::Cell(col)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
for val in vals {
|
for val in vals {
|
||||||
|
let val = get_as_record(val);
|
||||||
|
for (i, (_, val)) in val.into_owned().into_iter().enumerate() {
|
||||||
|
let value = convert_nu_value_to_table_value(val, config);
|
||||||
|
let list = get_table_value_column_mut(&mut list[i]);
|
||||||
|
|
||||||
|
list.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TableValue::Row(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_table_value_column_mut(val: &mut TableValue) -> &mut Vec<TableValue> {
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val, .. } => {
|
TableValue::Column(row) => row,
|
||||||
for (i, (_key, val)) in val.into_owned().into_iter().take(count_columns).enumerate()
|
_ => {
|
||||||
{
|
unreachable!();
|
||||||
let cell = convert_nu_value_to_table_value(val, config);
|
|
||||||
list[i].push(cell);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_as_record(val: Value) -> nu_utils::SharedCow<Record> {
|
||||||
|
match val {
|
||||||
|
Value::Record { val, .. } => val,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let columns = list.into_iter().map(TableValue::Column).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
TableValue::Row(columns)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_columns_in_record(vals: &[Value]) -> Vec<String> {
|
fn get_columns_in_record(vals: &[Value]) -> Vec<String> {
|
||||||
@ -218,32 +208,6 @@ fn get_columns_in_record(vals: &[Value]) -> Vec<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SetRawStyle(Theme);
|
|
||||||
|
|
||||||
impl<R, D> TableOption<R, CompactMultilineConfig, D> for SetRawStyle {
|
|
||||||
fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) {
|
|
||||||
let borders = *self.0.get_borders();
|
|
||||||
cfg.set_borders(borders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SetBorderColor(ANSIStr<'static>);
|
|
||||||
|
|
||||||
impl<R, D> TableOption<R, CompactMultilineConfig, D> for SetBorderColor {
|
|
||||||
fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) {
|
|
||||||
let borders = Borders::filled(self.0);
|
|
||||||
cfg.set_borders_color(borders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SetAlignment(AlignmentHorizontal);
|
|
||||||
|
|
||||||
impl<R, D> TableOption<R, CompactMultilineConfig, D> for SetAlignment {
|
|
||||||
fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) {
|
|
||||||
cfg.set_alignment_horizontal(self.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn truncate_table_value(
|
fn truncate_table_value(
|
||||||
value: &mut TableValue,
|
value: &mut TableValue,
|
||||||
has_vertical: bool,
|
has_vertical: bool,
|
||||||
@ -348,3 +312,44 @@ fn truncate_table_value(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_table_with_border_color(mut table: PoolTable, color: Color) -> String {
|
||||||
|
// NOTE: We have this function presizely because of color_into_ansistr internals
|
||||||
|
// color must be alive why we build table
|
||||||
|
|
||||||
|
let color = color_into_ansistr(&color);
|
||||||
|
table.with(SetBorderColor(color));
|
||||||
|
table.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_into_ansistr(color: &Color) -> ANSIStr<'static> {
|
||||||
|
// # SAFETY
|
||||||
|
//
|
||||||
|
// It's perfectly save to do cause table does not store the reference internally.
|
||||||
|
// We just need this unsafe section to cope with some limitations of [`PoolTable`].
|
||||||
|
// Mitigation of this is definitely on a todo list.
|
||||||
|
|
||||||
|
let prefix = color.get_prefix();
|
||||||
|
let suffix = color.get_suffix();
|
||||||
|
let prefix: &'static str = unsafe { std::mem::transmute(prefix) };
|
||||||
|
let suffix: &'static str = unsafe { std::mem::transmute(suffix) };
|
||||||
|
|
||||||
|
ANSIStr::new(prefix, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SetBorderColor(ANSIStr<'static>);
|
||||||
|
|
||||||
|
impl<R, D> TableOption<R, CompactMultilineConfig, D> for SetBorderColor {
|
||||||
|
fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) {
|
||||||
|
let borders = Borders::filled(self.0);
|
||||||
|
cfg.set_borders_color(borders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_border_color(style: &StyleComputer<'_>) -> Option<Color> {
|
||||||
|
// color_config closures for "separator" are just given a null.
|
||||||
|
let color = style.compute("separator", &Value::nothing(Span::unknown()));
|
||||||
|
let color = color.paint(" ").to_string();
|
||||||
|
let color = Color::try_from(color);
|
||||||
|
color.ok()
|
||||||
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
|
|
||||||
use tabled::{
|
use tabled::{
|
||||||
builder::Builder,
|
|
||||||
grid::{
|
grid::{
|
||||||
ansi::{ANSIBuf, ANSIStr},
|
ansi::{ANSIBuf, ANSIStr},
|
||||||
records::vec_records::Text,
|
records::vec_records::Text,
|
||||||
util::string::get_text_width,
|
util::string::get_text_width,
|
||||||
},
|
},
|
||||||
settings::{width::Truncate, Color, Modify, Padding, Style, Width},
|
settings::{
|
||||||
|
width::{Truncate, Wrap},
|
||||||
|
Color,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::common::get_leading_trailing_space_style;
|
use crate::common::get_leading_trailing_space_style;
|
||||||
@ -16,34 +19,21 @@ pub fn string_width(text: &str) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn string_wrap(text: &str, width: usize, keep_words: bool) -> String {
|
pub fn string_wrap(text: &str, width: usize, keep_words: bool) -> String {
|
||||||
// todo: change me...
|
|
||||||
//
|
|
||||||
// well... it's not efficient to build a table to wrap a string,
|
|
||||||
// but ... it's better than a copy paste (is it?)
|
|
||||||
|
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let wrap = if keep_words {
|
let text_width = string_width(text);
|
||||||
Width::wrap(width).keep_words(true)
|
if text_width <= width {
|
||||||
} else {
|
return text.to_owned();
|
||||||
Width::wrap(width)
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Builder::from_iter([[text]])
|
Wrap::wrap(text, width, keep_words)
|
||||||
.build()
|
|
||||||
.with(Style::empty())
|
|
||||||
.with(Padding::zero())
|
|
||||||
.with(Modify::new((0, 0)).with(wrap))
|
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn string_truncate(text: &str, width: usize) -> String {
|
pub fn string_truncate(text: &str, width: usize) -> String {
|
||||||
// todo: change me...
|
|
||||||
|
|
||||||
let line = match text.lines().next() {
|
let line = match text.lines().next() {
|
||||||
Some(first_line) => first_line,
|
Some(line) => line,
|
||||||
None => return String::new(),
|
None => return String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,35 +41,77 @@ pub fn string_truncate(text: &str, width: usize) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clean_charset(text: &str) -> String {
|
pub fn clean_charset(text: &str) -> String {
|
||||||
// todo: optimize, I bet it can be done in 1 path
|
// TODO: We could make an optimization to take a String and modify it
|
||||||
text.replace('\t', " ").replace('\r', "")
|
// We could check if there was any changes and if not make no allocations at all and don't change the origin.
|
||||||
|
// Why it's not done...
|
||||||
|
// Cause I am not sure how the `if` in a loop will affect performance.
|
||||||
|
// So it's better be profiled, but likely the optimization be worth it.
|
||||||
|
// At least because it's a base case where we won't change anything....
|
||||||
|
|
||||||
|
// allocating at least the text size,
|
||||||
|
// in most cases the buf will be a copy of text anyhow.
|
||||||
|
//
|
||||||
|
// but yes sometimes we will alloc more then necessary.
|
||||||
|
// We could shrink it but...it will be another realloc which make no scense.
|
||||||
|
let mut buf = String::with_capacity(text.len());
|
||||||
|
|
||||||
|
for c in text.chars() {
|
||||||
|
if c == '\n' {
|
||||||
|
buf.push(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == '\t' {
|
||||||
|
buf.push(' ');
|
||||||
|
buf.push(' ');
|
||||||
|
buf.push(' ');
|
||||||
|
buf.push(' ');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: Overall maybe we shall delete this check?
|
||||||
|
// it was made in order to cope with emojie issue.
|
||||||
|
// if c < ' ' && c != '\u{1b}' {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
buf.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn colorize_space(data: &mut [Vec<Text<String>>], style_computer: &StyleComputer<'_>) {
|
pub fn colorize_space(data: &mut [Vec<Text<String>>], style_computer: &StyleComputer<'_>) {
|
||||||
if let Some(style) = get_leading_trailing_space_style(style_computer).color_style {
|
let style = match get_leading_trailing_space_style(style_computer).color_style {
|
||||||
|
Some(color) => color,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
let style = ANSIBuf::from(convert_style(style));
|
let style = ANSIBuf::from(convert_style(style));
|
||||||
let style = style.as_ref();
|
let style = style.as_ref();
|
||||||
colorize_lead_trail_space(data, Some(style), Some(style));
|
if style.is_empty() {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn colorize_space_str(text: &mut String, style_computer: &StyleComputer<'_>) {
|
|
||||||
if let Some(style) = get_leading_trailing_space_style(style_computer).color_style {
|
|
||||||
let style = ANSIBuf::from(convert_style(style));
|
|
||||||
let style = style.as_ref();
|
|
||||||
*text = colorize_space_one(text, Some(style), Some(style));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn colorize_lead_trail_space(
|
|
||||||
data: &mut [Vec<Text<String>>],
|
|
||||||
lead: Option<ANSIStr<'_>>,
|
|
||||||
trail: Option<ANSIStr<'_>>,
|
|
||||||
) {
|
|
||||||
if lead.is_none() && trail.is_none() {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
colorize_list(data, style, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn colorize_space_str(text: &mut String, style_computer: &StyleComputer<'_>) {
|
||||||
|
let style = match get_leading_trailing_space_style(style_computer).color_style {
|
||||||
|
Some(color) => color,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let style = ANSIBuf::from(convert_style(style));
|
||||||
|
let style = style.as_ref();
|
||||||
|
if style.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*text = colorize_space_one(text, style, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn colorize_list(data: &mut [Vec<Text<String>>], lead: ANSIStr<'_>, trail: ANSIStr<'_>) {
|
||||||
for row in data.iter_mut() {
|
for row in data.iter_mut() {
|
||||||
for cell in row {
|
for cell in row {
|
||||||
let buf = colorize_space_one(cell.as_ref(), lead, trail);
|
let buf = colorize_space_one(cell.as_ref(), lead, trail);
|
||||||
@ -88,7 +120,7 @@ fn colorize_lead_trail_space(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn colorize_space_one(text: &str, lead: Option<ANSIStr<'_>>, trail: Option<ANSIStr<'_>>) -> String {
|
fn colorize_space_one(text: &str, lead: ANSIStr<'_>, trail: ANSIStr<'_>) -> String {
|
||||||
use fancy_regex::Captures;
|
use fancy_regex::Captures;
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
@ -102,20 +134,20 @@ fn colorize_space_one(text: &str, lead: Option<ANSIStr<'_>>, trail: Option<ANSIS
|
|||||||
|
|
||||||
let mut buf = text.to_owned();
|
let mut buf = text.to_owned();
|
||||||
|
|
||||||
if let Some(color) = &lead {
|
if !lead.is_empty() {
|
||||||
buf = RE_LEADING
|
buf = RE_LEADING
|
||||||
.replace_all(&buf, |cap: &Captures| {
|
.replace_all(&buf, |cap: &Captures| {
|
||||||
let spaces = cap.get(1).expect("valid").as_str();
|
let spaces = cap.get(1).expect("valid").as_str();
|
||||||
format!("{}{}{}", color.get_prefix(), spaces, color.get_suffix())
|
format!("{}{}{}", lead.get_prefix(), spaces, lead.get_suffix())
|
||||||
})
|
})
|
||||||
.into_owned();
|
.into_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(color) = &trail {
|
if !trail.is_empty() {
|
||||||
buf = RE_TRAILING
|
buf = RE_TRAILING
|
||||||
.replace_all(&buf, |cap: &Captures| {
|
.replace_all(&buf, |cap: &Captures| {
|
||||||
let spaces = cap.get(1).expect("valid").as_str();
|
let spaces = cap.get(1).expect("valid").as_str();
|
||||||
format!("{}{}{}", color.get_prefix(), spaces, color.get_suffix())
|
format!("{}{}{}", trail.get_prefix(), spaces, trail.get_suffix())
|
||||||
})
|
})
|
||||||
.into_owned();
|
.into_owned();
|
||||||
}
|
}
|
||||||
@ -126,3 +158,7 @@ fn colorize_space_one(text: &str, lead: Option<ANSIStr<'_>>, trail: Option<ANSIS
|
|||||||
pub fn convert_style(style: nu_ansi_term::Style) -> Color {
|
pub fn convert_style(style: nu_ansi_term::Style) -> Color {
|
||||||
Color::new(style.prefix().to_string(), style.suffix().to_string())
|
Color::new(style.prefix().to_string(), style.suffix().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_color_empty(c: &Color) -> bool {
|
||||||
|
c.get_prefix().is_empty() && c.get_suffix().is_empty()
|
||||||
|
}
|
||||||
|
@ -1,29 +1,79 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use nu_table::{string_width, NuTable, NuTableConfig};
|
use nu_protocol::TrimStrategy;
|
||||||
|
use nu_table::{string_width, NuTable, TableTheme};
|
||||||
use tabled::grid::records::vec_records::Text;
|
use tabled::grid::records::vec_records::Text;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct TestCase {
|
pub struct TestCase {
|
||||||
cfg: NuTableConfig,
|
theme: TableTheme,
|
||||||
|
with_header: bool,
|
||||||
|
with_footer: bool,
|
||||||
|
with_index: bool,
|
||||||
|
expand: bool,
|
||||||
|
strategy: TrimStrategy,
|
||||||
termwidth: usize,
|
termwidth: usize,
|
||||||
expected: Option<String>,
|
expected: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestCase {
|
impl TestCase {
|
||||||
pub fn new(cfg: NuTableConfig, termwidth: usize, expected: Option<String>) -> Self {
|
pub fn new(termwidth: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cfg,
|
|
||||||
termwidth,
|
termwidth,
|
||||||
expected,
|
expected: None,
|
||||||
|
theme: TableTheme::basic(),
|
||||||
|
with_header: false,
|
||||||
|
with_footer: false,
|
||||||
|
with_index: false,
|
||||||
|
expand: false,
|
||||||
|
strategy: TrimStrategy::truncate(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expected(mut self, value: Option<String>) -> Self {
|
||||||
|
self.expected = value;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn theme(mut self, theme: TableTheme) -> Self {
|
||||||
|
self.theme = theme;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand(mut self) -> Self {
|
||||||
|
self.expand = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn header(mut self) -> Self {
|
||||||
|
self.with_header = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn footer(mut self) -> Self {
|
||||||
|
self.with_footer = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index(mut self) -> Self {
|
||||||
|
self.with_index = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim(mut self, trim: TrimStrategy) -> Self {
|
||||||
|
self.strategy = trim;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Data = Vec<Vec<Text<String>>>;
|
type Data = Vec<Vec<Text<String>>>;
|
||||||
|
|
||||||
pub fn test_table<I: IntoIterator<Item = TestCase>>(data: Data, tests: I) {
|
pub fn test_table<I>(data: Data, tests: I)
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = TestCase>,
|
||||||
|
{
|
||||||
for (i, test) in tests.into_iter().enumerate() {
|
for (i, test) in tests.into_iter().enumerate() {
|
||||||
let actual = create_table(data.clone(), test.cfg.clone(), test.termwidth);
|
let actual = create_table(data.clone(), test.clone());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual, test.expected,
|
actual, test.expected,
|
||||||
@ -37,9 +87,14 @@ pub fn test_table<I: IntoIterator<Item = TestCase>>(data: Data, tests: I) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_table(data: Data, config: NuTableConfig, termwidth: usize) -> Option<String> {
|
pub fn create_table(data: Data, case: TestCase) -> Option<String> {
|
||||||
let table = NuTable::from(data);
|
let mut table = NuTable::from(data);
|
||||||
table.draw(config, termwidth)
|
table.set_theme(case.theme);
|
||||||
|
table.set_structure(case.with_index, case.with_header, case.with_footer);
|
||||||
|
table.set_trim(case.strategy);
|
||||||
|
table.set_strategy(case.expand);
|
||||||
|
|
||||||
|
table.draw(case.termwidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_row(count_columns: usize) -> Vec<Text<String>> {
|
pub fn create_row(count_columns: usize) -> Vec<Text<String>> {
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use common::{create_row, test_table, TestCase};
|
|
||||||
use nu_protocol::TrimStrategy;
|
use nu_protocol::TrimStrategy;
|
||||||
use nu_table::{NuTable, NuTableConfig, TableTheme as theme};
|
use nu_table::{NuTable, TableTheme as theme};
|
||||||
|
|
||||||
|
use common::{create_row, test_table, TestCase};
|
||||||
|
|
||||||
use tabled::grid::records::vec_records::Text;
|
use tabled::grid::records::vec_records::Text;
|
||||||
|
|
||||||
#[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 mut table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
|
||||||
let cfg = NuTableConfig {
|
table.set_theme(theme::heavy());
|
||||||
theme: theme::heavy(),
|
table.set_structure(false, true, false);
|
||||||
with_header: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let table = table.draw(cfg.clone(), usize::MAX);
|
let table = table.draw(usize::MAX);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
table.as_deref(),
|
table.as_deref(),
|
||||||
@ -28,9 +27,11 @@ fn data_and_header_has_different_size_doesnt_work() {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
|
let mut table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
|
||||||
|
table.set_theme(theme::heavy());
|
||||||
|
table.set_structure(false, true, false);
|
||||||
|
|
||||||
let table = table.draw(cfg, usize::MAX);
|
let table = table.draw(usize::MAX);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
table.as_deref(),
|
table.as_deref(),
|
||||||
@ -47,30 +48,27 @@ fn data_and_header_has_different_size_doesnt_work() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn termwidth_too_small() {
|
fn termwidth_too_small() {
|
||||||
let test_loop = |config: NuTableConfig| {
|
let tests = [
|
||||||
for i in 0..10 {
|
|
||||||
let table = NuTable::from(vec![create_row(5), create_row(5), create_row(5)]);
|
|
||||||
let table = table.draw(config.clone(), i);
|
|
||||||
|
|
||||||
assert!(table.is_none());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut cfg = NuTableConfig {
|
|
||||||
theme: theme::heavy(),
|
|
||||||
with_header: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
for case in [
|
|
||||||
TrimStrategy::truncate(None),
|
TrimStrategy::truncate(None),
|
||||||
TrimStrategy::truncate(Some(String::from("**"))),
|
TrimStrategy::truncate(Some(String::from("**"))),
|
||||||
TrimStrategy::truncate(Some(String::from(""))),
|
TrimStrategy::truncate(Some(String::from(""))),
|
||||||
TrimStrategy::wrap(false),
|
TrimStrategy::wrap(false),
|
||||||
TrimStrategy::wrap(true),
|
TrimStrategy::wrap(true),
|
||||||
] {
|
];
|
||||||
cfg.trim = case;
|
|
||||||
test_loop(cfg.clone());
|
let data = vec![create_row(5), create_row(5), create_row(5)];
|
||||||
|
|
||||||
|
for case in tests {
|
||||||
|
for i in 0..10 {
|
||||||
|
let mut table = NuTable::from(data.clone());
|
||||||
|
table.set_theme(theme::heavy());
|
||||||
|
table.set_structure(false, true, false);
|
||||||
|
table.set_trim(case.clone());
|
||||||
|
|
||||||
|
let table = table.draw(i);
|
||||||
|
|
||||||
|
assert!(table.is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,30 +193,24 @@ fn width_control_test_0() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_width(data: Vec<Vec<Text<String>>>, tests: &[(usize, &str)]) {
|
fn test_width(data: Vec<Vec<Text<String>>>, tests: &[(usize, &str)]) {
|
||||||
let config = NuTableConfig {
|
|
||||||
theme: theme::heavy(),
|
|
||||||
trim: TrimStrategy::truncate(Some(String::from("..."))),
|
|
||||||
with_header: true,
|
|
||||||
..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(termwidth)
|
||||||
|
.theme(theme::heavy())
|
||||||
|
.trim(TrimStrategy::truncate(Some(String::from("..."))))
|
||||||
|
.header()
|
||||||
|
.expected(Some(expected.to_owned()))
|
||||||
});
|
});
|
||||||
|
|
||||||
test_table(data, tests);
|
test_table(data, tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_trim(tests: &[(usize, Option<&str>)], trim: TrimStrategy) {
|
fn test_trim(tests: &[(usize, Option<&str>)], trim: TrimStrategy) {
|
||||||
let config = NuTableConfig {
|
|
||||||
theme: theme::heavy(),
|
|
||||||
with_header: true,
|
|
||||||
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(termwidth)
|
||||||
|
.theme(theme::heavy())
|
||||||
|
.trim(trim.clone())
|
||||||
|
.header()
|
||||||
|
.expected(expected.map(|s| s.to_string()))
|
||||||
});
|
});
|
||||||
|
|
||||||
let data = vec![
|
let data = vec![
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use common::{create_row, create_table};
|
use common::{create_row, create_table, TestCase};
|
||||||
|
|
||||||
use nu_table::{NuTableConfig, TableTheme as theme};
|
use nu_table::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],
|
||||||
NuTableConfig {
|
TestCase::new(50).theme(theme::rounded()).header().expand(),
|
||||||
theme: theme::rounded(),
|
|
||||||
with_header: true,
|
|
||||||
expand: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
50,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use common::create_row as row;
|
use common::{create_row as row, TestCase};
|
||||||
use nu_table::{NuTable, NuTableConfig, TableTheme as theme};
|
use nu_table::{NuTable, TableTheme as theme};
|
||||||
use tabled::grid::records::vec_records::Text;
|
use tabled::grid::records::vec_records::Text;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -452,27 +452,18 @@ fn test_with_love() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_table(data: Vec<Vec<Text<String>>>, with_header: bool, theme: theme) -> String {
|
fn create_table(data: Vec<Vec<Text<String>>>, with_header: bool, theme: theme) -> String {
|
||||||
let config = NuTableConfig {
|
let mut case = TestCase::new(usize::MAX).theme(theme);
|
||||||
theme,
|
if with_header {
|
||||||
with_header,
|
case = case.header();
|
||||||
..Default::default()
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let out = common::create_table(data, config, usize::MAX);
|
common::create_table(data, case).expect("not expected to get None")
|
||||||
|
|
||||||
out.expect("not expected to get None")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_table_with_size(data: Vec<Vec<Text<String>>>, with_header: bool, theme: theme) -> String {
|
fn create_table_with_size(data: Vec<Vec<Text<String>>>, with_header: bool, theme: theme) -> String {
|
||||||
let config = NuTableConfig {
|
let mut table = NuTable::from(data);
|
||||||
theme,
|
table.set_theme(theme);
|
||||||
with_header,
|
table.set_structure(false, with_header, false);
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let table = NuTable::from(data);
|
table.draw(usize::MAX).expect("not expected to get None")
|
||||||
|
|
||||||
table
|
|
||||||
.draw(config, usize::MAX)
|
|
||||||
.expect("not expected to get None")
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user