2022-09-14 10:55:41 +00:00
|
|
|
use lscolors::{LsColors, Style};
|
2022-11-15 17:12:56 +00:00
|
|
|
use nu_color_config::{color_from_hex, get_color_config, style_primitive};
|
2022-05-16 15:35:57 +00:00
|
|
|
use nu_engine::{column::get_columns, env_to_string, CallExt};
|
2021-11-17 04:22:37 +00:00
|
|
|
use nu_protocol::{
|
2022-05-16 15:35:57 +00:00
|
|
|
ast::{Call, PathMember},
|
|
|
|
engine::{Command, EngineState, Stack, StateWorkingSet},
|
2022-10-03 16:40:16 +00:00
|
|
|
format_error, Category, Config, DataSource, Example, FooterMode, IntoPipelineData, ListStream,
|
2022-09-28 22:07:33 +00:00
|
|
|
PipelineData, PipelineMetadata, RawStream, ShellError, Signature, Span, SyntaxShape,
|
|
|
|
TableIndexMode, Value,
|
2021-11-17 04:22:37 +00:00
|
|
|
};
|
2022-11-16 14:03:56 +00:00
|
|
|
use nu_table::{string_width, Alignment, Alignments, Table as NuTable, TableTheme, TextStyle};
|
2022-07-20 15:09:33 +00:00
|
|
|
use nu_utils::get_ls_colors;
|
2021-11-09 06:14:00 +00:00
|
|
|
use std::sync::Arc;
|
2021-12-03 06:15:23 +00:00
|
|
|
use std::time::Instant;
|
2022-08-18 10:45:49 +00:00
|
|
|
use std::{
|
2022-10-03 16:40:16 +00:00
|
|
|
cmp::max,
|
|
|
|
collections::HashMap,
|
2022-08-18 10:45:49 +00:00
|
|
|
path::PathBuf,
|
|
|
|
sync::atomic::{AtomicBool, Ordering},
|
|
|
|
};
|
2021-10-08 13:14:32 +00:00
|
|
|
use terminal_size::{Height, Width};
|
2022-08-18 10:45:49 +00:00
|
|
|
use url::Url;
|
2022-02-26 17:23:05 +00:00
|
|
|
|
2021-12-03 06:15:23 +00:00
|
|
|
const STREAM_PAGE_SIZE: usize = 1000;
|
|
|
|
const STREAM_TIMEOUT_CHECK_INTERVAL: usize = 100;
|
2022-06-26 22:32:18 +00:00
|
|
|
const INDEX_COLUMN_NAME: &str = "index";
|
2021-12-03 06:15:23 +00:00
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
type NuText = (String, TextStyle);
|
|
|
|
type NuColorMap = HashMap<String, nu_ansi_term::Style>;
|
|
|
|
|
2022-05-26 11:53:05 +00:00
|
|
|
fn get_width_param(width_param: Option<i64>) -> usize {
|
|
|
|
if let Some(col) = width_param {
|
|
|
|
col as usize
|
2022-07-09 19:55:47 +00:00
|
|
|
} else if let Some((Width(w), Height(_))) = terminal_size::terminal_size() {
|
|
|
|
w as usize
|
2022-05-26 11:53:05 +00:00
|
|
|
} else {
|
2022-07-09 19:55:47 +00:00
|
|
|
80
|
2022-05-26 11:53:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-25 04:01:02 +00:00
|
|
|
#[derive(Clone)]
|
2021-09-29 18:25:05 +00:00
|
|
|
pub struct Table;
|
|
|
|
|
|
|
|
//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one.
|
|
|
|
impl Command for Table {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"table"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
|
|
|
"Render the table."
|
|
|
|
}
|
|
|
|
|
2022-06-26 22:32:18 +00:00
|
|
|
fn extra_usage(&self) -> &str {
|
|
|
|
"If the table contains a column called 'index', this column is used as the table index instead of the usual continuous index"
|
|
|
|
}
|
|
|
|
|
2022-04-05 12:01:21 +00:00
|
|
|
fn search_terms(&self) -> Vec<&str> {
|
|
|
|
vec!["display", "render"]
|
|
|
|
}
|
|
|
|
|
2021-09-29 18:25:05 +00:00
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
2021-12-23 21:41:29 +00:00
|
|
|
Signature::build("table")
|
|
|
|
.named(
|
2022-02-17 14:55:17 +00:00
|
|
|
"start-number",
|
2021-12-23 21:41:29 +00:00
|
|
|
SyntaxShape::Int,
|
|
|
|
"row number to start viewing from",
|
|
|
|
Some('n'),
|
|
|
|
)
|
2022-05-11 21:15:31 +00:00
|
|
|
.switch("list", "list available table modes/themes", Some('l'))
|
2022-05-26 11:53:05 +00:00
|
|
|
.named(
|
|
|
|
"width",
|
|
|
|
SyntaxShape::Int,
|
|
|
|
"number of terminal columns wide (not output columns)",
|
|
|
|
Some('w'),
|
|
|
|
)
|
2022-10-03 16:40:16 +00:00
|
|
|
.switch(
|
|
|
|
"expand",
|
|
|
|
"expand the table structure in a light mode",
|
|
|
|
Some('e'),
|
|
|
|
)
|
|
|
|
.named(
|
|
|
|
"expand-deep",
|
|
|
|
SyntaxShape::Int,
|
|
|
|
"an expand limit of recursion which will take place",
|
|
|
|
Some('d'),
|
|
|
|
)
|
|
|
|
.switch("flatten", "Flatten simple arrays", None)
|
|
|
|
.named(
|
|
|
|
"flatten-separator",
|
|
|
|
SyntaxShape::String,
|
|
|
|
"sets a seperator when 'flatten' used",
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
.switch(
|
|
|
|
"collapse",
|
|
|
|
"expand the table structure in colapse mode.\nBe aware collapse mode currently doesn't support width controll",
|
|
|
|
Some('c'),
|
|
|
|
)
|
2021-12-23 21:41:29 +00:00
|
|
|
.category(Category::Viewers)
|
2021-09-29 18:25:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
2021-11-09 06:14:00 +00:00
|
|
|
engine_state: &EngineState,
|
2021-11-14 19:25:57 +00:00
|
|
|
stack: &mut Stack,
|
2021-09-29 18:25:05 +00:00
|
|
|
call: &Call,
|
2021-10-25 04:24:10 +00:00
|
|
|
input: PipelineData,
|
2022-10-03 16:40:16 +00:00
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-02-17 14:55:17 +00:00
|
|
|
let start_num: Option<i64> = call.get_flag(engine_state, stack, "start-number")?;
|
2021-12-23 21:41:29 +00:00
|
|
|
let row_offset = start_num.unwrap_or_default() as usize;
|
2022-05-11 21:15:31 +00:00
|
|
|
let list: bool = call.has_flag("list");
|
2021-11-09 06:14:00 +00:00
|
|
|
|
2022-05-26 11:53:05 +00:00
|
|
|
let width_param: Option<i64> = call.get_flag(engine_state, stack, "width")?;
|
|
|
|
let term_width = get_width_param(width_param);
|
2021-10-08 13:14:32 +00:00
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
let expand: bool = call.has_flag("expand");
|
|
|
|
let expand_limit: Option<usize> = call.get_flag(engine_state, stack, "expand-deep")?;
|
|
|
|
let collapse: bool = call.has_flag("collapse");
|
|
|
|
let flatten: bool = call.has_flag("flatten");
|
|
|
|
let flatten_separator: Option<String> =
|
|
|
|
call.get_flag(engine_state, stack, "flatten-separator")?;
|
|
|
|
|
|
|
|
let table_view = match (expand, collapse) {
|
|
|
|
(false, false) => TableView::General,
|
|
|
|
(_, true) => TableView::Collapsed,
|
|
|
|
(true, _) => TableView::Expanded {
|
|
|
|
limit: expand_limit,
|
|
|
|
flatten,
|
|
|
|
flatten_separator,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
// if list argument is present we just need to return a list of supported table themes
|
2022-05-11 21:15:31 +00:00
|
|
|
if list {
|
2022-10-03 16:40:16 +00:00
|
|
|
let val = Value::List {
|
|
|
|
vals: supported_table_modes(),
|
2022-05-11 21:15:31 +00:00
|
|
|
span: Span::test_data(),
|
2022-10-03 16:40:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return Ok(val.into_pipeline_data());
|
2022-05-11 21:15:31 +00:00
|
|
|
}
|
|
|
|
|
2022-03-16 22:21:06 +00:00
|
|
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
|
|
|
#[cfg(windows)]
|
|
|
|
{
|
|
|
|
let _ = nu_utils::enable_vt_processing();
|
|
|
|
}
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
handle_table_command(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
call,
|
|
|
|
input,
|
|
|
|
row_offset,
|
|
|
|
table_view,
|
|
|
|
term_width,
|
|
|
|
)
|
2021-09-29 18:25:05 +00:00
|
|
|
}
|
2022-02-21 13:52:50 +00:00
|
|
|
|
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
|
|
let span = Span::test_data();
|
|
|
|
vec![
|
|
|
|
Example {
|
2022-10-23 06:02:52 +00:00
|
|
|
description: "List the files in current directory, with indexes starting from 1.",
|
2022-02-21 13:52:50 +00:00
|
|
|
example: r#"ls | table -n 1"#,
|
|
|
|
result: None,
|
|
|
|
},
|
|
|
|
Example {
|
|
|
|
description: "Render data in table view",
|
2022-10-26 16:36:42 +00:00
|
|
|
example: r#"[[a b]; [1 2] [3 4]] | table"#,
|
2022-02-21 13:52:50 +00:00
|
|
|
result: Some(Value::List {
|
|
|
|
vals: vec![
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["a".to_string(), "b".to_string()],
|
|
|
|
vals: vec![Value::test_int(1), Value::test_int(2)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["a".to_string(), "b".to_string()],
|
|
|
|
vals: vec![Value::test_int(3), Value::test_int(4)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
span,
|
|
|
|
}),
|
|
|
|
},
|
2022-10-03 16:40:16 +00:00
|
|
|
Example {
|
|
|
|
description: "Render data in table view (expanded)",
|
2022-10-26 16:36:42 +00:00
|
|
|
example: r#"[[a b]; [1 2] [2 [4 4]]] | table --expand"#,
|
2022-10-03 16:40:16 +00:00
|
|
|
result: Some(Value::List {
|
|
|
|
vals: vec![
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["a".to_string(), "b".to_string()],
|
|
|
|
vals: vec![Value::test_int(1), Value::test_int(2)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["a".to_string(), "b".to_string()],
|
|
|
|
vals: vec![Value::test_int(3), Value::test_int(4)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
span,
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
Example {
|
|
|
|
description: "Render data in table view (collapsed)",
|
2022-10-26 16:36:42 +00:00
|
|
|
example: r#"[[a b]; [1 2] [2 [4 4]]] | table --collapse"#,
|
2022-10-03 16:40:16 +00:00
|
|
|
result: Some(Value::List {
|
|
|
|
vals: vec![
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["a".to_string(), "b".to_string()],
|
|
|
|
vals: vec![Value::test_int(1), Value::test_int(2)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["a".to_string(), "b".to_string()],
|
|
|
|
vals: vec![Value::test_int(3), Value::test_int(4)],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
span,
|
|
|
|
}),
|
|
|
|
},
|
2022-02-21 13:52:50 +00:00
|
|
|
]
|
|
|
|
}
|
2021-09-29 18:25:05 +00:00
|
|
|
}
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
fn handle_table_command(
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
call: &Call,
|
|
|
|
input: PipelineData,
|
|
|
|
row_offset: usize,
|
|
|
|
table_view: TableView,
|
|
|
|
term_width: usize,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
|
|
|
let ctrlc = engine_state.ctrlc.clone();
|
|
|
|
let config = engine_state.get_config();
|
|
|
|
|
|
|
|
match input {
|
|
|
|
PipelineData::ExternalStream { .. } => Ok(input),
|
|
|
|
PipelineData::Value(Value::Binary { val, .. }, ..) => Ok(PipelineData::ExternalStream {
|
|
|
|
stdout: Some(RawStream::new(
|
|
|
|
Box::new(
|
|
|
|
vec![Ok(format!("{}\n", nu_pretty_hex::pretty_hex(&val))
|
|
|
|
.as_bytes()
|
|
|
|
.to_vec())]
|
|
|
|
.into_iter(),
|
|
|
|
),
|
|
|
|
ctrlc,
|
|
|
|
call.head,
|
|
|
|
)),
|
|
|
|
stderr: None,
|
|
|
|
exit_code: None,
|
|
|
|
span: call.head,
|
|
|
|
metadata: None,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 03:51:57 +00:00
|
|
|
trim_end_newline: false,
|
2022-10-03 16:40:16 +00:00
|
|
|
}),
|
|
|
|
PipelineData::Value(Value::List { vals, .. }, metadata) => handle_row_stream(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
ListStream::from_stream(vals.into_iter(), ctrlc.clone()),
|
|
|
|
call,
|
|
|
|
row_offset,
|
|
|
|
ctrlc,
|
|
|
|
metadata,
|
|
|
|
),
|
|
|
|
PipelineData::ListStream(stream, metadata) => handle_row_stream(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
stream,
|
|
|
|
call,
|
|
|
|
row_offset,
|
|
|
|
ctrlc,
|
|
|
|
metadata,
|
|
|
|
),
|
|
|
|
PipelineData::Value(Value::Record { cols, vals, span }, ..) => {
|
|
|
|
let result = match table_view {
|
2022-12-05 00:49:47 +00:00
|
|
|
TableView::General => {
|
|
|
|
build_general_table2(cols, vals, ctrlc.clone(), config, term_width)
|
|
|
|
}
|
2022-10-03 16:40:16 +00:00
|
|
|
TableView::Expanded {
|
|
|
|
limit,
|
|
|
|
flatten,
|
|
|
|
flatten_separator,
|
|
|
|
} => {
|
|
|
|
let sep = flatten_separator.as_deref().unwrap_or(" ");
|
|
|
|
build_expanded_table(
|
2022-12-05 00:49:47 +00:00
|
|
|
cols,
|
|
|
|
vals,
|
|
|
|
span,
|
|
|
|
ctrlc.clone(),
|
|
|
|
config,
|
|
|
|
term_width,
|
|
|
|
limit,
|
|
|
|
flatten,
|
|
|
|
sep,
|
2022-10-03 16:40:16 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
TableView::Collapsed => build_collapsed_table(cols, vals, config, term_width),
|
|
|
|
}?;
|
|
|
|
|
2022-12-05 00:49:47 +00:00
|
|
|
let ctrl_c_was_triggered = || match &ctrlc {
|
|
|
|
Some(ctrlc) => ctrlc.load(Ordering::SeqCst),
|
|
|
|
None => false,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = result.unwrap_or_else(|| {
|
|
|
|
if ctrl_c_was_triggered() {
|
|
|
|
"".into()
|
|
|
|
} else {
|
|
|
|
// assume this failed because the table was too wide
|
|
|
|
// TODO: more robust error classification
|
|
|
|
format!("Couldn't fit table into {} columns!", term_width)
|
|
|
|
}
|
|
|
|
});
|
2022-10-03 16:40:16 +00:00
|
|
|
|
|
|
|
let val = Value::String {
|
|
|
|
val: result,
|
|
|
|
span: call.head,
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(val.into_pipeline_data())
|
|
|
|
}
|
|
|
|
PipelineData::Value(Value::Error { error }, ..) => {
|
|
|
|
let working_set = StateWorkingSet::new(engine_state);
|
|
|
|
Ok(Value::String {
|
|
|
|
val: format_error(&working_set, &error),
|
|
|
|
span: call.head,
|
|
|
|
}
|
|
|
|
.into_pipeline_data())
|
|
|
|
}
|
|
|
|
PipelineData::Value(Value::CustomValue { val, span }, ..) => {
|
|
|
|
let base_pipeline = val.to_base_value(span)?.into_pipeline_data();
|
|
|
|
Table.run(engine_state, stack, call, base_pipeline)
|
|
|
|
}
|
|
|
|
PipelineData::Value(Value::Range { val, .. }, metadata) => handle_row_stream(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
ListStream::from_stream(val.into_range_iter(ctrlc.clone())?, ctrlc.clone()),
|
|
|
|
call,
|
|
|
|
row_offset,
|
|
|
|
ctrlc,
|
|
|
|
metadata,
|
|
|
|
),
|
|
|
|
x => Ok(x),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn supported_table_modes() -> Vec<Value> {
|
|
|
|
vec![
|
|
|
|
Value::string("basic", Span::test_data()),
|
|
|
|
Value::string("compact", Span::test_data()),
|
|
|
|
Value::string("compact_double", Span::test_data()),
|
|
|
|
Value::string("default", Span::test_data()),
|
|
|
|
Value::string("heavy", Span::test_data()),
|
|
|
|
Value::string("light", Span::test_data()),
|
|
|
|
Value::string("none", Span::test_data()),
|
|
|
|
Value::string("reinforced", Span::test_data()),
|
|
|
|
Value::string("rounded", Span::test_data()),
|
|
|
|
Value::string("thin", Span::test_data()),
|
|
|
|
Value::string("with_love", Span::test_data()),
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_collapsed_table(
|
|
|
|
cols: Vec<String>,
|
|
|
|
vals: Vec<Value>,
|
|
|
|
config: &Config,
|
|
|
|
term_width: usize,
|
|
|
|
) -> Result<Option<String>, ShellError> {
|
|
|
|
let value = Value::Record {
|
|
|
|
cols,
|
|
|
|
vals,
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
let color_hm = get_color_config(config);
|
|
|
|
let theme = load_theme_from_config(config);
|
|
|
|
let table = nu_table::NuTable::new(value, true, term_width, config, &color_hm, &theme, false);
|
|
|
|
|
|
|
|
let table = table.draw();
|
|
|
|
|
|
|
|
Ok(table)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_general_table2(
|
|
|
|
cols: Vec<String>,
|
|
|
|
vals: Vec<Value>,
|
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
config: &Config,
|
|
|
|
term_width: usize,
|
|
|
|
) -> Result<Option<String>, ShellError> {
|
|
|
|
let mut data = Vec::with_capacity(vals.len());
|
|
|
|
for (column, value) in cols.into_iter().zip(vals.into_iter()) {
|
|
|
|
// handle CTRLC event
|
|
|
|
if let Some(ctrlc) = &ctrlc {
|
|
|
|
if ctrlc.load(Ordering::SeqCst) {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let row = vec![
|
|
|
|
NuTable::create_cell(column, TextStyle::default_field()),
|
|
|
|
NuTable::create_cell(value.into_abbreviated_string(config), TextStyle::default()),
|
|
|
|
];
|
|
|
|
|
|
|
|
data.push(row);
|
|
|
|
}
|
|
|
|
|
|
|
|
let data_len = data.len();
|
|
|
|
let table = NuTable::new(data, (data_len, 2), term_width, false, false);
|
|
|
|
|
|
|
|
let theme = load_theme_from_config(config);
|
|
|
|
let color_hm = get_color_config(config);
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
let table = table.draw_table(
|
|
|
|
config,
|
|
|
|
&color_hm,
|
|
|
|
Alignments::default(),
|
|
|
|
&theme,
|
|
|
|
term_width,
|
|
|
|
false,
|
|
|
|
);
|
2022-10-03 16:40:16 +00:00
|
|
|
|
|
|
|
Ok(table)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn build_expanded_table(
|
|
|
|
cols: Vec<String>,
|
|
|
|
vals: Vec<Value>,
|
|
|
|
span: Span,
|
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
config: &Config,
|
|
|
|
term_width: usize,
|
|
|
|
expand_limit: Option<usize>,
|
|
|
|
flatten: bool,
|
|
|
|
flatten_sep: &str,
|
|
|
|
) -> Result<Option<String>, ShellError> {
|
|
|
|
let theme = load_theme_from_config(config);
|
|
|
|
let color_hm = get_color_config(config);
|
|
|
|
let alignments = Alignments::default();
|
|
|
|
|
|
|
|
// calculate the width of a key part + the rest of table so we know the rest of the table width available for value.
|
2022-11-16 14:03:56 +00:00
|
|
|
let key_width = cols.iter().map(|col| string_width(col)).max().unwrap_or(0);
|
2022-10-03 16:40:16 +00:00
|
|
|
let key = NuTable::create_cell(" ".repeat(key_width), TextStyle::default());
|
|
|
|
let key_table = NuTable::new(vec![vec![key]], (1, 2), term_width, false, false);
|
|
|
|
let key_width = key_table
|
2022-11-16 14:03:56 +00:00
|
|
|
.draw_table(config, &color_hm, alignments, &theme, usize::MAX, false)
|
|
|
|
.map(|table| string_width(&table))
|
2022-10-03 16:40:16 +00:00
|
|
|
.unwrap_or(0);
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
// 3 - count borders (left, center, right)
|
|
|
|
// 2 - padding
|
|
|
|
if key_width + 3 + 2 > term_width {
|
2022-10-03 16:40:16 +00:00
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
let remaining_width = term_width - key_width - 3 - 2;
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
let mut data = Vec::with_capacity(cols.len());
|
|
|
|
for (key, value) in cols.into_iter().zip(vals) {
|
|
|
|
// handle CTRLC event
|
|
|
|
if let Some(ctrlc) = &ctrlc {
|
|
|
|
if ctrlc.load(Ordering::SeqCst) {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let is_limited = matches!(expand_limit, Some(0));
|
2022-10-21 11:29:55 +00:00
|
|
|
let mut is_expanded = false;
|
2022-10-03 16:40:16 +00:00
|
|
|
let value = if is_limited {
|
2022-11-16 14:03:56 +00:00
|
|
|
value_to_styled_string(&value, config, &color_hm).0
|
2022-10-03 16:40:16 +00:00
|
|
|
} else {
|
|
|
|
let deep = expand_limit.map(|i| i - 1);
|
2022-11-16 14:03:56 +00:00
|
|
|
|
|
|
|
match value {
|
|
|
|
Value::List { vals, .. } => {
|
|
|
|
let table = convert_to_table2(
|
|
|
|
0,
|
|
|
|
vals.iter(),
|
|
|
|
ctrlc.clone(),
|
|
|
|
config,
|
|
|
|
span,
|
|
|
|
&color_hm,
|
|
|
|
&theme,
|
|
|
|
deep,
|
|
|
|
flatten,
|
|
|
|
flatten_sep,
|
|
|
|
remaining_width,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
match table {
|
|
|
|
Some(mut table) => {
|
|
|
|
// controll width via removing table columns.
|
|
|
|
let theme = load_theme_from_config(config);
|
|
|
|
table.truncate(remaining_width, &theme);
|
|
|
|
|
|
|
|
is_expanded = true;
|
|
|
|
|
|
|
|
let val = table.draw_table(
|
|
|
|
config,
|
|
|
|
&color_hm,
|
|
|
|
alignments,
|
|
|
|
&theme,
|
|
|
|
remaining_width,
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
match val {
|
|
|
|
Some(result) => result,
|
|
|
|
None => return Ok(None),
|
2022-10-28 12:00:10 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-16 14:03:56 +00:00
|
|
|
None => {
|
|
|
|
// it means that the list is empty
|
|
|
|
let value = Value::List { vals, span };
|
|
|
|
value_to_styled_string(&value, config, &color_hm).0
|
2022-10-28 12:00:10 +00:00
|
|
|
}
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-16 14:03:56 +00:00
|
|
|
Value::Record { cols, vals, span } => {
|
|
|
|
let result = build_expanded_table(
|
|
|
|
cols.clone(),
|
|
|
|
vals.clone(),
|
|
|
|
span,
|
|
|
|
ctrlc.clone(),
|
|
|
|
config,
|
|
|
|
remaining_width,
|
|
|
|
deep,
|
|
|
|
flatten,
|
|
|
|
flatten_sep,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Some(result) => {
|
|
|
|
is_expanded = true;
|
|
|
|
result
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
let failed_value = value_to_styled_string(
|
|
|
|
&Value::Record { cols, vals, span },
|
|
|
|
config,
|
|
|
|
&color_hm,
|
|
|
|
);
|
|
|
|
|
|
|
|
nu_table::wrap_string(&failed_value.0, remaining_width)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val => {
|
|
|
|
let text = value_to_styled_string(&val, config, &color_hm).0;
|
|
|
|
nu_table::wrap_string(&text, remaining_width)
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-21 11:29:55 +00:00
|
|
|
// we want to have a key being aligned to 2nd line,
|
|
|
|
// we could use Padding for it but,
|
|
|
|
// the easiest way to do so is just push a new_line char before
|
|
|
|
let mut key = key;
|
|
|
|
if !key.is_empty() && is_expanded && theme.has_top_line() {
|
|
|
|
key.insert(0, '\n');
|
|
|
|
}
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
let key = NuTable::create_cell(key, TextStyle::default_field());
|
2022-10-03 16:40:16 +00:00
|
|
|
let val = NuTable::create_cell(value, TextStyle::default());
|
|
|
|
|
|
|
|
let row = vec![key, val];
|
|
|
|
data.push(row);
|
|
|
|
}
|
|
|
|
|
|
|
|
let data_len = data.len();
|
|
|
|
let table = NuTable::new(data, (data_len, 2), term_width, false, false);
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
let table_s = table
|
|
|
|
.clone()
|
|
|
|
.draw_table(config, &color_hm, alignments, &theme, term_width, false);
|
|
|
|
|
|
|
|
let table = match table_s {
|
|
|
|
Some(s) => {
|
|
|
|
// check whether we need to expand table or not,
|
|
|
|
// todo: we can make it more effitient
|
|
|
|
|
|
|
|
const EXPAND_TREASHHOLD: f32 = 0.80;
|
|
|
|
|
|
|
|
let width = string_width(&s);
|
|
|
|
let used_percent = width as f32 / term_width as f32;
|
|
|
|
|
|
|
|
if width < term_width && used_percent > EXPAND_TREASHHOLD {
|
|
|
|
table.draw_table(config, &color_hm, alignments, &theme, term_width, true)
|
|
|
|
} else {
|
|
|
|
Some(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => None,
|
|
|
|
};
|
2022-10-03 16:40:16 +00:00
|
|
|
|
|
|
|
Ok(table)
|
|
|
|
}
|
|
|
|
|
2022-02-25 09:27:50 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn handle_row_stream(
|
|
|
|
engine_state: &EngineState,
|
2022-05-26 11:53:05 +00:00
|
|
|
stack: &mut Stack,
|
2022-02-25 09:27:50 +00:00
|
|
|
stream: ListStream,
|
|
|
|
call: &Call,
|
|
|
|
row_offset: usize,
|
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
metadata: Option<PipelineMetadata>,
|
2022-10-03 16:40:16 +00:00
|
|
|
) -> Result<PipelineData, nu_protocol::ShellError> {
|
2022-02-25 09:27:50 +00:00
|
|
|
let stream = match metadata {
|
2022-11-15 17:12:56 +00:00
|
|
|
// First, `ls` sources:
|
2022-02-25 09:27:50 +00:00
|
|
|
Some(PipelineMetadata {
|
|
|
|
data_source: DataSource::Ls,
|
|
|
|
}) => {
|
2022-04-18 22:28:01 +00:00
|
|
|
let config = engine_state.config.clone();
|
2022-02-25 09:27:50 +00:00
|
|
|
let ctrlc = ctrlc.clone();
|
2022-07-20 15:09:33 +00:00
|
|
|
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
|
|
|
Some(v) => Some(env_to_string("LS_COLORS", &v, engine_state, stack)?),
|
|
|
|
None => None,
|
|
|
|
};
|
|
|
|
let ls_colors = get_ls_colors(ls_colors_env_str);
|
2022-08-29 14:52:55 +00:00
|
|
|
|
2022-02-25 09:27:50 +00:00
|
|
|
ListStream::from_stream(
|
2022-08-31 23:09:40 +00:00
|
|
|
stream.map(move |mut x| match &mut x {
|
2022-02-25 09:27:50 +00:00
|
|
|
Value::Record { cols, vals, .. } => {
|
|
|
|
let mut idx = 0;
|
|
|
|
|
|
|
|
while idx < cols.len() {
|
2022-11-15 17:12:56 +00:00
|
|
|
// Only the name column gets special colors, for now
|
2022-02-25 09:27:50 +00:00
|
|
|
if cols[idx] == "name" {
|
2022-10-03 16:40:16 +00:00
|
|
|
if let Some(Value::String { val, span }) = vals.get(idx) {
|
|
|
|
let val = render_path_name(val, &config, &ls_colors, *span);
|
|
|
|
if let Some(val) = val {
|
2022-09-14 10:55:41 +00:00
|
|
|
vals[idx] = val;
|
2022-02-25 09:27:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
x
|
|
|
|
}
|
|
|
|
_ => x,
|
|
|
|
}),
|
|
|
|
ctrlc,
|
|
|
|
)
|
|
|
|
}
|
2022-11-15 17:12:56 +00:00
|
|
|
// Next, `into html -l` sources:
|
|
|
|
Some(PipelineMetadata {
|
|
|
|
data_source: DataSource::HtmlThemes,
|
|
|
|
}) => {
|
|
|
|
let ctrlc = ctrlc.clone();
|
|
|
|
|
|
|
|
ListStream::from_stream(
|
|
|
|
stream.map(move |mut x| match &mut x {
|
|
|
|
Value::Record { cols, vals, .. } => {
|
|
|
|
let mut idx = 0;
|
|
|
|
// Every column in the HTML theme table except 'name' is colored
|
|
|
|
while idx < cols.len() {
|
|
|
|
if cols[idx] != "name" {
|
|
|
|
// Simple routine to grab the hex code, convert to a style,
|
|
|
|
// then place it in a new Value::String.
|
|
|
|
if let Some(Value::String { val, span }) = vals.get(idx) {
|
|
|
|
let s = match color_from_hex(val) {
|
|
|
|
Ok(c) => match c {
|
|
|
|
// .normal() just sets the text foreground color.
|
|
|
|
Some(c) => c.normal(),
|
|
|
|
None => nu_ansi_term::Style::default(),
|
|
|
|
},
|
|
|
|
Err(_) => nu_ansi_term::Style::default(),
|
|
|
|
};
|
|
|
|
vals[idx] = Value::String {
|
|
|
|
// Apply the style (ANSI codes) to the string
|
|
|
|
val: s.paint(val).to_string(),
|
|
|
|
span: *span,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
x
|
|
|
|
}
|
|
|
|
_ => x,
|
|
|
|
}),
|
|
|
|
ctrlc,
|
|
|
|
)
|
|
|
|
}
|
2022-02-25 09:27:50 +00:00
|
|
|
_ => stream,
|
|
|
|
};
|
|
|
|
|
|
|
|
let head = call.head;
|
2022-05-26 11:53:05 +00:00
|
|
|
let width_param: Option<i64> = call.get_flag(engine_state, stack, "width")?;
|
2022-02-25 09:27:50 +00:00
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
let collapse: bool = call.has_flag("collapse");
|
|
|
|
|
|
|
|
let expand: bool = call.has_flag("expand");
|
|
|
|
let limit: Option<usize> = call.get_flag(engine_state, stack, "expand-deep")?;
|
|
|
|
let flatten: bool = call.has_flag("flatten");
|
|
|
|
let flatten_separator: Option<String> =
|
|
|
|
call.get_flag(engine_state, stack, "flatten-separator")?;
|
|
|
|
|
|
|
|
let table_view = match (expand, collapse) {
|
|
|
|
(_, true) => TableView::Collapsed,
|
|
|
|
(true, _) => TableView::Expanded {
|
|
|
|
flatten,
|
|
|
|
flatten_separator,
|
|
|
|
limit,
|
|
|
|
},
|
|
|
|
_ => TableView::General,
|
|
|
|
};
|
|
|
|
|
2022-02-25 19:51:31 +00:00
|
|
|
Ok(PipelineData::ExternalStream {
|
2022-03-08 01:17:33 +00:00
|
|
|
stdout: Some(RawStream::new(
|
2022-02-25 09:27:50 +00:00
|
|
|
Box::new(PagingTableCreator {
|
|
|
|
row_offset,
|
2022-04-18 22:28:01 +00:00
|
|
|
config: engine_state.get_config().clone(),
|
2022-02-25 09:27:50 +00:00
|
|
|
ctrlc: ctrlc.clone(),
|
|
|
|
head,
|
|
|
|
stream,
|
2022-05-26 11:53:05 +00:00
|
|
|
width_param,
|
2022-10-03 16:40:16 +00:00
|
|
|
view: table_view,
|
2022-02-25 09:27:50 +00:00
|
|
|
}),
|
|
|
|
ctrlc,
|
|
|
|
head,
|
2022-03-08 01:17:33 +00:00
|
|
|
)),
|
2022-02-25 19:51:31 +00:00
|
|
|
stderr: None,
|
|
|
|
exit_code: None,
|
|
|
|
span: head,
|
|
|
|
metadata: None,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 03:51:57 +00:00
|
|
|
trim_end_newline: false,
|
2022-02-25 19:51:31 +00:00
|
|
|
})
|
2022-02-25 09:27:50 +00:00
|
|
|
}
|
|
|
|
|
2022-08-18 10:45:49 +00:00
|
|
|
fn make_clickable_link(
|
|
|
|
full_path: String,
|
|
|
|
link_name: Option<&str>,
|
|
|
|
show_clickable_links: bool,
|
|
|
|
) -> String {
|
|
|
|
// uri's based on this https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
|
|
|
|
|
|
|
if show_clickable_links {
|
|
|
|
format!(
|
|
|
|
"\x1b]8;;{}\x1b\\{}\x1b]8;;\x1b\\",
|
|
|
|
match Url::from_file_path(full_path.clone()) {
|
|
|
|
Ok(url) => url.to_string(),
|
|
|
|
Err(_) => full_path.clone(),
|
|
|
|
},
|
|
|
|
link_name.unwrap_or(full_path.as_str())
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
match link_name {
|
|
|
|
Some(link_name) => link_name.to_string(),
|
|
|
|
None => full_path,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-11 17:35:40 +00:00
|
|
|
fn convert_to_table(
|
2021-12-03 06:15:23 +00:00
|
|
|
row_offset: usize,
|
2021-12-20 21:03:47 +00:00
|
|
|
input: &[Value],
|
2021-11-09 06:14:00 +00:00
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
2021-11-14 19:25:57 +00:00
|
|
|
config: &Config,
|
2021-12-19 07:46:13 +00:00
|
|
|
head: Span,
|
2022-10-03 16:40:16 +00:00
|
|
|
termwidth: usize,
|
2022-11-16 14:03:56 +00:00
|
|
|
color_hm: &NuColorMap,
|
2022-10-03 16:40:16 +00:00
|
|
|
) -> Result<Option<NuTable>, ShellError> {
|
2021-12-20 21:03:47 +00:00
|
|
|
let mut headers = get_columns(input);
|
|
|
|
let mut input = input.iter().peekable();
|
2021-12-07 20:06:14 +00:00
|
|
|
let float_precision = config.float_precision as usize;
|
2022-09-28 22:07:33 +00:00
|
|
|
let with_index = match config.table_index_mode {
|
|
|
|
TableIndexMode::Always => true,
|
|
|
|
TableIndexMode::Never => false,
|
|
|
|
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
|
|
|
|
};
|
2021-09-29 18:25:05 +00:00
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
if input.peek().is_none() {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
|
|
|
if !headers.is_empty() && 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)
|
|
|
|
.map(|text| {
|
|
|
|
NuTable::create_cell(
|
|
|
|
text,
|
|
|
|
TextStyle {
|
|
|
|
alignment: Alignment::Center,
|
|
|
|
color_style: Some(color_hm["header"]),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let with_header = !headers.is_empty();
|
|
|
|
let mut count_columns = headers.len();
|
|
|
|
|
|
|
|
let mut data: Vec<Vec<_>> = if headers.is_empty() {
|
|
|
|
Vec::new()
|
|
|
|
} else {
|
|
|
|
vec![headers]
|
|
|
|
};
|
|
|
|
|
|
|
|
for (row_num, item) in input.enumerate() {
|
|
|
|
if let Some(ctrlc) = &ctrlc {
|
|
|
|
if ctrlc.load(Ordering::SeqCst) {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Value::Error { error } = item {
|
|
|
|
return Err(error.clone());
|
2021-09-29 18:25:05 +00:00
|
|
|
}
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
let mut row = vec![];
|
|
|
|
if with_index {
|
|
|
|
let text = match &item {
|
|
|
|
Value::Record { .. } => item
|
|
|
|
.get_data_by_key(INDEX_COLUMN_NAME)
|
|
|
|
.map(|value| value.into_string("", config)),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
.unwrap_or_else(|| (row_num + row_offset).to_string());
|
2022-06-27 11:33:45 +00:00
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
let value = make_index_string(text, color_hm);
|
2022-10-03 16:40:16 +00:00
|
|
|
let value = NuTable::create_cell(value.0, value.1);
|
2021-09-29 18:25:05 +00:00
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
row.push(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if !with_header {
|
|
|
|
let text = item.into_abbreviated_string(config);
|
|
|
|
let text_type = item.get_type().to_string();
|
2022-11-16 14:03:56 +00:00
|
|
|
let value = make_styled_string(text, &text_type, color_hm, float_precision);
|
2022-10-03 16:40:16 +00:00
|
|
|
let value = NuTable::create_cell(value.0, value.1);
|
|
|
|
|
|
|
|
row.push(value);
|
|
|
|
} else {
|
2022-11-26 20:26:54 +00:00
|
|
|
let skip_num = usize::from(with_index);
|
2022-11-16 14:03:56 +00:00
|
|
|
for header in data[0].iter().skip(skip_num) {
|
|
|
|
let value =
|
|
|
|
create_table2_entry_basic(item, header.as_ref(), head, config, color_hm);
|
2022-10-03 16:40:16 +00:00
|
|
|
let value = NuTable::create_cell(value.0, value.1);
|
|
|
|
row.push(value);
|
2021-11-09 06:14:00 +00:00
|
|
|
}
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
count_columns = max(count_columns, row.len());
|
|
|
|
|
|
|
|
data.push(row);
|
|
|
|
}
|
|
|
|
|
|
|
|
let count_rows = data.len();
|
|
|
|
let table = NuTable::new(
|
|
|
|
data,
|
|
|
|
(count_rows, count_columns),
|
|
|
|
termwidth,
|
|
|
|
with_header,
|
|
|
|
with_index,
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(Some(table))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-10-26 16:36:42 +00:00
|
|
|
#[allow(clippy::into_iter_on_ref)]
|
2022-10-22 16:52:32 +00:00
|
|
|
fn convert_to_table2<'a>(
|
2022-10-03 16:40:16 +00:00
|
|
|
row_offset: usize,
|
2022-10-22 16:52:32 +00:00
|
|
|
input: impl Iterator<Item = &'a Value> + ExactSizeIterator + Clone,
|
2022-10-03 16:40:16 +00:00
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
config: &Config,
|
|
|
|
head: Span,
|
2022-11-16 14:03:56 +00:00
|
|
|
color_hm: &NuColorMap,
|
2022-10-03 16:40:16 +00:00
|
|
|
theme: &TableTheme,
|
|
|
|
deep: Option<usize>,
|
|
|
|
flatten: bool,
|
|
|
|
flatten_sep: &str,
|
2022-11-16 14:03:56 +00:00
|
|
|
available_width: usize,
|
2022-10-03 16:40:16 +00:00
|
|
|
) -> Result<Option<NuTable>, ShellError> {
|
2022-11-16 14:03:56 +00:00
|
|
|
const PADDING_SPACE: usize = 2;
|
|
|
|
const SPLIT_LINE_SPACE: usize = 1;
|
|
|
|
const ADDITIONAL_CELL_SPACE: usize = PADDING_SPACE + SPLIT_LINE_SPACE;
|
|
|
|
const TRUNCATE_CELL_WIDTH: usize = 3;
|
|
|
|
const MIN_CELL_CONTENT_WIDTH: usize = 1;
|
|
|
|
const OK_CELL_CONTENT_WIDTH: usize = 25;
|
|
|
|
|
2022-10-22 16:52:32 +00:00
|
|
|
if input.len() == 0 {
|
2022-10-03 16:40:16 +00:00
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
// 2 - split lines
|
|
|
|
let mut available_width = available_width.saturating_sub(SPLIT_LINE_SPACE + SPLIT_LINE_SPACE);
|
|
|
|
if available_width < MIN_CELL_CONTENT_WIDTH {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
|
|
|
let headers = get_columns(input.clone());
|
2022-10-03 16:40:16 +00:00
|
|
|
|
|
|
|
let with_index = match config.table_index_mode {
|
|
|
|
TableIndexMode::Always => true,
|
|
|
|
TableIndexMode::Never => false,
|
|
|
|
TableIndexMode::Auto => headers.iter().any(|header| header == INDEX_COLUMN_NAME),
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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 with_header = !headers.is_empty();
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
let mut data = vec![vec![]; input.len()];
|
|
|
|
if !headers.is_empty() {
|
|
|
|
data.push(vec![]);
|
2022-10-03 16:40:16 +00:00
|
|
|
};
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
if with_index {
|
|
|
|
let mut column_width = 0;
|
|
|
|
|
|
|
|
if with_header {
|
|
|
|
data[0].push(NuTable::create_cell("#", header_style(color_hm)));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (row, item) in input.clone().into_iter().enumerate() {
|
|
|
|
if let Some(ctrlc) = &ctrlc {
|
|
|
|
if ctrlc.load(Ordering::SeqCst) {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Value::Error { error } = item {
|
|
|
|
return Err(error.clone());
|
2021-10-11 17:35:40 +00:00
|
|
|
}
|
2022-11-16 14:03:56 +00:00
|
|
|
|
|
|
|
let index = row + row_offset;
|
|
|
|
let text = matches!(item, Value::Record { .. })
|
|
|
|
.then(|| lookup_index_value(item, config).unwrap_or_else(|| index.to_string()))
|
|
|
|
.unwrap_or_else(|| index.to_string());
|
|
|
|
|
|
|
|
let value = make_index_string(text, color_hm);
|
|
|
|
|
|
|
|
let width = string_width(&value.0);
|
|
|
|
column_width = max(column_width, width);
|
|
|
|
|
|
|
|
let value = NuTable::create_cell(value.0, value.1);
|
2022-12-04 23:47:46 +00:00
|
|
|
|
|
|
|
let row = if with_header { row + 1 } else { row };
|
2022-11-16 14:03:56 +00:00
|
|
|
data[row].push(value);
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
if column_width + ADDITIONAL_CELL_SPACE > available_width {
|
|
|
|
available_width = 0;
|
|
|
|
} else {
|
|
|
|
available_width -= column_width + ADDITIONAL_CELL_SPACE;
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
2022-11-16 14:03:56 +00:00
|
|
|
}
|
2022-10-03 16:40:16 +00:00
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
if !with_header {
|
|
|
|
for (row, item) in input.into_iter().enumerate() {
|
|
|
|
if let Some(ctrlc) = &ctrlc {
|
|
|
|
if ctrlc.load(Ordering::SeqCst) {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
2022-04-30 14:07:46 +00:00
|
|
|
}
|
2021-09-29 18:25:05 +00:00
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
if let Value::Error { error } = item {
|
|
|
|
return Err(error.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
let value = convert_to_table2_entry(
|
|
|
|
item,
|
|
|
|
config,
|
|
|
|
&ctrlc,
|
|
|
|
color_hm,
|
|
|
|
theme,
|
|
|
|
deep,
|
|
|
|
flatten,
|
|
|
|
flatten_sep,
|
|
|
|
available_width,
|
|
|
|
);
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
let value = NuTable::create_cell(value.0, value.1);
|
2022-11-16 14:03:56 +00:00
|
|
|
data[row].push(value);
|
|
|
|
}
|
2021-12-21 09:05:16 +00:00
|
|
|
|
2022-11-23 03:57:27 +00:00
|
|
|
let count_columns = if with_index { 2 } else { 1 };
|
2022-11-16 14:03:56 +00:00
|
|
|
let size = (data.len(), count_columns);
|
|
|
|
let table = NuTable::new(data, size, usize::MAX, with_header, with_index);
|
|
|
|
|
|
|
|
return Ok(Some(table));
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut widths = Vec::new();
|
|
|
|
let mut truncate = false;
|
|
|
|
let count_columns = headers.len();
|
|
|
|
for (col, header) in headers.into_iter().enumerate() {
|
|
|
|
let is_last_col = col + 1 == count_columns;
|
|
|
|
|
|
|
|
let mut nessary_space = PADDING_SPACE;
|
|
|
|
if !is_last_col {
|
|
|
|
nessary_space += SPLIT_LINE_SPACE;
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
if available_width == 0 || available_width <= nessary_space {
|
2022-11-17 13:51:04 +00:00
|
|
|
// MUST NEVER HAPPEN (ideally)
|
|
|
|
// but it does...
|
|
|
|
|
|
|
|
truncate = true;
|
2022-11-16 14:03:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-11-17 13:51:04 +00:00
|
|
|
available_width -= nessary_space;
|
2022-11-16 14:03:56 +00:00
|
|
|
|
|
|
|
let mut column_width = string_width(&header);
|
|
|
|
|
|
|
|
data[0].push(NuTable::create_cell(&header, header_style(color_hm)));
|
|
|
|
|
|
|
|
for (row, item) in input.clone().into_iter().enumerate() {
|
|
|
|
if let Some(ctrlc) = &ctrlc {
|
|
|
|
if ctrlc.load(Ordering::SeqCst) {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Value::Error { error } = item {
|
|
|
|
return Err(error.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
let value = create_table2_entry(
|
|
|
|
item,
|
|
|
|
&header,
|
|
|
|
head,
|
2022-10-03 16:40:16 +00:00
|
|
|
config,
|
|
|
|
&ctrlc,
|
|
|
|
color_hm,
|
|
|
|
theme,
|
|
|
|
deep,
|
|
|
|
flatten,
|
|
|
|
flatten_sep,
|
2022-11-16 14:03:56 +00:00
|
|
|
available_width,
|
2022-10-03 16:40:16 +00:00
|
|
|
);
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
let value_width = string_width(&value.0);
|
|
|
|
column_width = max(column_width, value_width);
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
let value = NuTable::create_cell(value.0, value.1);
|
2022-11-16 14:03:56 +00:00
|
|
|
|
2022-11-17 13:51:04 +00:00
|
|
|
data[row + 1].push(value);
|
2022-11-16 14:03:56 +00:00
|
|
|
}
|
|
|
|
|
2022-11-17 13:51:04 +00:00
|
|
|
if column_width >= available_width
|
|
|
|
|| (!is_last_col && column_width + nessary_space >= available_width)
|
|
|
|
{
|
2022-11-16 14:03:56 +00:00
|
|
|
// so we try to do soft landing
|
|
|
|
// by doing a truncating in case there will be enough space for it.
|
|
|
|
|
|
|
|
column_width = string_width(&header);
|
|
|
|
|
|
|
|
for (row, item) in input.clone().into_iter().enumerate() {
|
|
|
|
if let Some(ctrlc) = &ctrlc {
|
|
|
|
if ctrlc.load(Ordering::SeqCst) {
|
|
|
|
return Ok(None);
|
2022-10-22 16:52:32 +00:00
|
|
|
}
|
2022-11-16 14:03:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let value = create_table2_entry_basic(item, &header, head, config, color_hm);
|
|
|
|
let value = wrap_nu_text(value, available_width);
|
|
|
|
|
|
|
|
let value_width = string_width(&value.0);
|
|
|
|
column_width = max(column_width, value_width);
|
2022-10-03 16:40:16 +00:00
|
|
|
|
|
|
|
let value = NuTable::create_cell(value.0, value.1);
|
2022-11-16 14:03:56 +00:00
|
|
|
|
2022-11-17 13:51:04 +00:00
|
|
|
*data[row + 1].last_mut().expect("unwrap") = value;
|
2021-09-29 18:25:05 +00:00
|
|
|
}
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
let is_suitable_for_wrap =
|
|
|
|
available_width >= string_width(&header) && available_width >= OK_CELL_CONTENT_WIDTH;
|
|
|
|
if column_width >= available_width && is_suitable_for_wrap {
|
|
|
|
// so we try to do soft landing ONCE AGAIN
|
|
|
|
// but including a wrap
|
|
|
|
|
|
|
|
column_width = string_width(&header);
|
|
|
|
|
|
|
|
for (row, item) in input.clone().into_iter().enumerate() {
|
|
|
|
if let Some(ctrlc) = &ctrlc {
|
|
|
|
if ctrlc.load(Ordering::SeqCst) {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let value = create_table2_entry_basic(item, &header, head, config, color_hm);
|
|
|
|
let value = wrap_nu_text(value, OK_CELL_CONTENT_WIDTH);
|
|
|
|
|
|
|
|
let value = NuTable::create_cell(value.0, value.1);
|
|
|
|
|
2022-11-17 13:51:04 +00:00
|
|
|
*data[row + 1].last_mut().expect("unwrap") = value;
|
2022-11-16 14:03:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if column_width > available_width {
|
2022-11-17 13:51:04 +00:00
|
|
|
// remove just added column
|
|
|
|
for row in &mut data {
|
|
|
|
row.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
available_width += nessary_space;
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
truncate = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-11-17 13:51:04 +00:00
|
|
|
available_width -= column_width;
|
2022-11-16 14:03:56 +00:00
|
|
|
widths.push(column_width);
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
if truncate {
|
2022-11-17 13:51:04 +00:00
|
|
|
if available_width <= TRUNCATE_CELL_WIDTH + PADDING_SPACE {
|
2022-11-16 14:03:56 +00:00
|
|
|
// back up by removing last column.
|
|
|
|
// it's ALWAYS MUST has us enough space for a shift column
|
|
|
|
while let Some(width) = widths.pop() {
|
|
|
|
for row in &mut data {
|
|
|
|
row.pop();
|
|
|
|
}
|
|
|
|
|
2022-11-17 13:51:04 +00:00
|
|
|
available_width += width + PADDING_SPACE + SPLIT_LINE_SPACE;
|
2022-11-16 14:03:56 +00:00
|
|
|
|
2022-11-17 13:51:04 +00:00
|
|
|
if available_width > TRUNCATE_CELL_WIDTH + PADDING_SPACE {
|
2022-11-16 14:03:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// this must be a RARE case or even NEVER happen,
|
|
|
|
// but we do check it just in case.
|
|
|
|
if widths.is_empty() {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
|
|
|
let shift = NuTable::create_cell(String::from("..."), TextStyle::default());
|
|
|
|
for row in &mut data {
|
|
|
|
row.push(shift.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
widths.push(3);
|
|
|
|
}
|
|
|
|
|
|
|
|
let count_columns = widths.len() + with_index as usize;
|
2022-10-03 16:40:16 +00:00
|
|
|
let count_rows = data.len();
|
2022-11-16 14:03:56 +00:00
|
|
|
let size = (count_rows, count_columns);
|
|
|
|
|
|
|
|
let table = NuTable::new(data, size, usize::MAX, with_header, with_index);
|
2022-10-03 16:40:16 +00:00
|
|
|
|
|
|
|
Ok(Some(table))
|
|
|
|
}
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
fn lookup_index_value(item: &Value, config: &Config) -> Option<String> {
|
|
|
|
item.get_data_by_key(INDEX_COLUMN_NAME)
|
|
|
|
.map(|value| value.into_string("", config))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn header_style(color_hm: &NuColorMap) -> TextStyle {
|
|
|
|
TextStyle {
|
|
|
|
alignment: Alignment::Center,
|
|
|
|
color_style: Some(color_hm["header"]),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-11-16 14:03:56 +00:00
|
|
|
fn create_table2_entry_basic(
|
|
|
|
item: &Value,
|
|
|
|
header: &str,
|
|
|
|
head: Span,
|
|
|
|
config: &Config,
|
|
|
|
color_hm: &NuColorMap,
|
|
|
|
) -> NuText {
|
|
|
|
match item {
|
|
|
|
Value::Record { .. } => {
|
|
|
|
let val = header.to_owned();
|
|
|
|
let path = PathMember::String { val, span: head };
|
|
|
|
let val = item.clone().follow_cell_path(&[path], false);
|
|
|
|
|
|
|
|
match val {
|
|
|
|
Ok(val) => value_to_styled_string(&val, config, color_hm),
|
|
|
|
Err(_) => error_sign(color_hm),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => value_to_styled_string(item, config, color_hm),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn create_table2_entry(
|
|
|
|
item: &Value,
|
|
|
|
header: &str,
|
|
|
|
head: Span,
|
2022-10-03 16:40:16 +00:00
|
|
|
config: &Config,
|
|
|
|
ctrlc: &Option<Arc<AtomicBool>>,
|
2022-11-16 14:03:56 +00:00
|
|
|
color_hm: &NuColorMap,
|
2022-10-03 16:40:16 +00:00
|
|
|
theme: &TableTheme,
|
|
|
|
deep: Option<usize>,
|
|
|
|
flatten: bool,
|
|
|
|
flatten_sep: &str,
|
2022-11-16 14:03:56 +00:00
|
|
|
width: usize,
|
|
|
|
) -> NuText {
|
|
|
|
match item {
|
|
|
|
Value::Record { .. } => {
|
|
|
|
let val = header.to_owned();
|
|
|
|
let path = PathMember::String { val, span: head };
|
|
|
|
let val = item.clone().follow_cell_path(&[path], false);
|
|
|
|
|
|
|
|
match val {
|
|
|
|
Ok(val) => convert_to_table2_entry(
|
|
|
|
&val,
|
|
|
|
config,
|
|
|
|
ctrlc,
|
|
|
|
color_hm,
|
|
|
|
theme,
|
|
|
|
deep,
|
|
|
|
flatten,
|
|
|
|
flatten_sep,
|
|
|
|
width,
|
|
|
|
),
|
|
|
|
Err(_) => wrap_nu_text(error_sign(color_hm), width),
|
|
|
|
}
|
2021-09-29 18:25:05 +00:00
|
|
|
}
|
2022-11-16 14:03:56 +00:00
|
|
|
_ => convert_to_table2_entry(
|
|
|
|
item,
|
|
|
|
config,
|
|
|
|
ctrlc,
|
|
|
|
color_hm,
|
|
|
|
theme,
|
|
|
|
deep,
|
|
|
|
flatten,
|
|
|
|
flatten_sep,
|
|
|
|
width,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn error_sign(color_hm: &HashMap<String, nu_ansi_term::Style>) -> (String, TextStyle) {
|
|
|
|
make_styled_string(String::from("❎"), "empty", color_hm, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn wrap_nu_text(mut text: NuText, width: usize) -> NuText {
|
|
|
|
text.0 = nu_table::wrap_string(&text.0, width);
|
|
|
|
text
|
|
|
|
}
|
2021-09-29 18:25:05 +00:00
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn convert_to_table2_entry(
|
|
|
|
item: &Value,
|
|
|
|
config: &Config,
|
|
|
|
ctrlc: &Option<Arc<AtomicBool>>,
|
|
|
|
color_hm: &NuColorMap,
|
|
|
|
theme: &TableTheme,
|
|
|
|
deep: Option<usize>,
|
|
|
|
flatten: bool,
|
|
|
|
flatten_sep: &str,
|
|
|
|
width: usize,
|
|
|
|
) -> NuText {
|
2022-10-22 16:52:32 +00:00
|
|
|
let is_limit_reached = matches!(deep, Some(0));
|
|
|
|
if is_limit_reached {
|
2022-11-16 14:03:56 +00:00
|
|
|
return wrap_nu_text(value_to_styled_string(item, config, color_hm), width);
|
2022-10-22 16:52:32 +00:00
|
|
|
}
|
2022-10-03 16:40:16 +00:00
|
|
|
|
2022-10-22 16:52:32 +00:00
|
|
|
match &item {
|
|
|
|
Value::Record { span, cols, vals } => {
|
|
|
|
if cols.is_empty() && vals.is_empty() {
|
2022-11-16 14:03:56 +00:00
|
|
|
wrap_nu_text(value_to_styled_string(item, config, color_hm), width)
|
2022-10-22 16:52:32 +00:00
|
|
|
} else {
|
|
|
|
let table = convert_to_table2(
|
|
|
|
0,
|
|
|
|
std::iter::once(item),
|
|
|
|
ctrlc.clone(),
|
|
|
|
config,
|
|
|
|
*span,
|
|
|
|
color_hm,
|
|
|
|
theme,
|
|
|
|
deep.map(|i| i - 1),
|
|
|
|
flatten,
|
|
|
|
flatten_sep,
|
2022-11-16 14:03:56 +00:00
|
|
|
width,
|
2022-10-03 16:40:16 +00:00
|
|
|
);
|
|
|
|
|
2022-10-22 16:52:32 +00:00
|
|
|
let inner_table = table.map(|table| {
|
|
|
|
table.and_then(|table| {
|
2022-11-16 14:03:56 +00:00
|
|
|
let alignments = Alignments::default();
|
|
|
|
table.draw_table(config, color_hm, alignments, theme, usize::MAX, false)
|
2022-10-22 16:52:32 +00:00
|
|
|
})
|
|
|
|
});
|
2022-11-16 14:03:56 +00:00
|
|
|
|
2022-10-22 16:52:32 +00:00
|
|
|
if let Ok(Some(table)) = inner_table {
|
|
|
|
(table, TextStyle::default())
|
|
|
|
} else {
|
|
|
|
// error so back down to the default
|
2022-11-16 14:03:56 +00:00
|
|
|
wrap_nu_text(value_to_styled_string(item, config, color_hm), width)
|
2022-10-22 16:52:32 +00:00
|
|
|
}
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
2022-10-22 16:52:32 +00:00
|
|
|
}
|
|
|
|
Value::List { vals, span } => {
|
|
|
|
let is_simple_list = vals
|
|
|
|
.iter()
|
|
|
|
.all(|v| !matches!(v, Value::Record { .. } | Value::List { .. }));
|
|
|
|
|
|
|
|
if flatten && is_simple_list {
|
2022-11-16 14:03:56 +00:00
|
|
|
wrap_nu_text(
|
|
|
|
convert_value_list_to_string(vals, config, color_hm, flatten_sep),
|
|
|
|
width,
|
|
|
|
)
|
2022-10-03 16:40:16 +00:00
|
|
|
} else {
|
2022-10-22 16:52:32 +00:00
|
|
|
let table = convert_to_table2(
|
|
|
|
0,
|
|
|
|
vals.iter(),
|
|
|
|
ctrlc.clone(),
|
|
|
|
config,
|
|
|
|
*span,
|
|
|
|
color_hm,
|
|
|
|
theme,
|
|
|
|
deep.map(|i| i - 1),
|
|
|
|
flatten,
|
|
|
|
flatten_sep,
|
2022-11-16 14:03:56 +00:00
|
|
|
width,
|
2022-10-22 16:52:32 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
let inner_table = table.map(|table| {
|
|
|
|
table.and_then(|table| {
|
2022-11-16 14:03:56 +00:00
|
|
|
let alignments = Alignments::default();
|
|
|
|
table.draw_table(config, color_hm, alignments, theme, usize::MAX, false)
|
2022-10-22 16:52:32 +00:00
|
|
|
})
|
|
|
|
});
|
|
|
|
if let Ok(Some(table)) = inner_table {
|
|
|
|
(table, TextStyle::default())
|
|
|
|
} else {
|
|
|
|
// error so back down to the default
|
2022-11-16 14:03:56 +00:00
|
|
|
|
|
|
|
wrap_nu_text(value_to_styled_string(item, config, color_hm), width)
|
2022-10-22 16:52:32 +00:00
|
|
|
}
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-16 14:03:56 +00:00
|
|
|
_ => wrap_nu_text(value_to_styled_string(item, config, color_hm), width), // unknown type.
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
fn convert_value_list_to_string(
|
|
|
|
vals: &[Value],
|
2022-10-03 16:40:16 +00:00
|
|
|
config: &Config,
|
2022-11-16 14:03:56 +00:00
|
|
|
color_hm: &NuColorMap,
|
|
|
|
flatten_sep: &str,
|
|
|
|
) -> NuText {
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
for value in vals {
|
|
|
|
let (text, _) = value_to_styled_string(value, config, color_hm);
|
|
|
|
|
|
|
|
buf.push(text);
|
|
|
|
}
|
|
|
|
let text = buf.join(flatten_sep);
|
|
|
|
(text, TextStyle::default())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn value_to_styled_string(value: &Value, config: &Config, color_hm: &NuColorMap) -> NuText {
|
2022-10-03 16:40:16 +00:00
|
|
|
let float_precision = config.float_precision as usize;
|
|
|
|
make_styled_string(
|
|
|
|
value.into_abbreviated_string(config),
|
|
|
|
&value.get_type().to_string(),
|
|
|
|
color_hm,
|
|
|
|
float_precision,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_styled_string(
|
|
|
|
text: String,
|
|
|
|
text_type: &str,
|
2022-11-16 14:03:56 +00:00
|
|
|
color_hm: &NuColorMap,
|
2022-10-03 16:40:16 +00:00
|
|
|
float_precision: usize,
|
2022-11-16 14:03:56 +00:00
|
|
|
) -> NuText {
|
|
|
|
if text_type == "float" {
|
2022-10-03 16:40:16 +00:00
|
|
|
// set dynamic precision from config
|
|
|
|
let precise_number = match convert_with_precision(&text, float_precision) {
|
|
|
|
Ok(num) => num,
|
|
|
|
Err(e) => e.to_string(),
|
|
|
|
};
|
|
|
|
(precise_number, style_primitive(text_type, color_hm))
|
2021-09-29 18:25:05 +00:00
|
|
|
} else {
|
2022-10-03 16:40:16 +00:00
|
|
|
(text, style_primitive(text_type, color_hm))
|
2021-09-29 18:25:05 +00:00
|
|
|
}
|
|
|
|
}
|
2021-11-14 19:25:57 +00:00
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
fn make_index_string(text: String, color_hm: &NuColorMap) -> NuText {
|
|
|
|
let style = TextStyle::new()
|
|
|
|
.alignment(Alignment::Right)
|
|
|
|
.style(color_hm["row_index"]);
|
|
|
|
(text, style)
|
|
|
|
}
|
|
|
|
|
2021-12-07 20:06:14 +00:00
|
|
|
fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellError> {
|
|
|
|
// vall will always be a f64 so convert it with precision formatting
|
|
|
|
let val_float = match val.trim().parse::<f64>() {
|
|
|
|
Ok(f) => f,
|
|
|
|
Err(e) => {
|
2022-04-18 12:34:10 +00:00
|
|
|
return Err(ShellError::GenericError(
|
2021-12-07 20:06:14 +00:00
|
|
|
format!("error converting string [{}] to f64", &val),
|
2022-04-18 12:34:10 +00:00
|
|
|
"".to_string(),
|
|
|
|
None,
|
|
|
|
Some(e.to_string()),
|
|
|
|
Vec::new(),
|
2021-12-07 20:06:14 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Ok(format!("{:.prec$}", val_float, prec = precision))
|
|
|
|
}
|
|
|
|
|
2021-12-03 06:15:23 +00:00
|
|
|
struct PagingTableCreator {
|
|
|
|
head: Span,
|
2022-01-28 18:32:33 +00:00
|
|
|
stream: ListStream,
|
2021-12-03 06:15:23 +00:00
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
config: Config,
|
|
|
|
row_offset: usize,
|
2022-05-26 11:53:05 +00:00
|
|
|
width_param: Option<i64>,
|
2022-10-03 16:40:16 +00:00
|
|
|
view: TableView,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PagingTableCreator {
|
|
|
|
fn build_extended(
|
|
|
|
&self,
|
|
|
|
batch: &[Value],
|
|
|
|
limit: Option<usize>,
|
|
|
|
flatten: bool,
|
|
|
|
flatten_separator: Option<String>,
|
|
|
|
) -> Result<Option<String>, ShellError> {
|
|
|
|
if batch.is_empty() {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
|
|
|
let theme = load_theme_from_config(&self.config);
|
|
|
|
let term_width = get_width_param(self.width_param);
|
|
|
|
let color_hm = get_color_config(&self.config);
|
|
|
|
let table = convert_to_table2(
|
|
|
|
self.row_offset,
|
2022-10-22 16:52:32 +00:00
|
|
|
batch.iter(),
|
2022-10-03 16:40:16 +00:00
|
|
|
self.ctrlc.clone(),
|
|
|
|
&self.config,
|
|
|
|
self.head,
|
|
|
|
&color_hm,
|
|
|
|
&theme,
|
|
|
|
limit,
|
|
|
|
flatten,
|
|
|
|
flatten_separator.as_deref().unwrap_or(" "),
|
2022-11-16 14:03:56 +00:00
|
|
|
term_width,
|
2022-10-03 16:40:16 +00:00
|
|
|
)?;
|
|
|
|
|
|
|
|
let mut table = match table {
|
|
|
|
Some(table) => table,
|
|
|
|
None => return Ok(None),
|
|
|
|
};
|
|
|
|
|
|
|
|
table.truncate(term_width, &theme);
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
let table_s = table.clone().draw_table(
|
2022-10-03 16:40:16 +00:00
|
|
|
&self.config,
|
|
|
|
&color_hm,
|
|
|
|
Alignments::default(),
|
|
|
|
&theme,
|
|
|
|
term_width,
|
2022-11-16 14:03:56 +00:00
|
|
|
false,
|
2022-10-03 16:40:16 +00:00
|
|
|
);
|
|
|
|
|
2022-11-16 14:03:56 +00:00
|
|
|
let table = match table_s {
|
|
|
|
Some(s) => {
|
|
|
|
// check whether we need to expand table or not,
|
|
|
|
// todo: we can make it more effitient
|
|
|
|
|
|
|
|
const EXPAND_TREASHHOLD: f32 = 0.80;
|
|
|
|
|
|
|
|
let width = string_width(&s);
|
|
|
|
let used_percent = width as f32 / term_width as f32;
|
|
|
|
|
|
|
|
if width < term_width && used_percent > EXPAND_TREASHHOLD {
|
|
|
|
table.draw_table(
|
|
|
|
&self.config,
|
|
|
|
&color_hm,
|
|
|
|
Alignments::default(),
|
|
|
|
&theme,
|
|
|
|
term_width,
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
Some(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => None,
|
|
|
|
};
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
Ok(table)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_collapsed(&self, batch: Vec<Value>) -> Result<Option<String>, ShellError> {
|
|
|
|
if batch.is_empty() {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
|
|
|
let color_hm = get_color_config(&self.config);
|
|
|
|
let theme = load_theme_from_config(&self.config);
|
|
|
|
let term_width = get_width_param(self.width_param);
|
|
|
|
let need_footer = matches!(self.config.footer_mode, FooterMode::RowCount(limit) if batch.len() as u64 > limit)
|
|
|
|
|| matches!(self.config.footer_mode, FooterMode::Always);
|
|
|
|
let value = Value::List {
|
|
|
|
vals: batch,
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
let table = nu_table::NuTable::new(
|
|
|
|
value,
|
|
|
|
true,
|
|
|
|
term_width,
|
|
|
|
&self.config,
|
|
|
|
&color_hm,
|
|
|
|
&theme,
|
|
|
|
need_footer,
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(table.draw())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_general(&self, batch: &[Value]) -> Result<Option<String>, ShellError> {
|
|
|
|
let term_width = get_width_param(self.width_param);
|
|
|
|
let color_hm = get_color_config(&self.config);
|
|
|
|
let theme = load_theme_from_config(&self.config);
|
|
|
|
|
|
|
|
let table = convert_to_table(
|
|
|
|
self.row_offset,
|
|
|
|
batch,
|
|
|
|
self.ctrlc.clone(),
|
|
|
|
&self.config,
|
|
|
|
self.head,
|
|
|
|
term_width,
|
|
|
|
&color_hm,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let table = match table {
|
|
|
|
Some(table) => table,
|
|
|
|
None => return Ok(None),
|
|
|
|
};
|
|
|
|
|
|
|
|
let table = table.draw_table(
|
|
|
|
&self.config,
|
|
|
|
&color_hm,
|
|
|
|
Alignments::default(),
|
|
|
|
&theme,
|
|
|
|
term_width,
|
2022-11-16 14:03:56 +00:00
|
|
|
false,
|
2022-10-03 16:40:16 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
Ok(table)
|
|
|
|
}
|
2021-12-03 06:15:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Iterator for PagingTableCreator {
|
2022-01-28 18:32:33 +00:00
|
|
|
type Item = Result<Vec<u8>, ShellError>;
|
2021-12-03 06:15:23 +00:00
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
let mut batch = vec![];
|
|
|
|
|
|
|
|
let start_time = Instant::now();
|
|
|
|
|
|
|
|
let mut idx = 0;
|
|
|
|
|
|
|
|
// Pull from stream until time runs out or we have enough items
|
2022-08-31 23:09:40 +00:00
|
|
|
for item in self.stream.by_ref() {
|
2021-12-03 06:15:23 +00:00
|
|
|
batch.push(item);
|
|
|
|
idx += 1;
|
|
|
|
|
|
|
|
if idx % STREAM_TIMEOUT_CHECK_INTERVAL == 0 {
|
|
|
|
let end_time = Instant::now();
|
|
|
|
|
|
|
|
// If we've been buffering over a second, go ahead and send out what we have so far
|
|
|
|
if (end_time - start_time).as_secs() >= 1 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if idx == STREAM_PAGE_SIZE {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(ctrlc) = &self.ctrlc {
|
|
|
|
if ctrlc.load(Ordering::SeqCst) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
let table = match &self.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()),
|
|
|
|
};
|
2021-12-03 06:15:23 +00:00
|
|
|
|
2022-10-21 16:02:25 +00:00
|
|
|
self.row_offset += idx;
|
|
|
|
|
2021-12-03 06:15:23 +00:00
|
|
|
match table {
|
2022-12-14 03:45:37 +00:00
|
|
|
Ok(Some(table)) => {
|
|
|
|
let mut bytes = table.as_bytes().to_vec();
|
|
|
|
bytes.push(b'\n'); // tabled tables don't come with a newline on the end
|
|
|
|
Some(Ok(bytes))
|
|
|
|
}
|
2021-12-24 07:22:11 +00:00
|
|
|
Err(err) => Some(Err(err)),
|
2021-12-03 06:15:23 +00:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-16 15:35:57 +00:00
|
|
|
fn load_theme_from_config(config: &Config) -> TableTheme {
|
2021-11-14 19:25:57 +00:00
|
|
|
match config.table_mode.as_str() {
|
2022-05-16 15:35:57 +00:00
|
|
|
"basic" => nu_table::TableTheme::basic(),
|
2022-06-28 19:14:20 +00:00
|
|
|
"thin" => nu_table::TableTheme::thin(),
|
2022-05-16 15:35:57 +00:00
|
|
|
"light" => nu_table::TableTheme::light(),
|
2022-06-28 19:14:20 +00:00
|
|
|
"compact" => nu_table::TableTheme::compact(),
|
2022-05-16 15:35:57 +00:00
|
|
|
"with_love" => nu_table::TableTheme::with_love(),
|
2022-06-28 19:14:20 +00:00
|
|
|
"compact_double" => nu_table::TableTheme::compact_double(),
|
2022-05-16 15:35:57 +00:00
|
|
|
"rounded" => nu_table::TableTheme::rounded(),
|
|
|
|
"reinforced" => nu_table::TableTheme::reinforced(),
|
|
|
|
"heavy" => nu_table::TableTheme::heavy(),
|
|
|
|
"none" => nu_table::TableTheme::none(),
|
|
|
|
_ => nu_table::TableTheme::rounded(),
|
2021-11-14 19:25:57 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-14 10:55:41 +00:00
|
|
|
|
|
|
|
fn render_path_name(
|
2022-10-03 16:40:16 +00:00
|
|
|
path: &str,
|
2022-09-14 10:55:41 +00:00
|
|
|
config: &Config,
|
|
|
|
ls_colors: &LsColors,
|
|
|
|
span: Span,
|
|
|
|
) -> Option<Value> {
|
|
|
|
if !config.use_ls_colors {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2022-11-04 18:49:45 +00:00
|
|
|
let stripped_path = nu_utils::strip_ansi_unlikely(path);
|
2022-09-14 10:55:41 +00:00
|
|
|
|
2022-11-04 18:49:45 +00:00
|
|
|
let (style, has_metadata) = match std::fs::symlink_metadata(stripped_path.as_ref()) {
|
2022-09-14 10:55:41 +00:00
|
|
|
Ok(metadata) => (
|
2022-11-04 18:49:45 +00:00
|
|
|
ls_colors.style_for_path_with_metadata(stripped_path.as_ref(), Some(&metadata)),
|
2022-09-14 10:55:41 +00:00
|
|
|
true,
|
|
|
|
),
|
2022-11-04 18:49:45 +00:00
|
|
|
Err(_) => (ls_colors.style_for_path(stripped_path.as_ref()), false),
|
2022-09-14 10:55:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// clickable links don't work in remote SSH sessions
|
|
|
|
let in_ssh_session = std::env::var("SSH_CLIENT").is_ok();
|
|
|
|
let show_clickable_links = config.show_clickable_links_in_ls && !in_ssh_session && has_metadata;
|
|
|
|
|
|
|
|
let ansi_style = style
|
|
|
|
.map(Style::to_crossterm_style)
|
|
|
|
// .map(ToNuAnsiStyle::to_nu_ansi_style)
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
2022-11-04 18:49:45 +00:00
|
|
|
let full_path = PathBuf::from(stripped_path.as_ref())
|
2022-09-14 10:55:41 +00:00
|
|
|
.canonicalize()
|
2022-11-04 18:49:45 +00:00
|
|
|
.unwrap_or_else(|_| PathBuf::from(stripped_path.as_ref()));
|
2022-09-14 10:55:41 +00:00
|
|
|
|
|
|
|
let full_path_link = make_clickable_link(
|
|
|
|
full_path.display().to_string(),
|
|
|
|
Some(path),
|
|
|
|
show_clickable_links,
|
|
|
|
);
|
|
|
|
|
|
|
|
let val = ansi_style.apply(full_path_link).to_string();
|
|
|
|
Some(Value::String { val, span })
|
|
|
|
}
|
2022-10-03 16:40:16 +00:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
enum TableView {
|
|
|
|
General,
|
|
|
|
Collapsed,
|
|
|
|
Expanded {
|
|
|
|
limit: Option<usize>,
|
|
|
|
flatten: bool,
|
|
|
|
flatten_separator: Option<String>,
|
|
|
|
},
|
|
|
|
}
|