2022-09-14 10:55:41 +00:00
|
|
|
use lscolors::{LsColors, Style};
|
color_config now accepts closures as color values (#7141)
# Description
Closes #6909. You can now add closures to your `color_config` themes.
Whenever a value would be printed with `table`, the closure is run with
the value piped-in. The closure must return either a {fg,bg,attr} record
or a color name (`'light_red'` etc.). This returned style is used to
colour the value.
This is entirely backwards-compatible with existing config.nu files.
Example code excerpt:
```
let my_theme = {
header: green_bold
bool: { if $in { 'light_cyan' } else { 'light_red' } }
int: purple_bold
filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } }
duration: purple_bold
date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } }
range: yellow_bold
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }
nothing: white
```
Example output with this in effect:
![2022-11-16 12 47 23 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952558-482de05d-69c7-4bf2-91fc-d0964bf71264.png)
![2022-11-16 12 39 41 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952580-2384bb86-b680-40fe-8192-71bae396c738.png)
![2022-11-15 09 21 54 PM - run_external
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952601-343fc15d-e4a8-4a92-ad89-9a7d17d42748.png)
Slightly important notes:
* Some color_config names, namely "separator", "empty" and "hints", pipe
in `null` instead of a value.
* Currently, doing anything non-trivial inside a closure has an
understandably big perf hit. I currently do not actually recommend
something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' }
}` for serious work, mainly because of the abundance of string-type data
in the world. Nevertheless, lesser-used types like "date" and "duration"
work well with this.
* I had to do some reorganisation in order to make it possible to call
`eval_block()` that late in table rendering. I invented a new struct
called "StyleComputer" which holds the engine_state and stack of the
initial `table` command (implicit or explicit).
* StyleComputer has a `compute()` method which takes a color_config name
and a nu value, and always returns the correct Style, so you don't have
to worry about A) the color_config value was set at all, B) whether it
was set to a closure or not, or C) which default style to use in those
cases.
* Currently, errors encountered during execution of the closures are
thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors
result in a huge perf hit when they are encountered. I think what should
be done is to assume something terrible happened to the user's config
and invalidate the StyleComputer for that `table` run, thus causing
subsequent output to just be Style::default().)
* More thorough tests are forthcoming - ran into some difficulty using
`nu!` to take an alternative config, and for some reason `let-env config
=` statements don't seem to work inside `nu!` pipelines(???)
* The default config.nu has not been updated to make use of this yet. Do
tell if you think I should incorporate that into this.
# User-Facing Changes
See above.
# 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-12-17 13:07:56 +00:00
|
|
|
use nu_color_config::color_from_hex;
|
2023-04-26 18:56:10 +00:00
|
|
|
use nu_color_config::{StyleComputer, TextStyle};
|
|
|
|
use nu_engine::{env_to_string, CallExt};
|
2021-11-17 04:22:37 +00:00
|
|
|
use nu_protocol::{
|
2023-04-26 18:56:10 +00:00
|
|
|
ast::Call,
|
2023-01-02 22:45:43 +00:00
|
|
|
engine::{Command, EngineState, Stack},
|
|
|
|
Category, Config, DataSource, Example, FooterMode, IntoPipelineData, ListStream, PipelineData,
|
2023-04-26 18:56:10 +00:00
|
|
|
PipelineMetadata, RawStream, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
|
|
};
|
|
|
|
use nu_table::{
|
|
|
|
BuildConfig, Cell, CollapsedTable, ExpandedTable, JustTable, NuTable, StringResult,
|
|
|
|
TableConfig, TableOutput, TableTheme,
|
2021-11-17 04:22:37 +00:00
|
|
|
};
|
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;
|
2023-04-26 18:56:10 +00:00
|
|
|
use std::{path::PathBuf, sync::atomic::AtomicBool};
|
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;
|
2022-11-16 14:03:56 +00:00
|
|
|
|
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 {
|
2023-03-01 05:33:02 +00:00
|
|
|
"If the table contains a column called 'index', this column is used as the table index instead of the usual continuous index."
|
2022-06-26 22:32:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-05 12:01:21 +00:00
|
|
|
fn search_terms(&self) -> Vec<&str> {
|
|
|
|
vec!["display", "render"]
|
|
|
|
}
|
|
|
|
|
2023-02-22 16:18:33 +00:00
|
|
|
fn signature(&self) -> Signature {
|
2021-12-23 21:41:29 +00:00
|
|
|
Signature::build("table")
|
2022-12-21 19:20:46 +00:00
|
|
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
|
|
|
// TODO: make this more precise: what turns into string and what into raw stream
|
2021-12-23 21:41:29 +00:00
|
|
|
.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,
|
Fix typos by codespell (#7600)
# Description
Found via `codespell -S target -L
crate,ser,numer,falsy,ro,te,nd,bu,ndoes,statics,ons,fo,rouge,pard`
# User-Facing Changes
None.
# Tests + Formatting
None and done.
# After Submitting
None.
2022-12-26 07:31:26 +00:00
|
|
|
"sets a separator when 'flatten' used",
|
2022-10-03 16:40:16 +00:00
|
|
|
None,
|
|
|
|
)
|
|
|
|
.switch(
|
|
|
|
"collapse",
|
2023-01-15 02:03:32 +00:00
|
|
|
"expand the table structure in collapse mode.\nBe aware collapse mode currently doesn't support width control",
|
2022-10-03 16:40:16 +00:00
|
|
|
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")?;
|
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,
|
2023-03-25 17:12:57 +00:00
|
|
|
width_param,
|
2022-10-03 16:40:16 +00:00
|
|
|
)
|
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,
|
2023-03-25 17:12:57 +00:00
|
|
|
term_width: Option<i64>,
|
2022-10-03 16:40:16 +00:00
|
|
|
) -> 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(
|
2023-02-24 20:39:52 +00:00
|
|
|
Box::new(if call.redirect_stdout {
|
|
|
|
vec![Ok(val)].into_iter()
|
|
|
|
} else {
|
2022-10-03 16:40:16 +00:00
|
|
|
vec![Ok(format!("{}\n", nu_pretty_hex::pretty_hex(&val))
|
|
|
|
.as_bytes()
|
|
|
|
.to_vec())]
|
2023-02-24 20:39:52 +00:00
|
|
|
.into_iter()
|
|
|
|
}),
|
2022-10-03 16:40:16 +00:00
|
|
|
ctrlc,
|
|
|
|
call.head,
|
2023-01-11 01:57:48 +00:00
|
|
|
None,
|
2022-10-03 16:40:16 +00:00
|
|
|
)),
|
|
|
|
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
|
|
|
}),
|
color_config now accepts closures as color values (#7141)
# Description
Closes #6909. You can now add closures to your `color_config` themes.
Whenever a value would be printed with `table`, the closure is run with
the value piped-in. The closure must return either a {fg,bg,attr} record
or a color name (`'light_red'` etc.). This returned style is used to
colour the value.
This is entirely backwards-compatible with existing config.nu files.
Example code excerpt:
```
let my_theme = {
header: green_bold
bool: { if $in { 'light_cyan' } else { 'light_red' } }
int: purple_bold
filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } }
duration: purple_bold
date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } }
range: yellow_bold
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }
nothing: white
```
Example output with this in effect:
![2022-11-16 12 47 23 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952558-482de05d-69c7-4bf2-91fc-d0964bf71264.png)
![2022-11-16 12 39 41 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952580-2384bb86-b680-40fe-8192-71bae396c738.png)
![2022-11-15 09 21 54 PM - run_external
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952601-343fc15d-e4a8-4a92-ad89-9a7d17d42748.png)
Slightly important notes:
* Some color_config names, namely "separator", "empty" and "hints", pipe
in `null` instead of a value.
* Currently, doing anything non-trivial inside a closure has an
understandably big perf hit. I currently do not actually recommend
something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' }
}` for serious work, mainly because of the abundance of string-type data
in the world. Nevertheless, lesser-used types like "date" and "duration"
work well with this.
* I had to do some reorganisation in order to make it possible to call
`eval_block()` that late in table rendering. I invented a new struct
called "StyleComputer" which holds the engine_state and stack of the
initial `table` command (implicit or explicit).
* StyleComputer has a `compute()` method which takes a color_config name
and a nu value, and always returns the correct Style, so you don't have
to worry about A) the color_config value was set at all, B) whether it
was set to a closure or not, or C) which default style to use in those
cases.
* Currently, errors encountered during execution of the closures are
thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors
result in a huge perf hit when they are encountered. I think what should
be done is to assume something terrible happened to the user's config
and invalidate the StyleComputer for that `table` run, thus causing
subsequent output to just be Style::default().)
* More thorough tests are forthcoming - ran into some difficulty using
`nu!` to take an alternative config, and for some reason `let-env config
=` statements don't seem to work inside `nu!` pipelines(???)
* The default config.nu has not been updated to make use of this yet. Do
tell if you think I should incorporate that into this.
# User-Facing Changes
See above.
# 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-12-17 13:07:56 +00:00
|
|
|
// None of these two receive a StyleComputer because handle_row_stream() can produce it by itself using engine_state and stack.
|
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,
|
|
|
|
),
|
2023-03-25 17:12:57 +00:00
|
|
|
PipelineData::Value(Value::Record { cols, vals, span }, ..) => {
|
|
|
|
let term_width = get_width_param(term_width);
|
|
|
|
|
|
|
|
handle_record(
|
|
|
|
cols,
|
|
|
|
vals,
|
|
|
|
span,
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
call,
|
|
|
|
table_view,
|
|
|
|
term_width,
|
|
|
|
ctrlc,
|
|
|
|
config,
|
|
|
|
)
|
|
|
|
}
|
LazyRecord (#7619)
This is an attempt to implement a new `Value::LazyRecord` variant for
performance reasons.
`LazyRecord` is like a regular `Record`, but it's possible to access
individual columns without evaluating other columns. I've implemented
`LazyRecord` for the special `$nu` variable; accessing `$nu` is
relatively slow because of all the information in `scope`, and [`$nu`
accounts for about 2/3 of Nu's startup time on
Linux](https://github.com/nushell/nushell/issues/6677#issuecomment-1364618122).
### Benchmarks
I ran some benchmarks on my desktop (Linux, 12900K) and the results are
very pleasing.
Nu's time to start up and run a command (`cargo build --release;
hyperfine 'target/release/nu -c "echo \"Hello, world!\""' --shell=none
--warmup 10`) goes from **8.8ms to 3.2ms, about 2.8x faster**.
Tests are also much faster! Running `cargo nextest` (with our very slow
`proptest` tests disabled) goes from **7.2s to 4.4s (1.6x faster)**,
because most tests involve launching a new instance of Nu.
### Design (updated)
I've added a new `LazyRecord` trait and added a `Value` variant wrapping
those trait objects, much like `CustomValue`. `LazyRecord`
implementations must implement these 2 functions:
```rust
// All column names
fn column_names(&self) -> Vec<&'static str>;
// Get 1 specific column value
fn get_column_value(&self, column: &str) -> Result<Value, ShellError>;
```
### Serializability
`Value` variants must implement `Serializable` and `Deserializable`, which poses some problems because I want to use unserializable things like `EngineState` in `LazyRecord`s. To work around this, I basically lie to the type system:
1. Add `#[typetag::serde(tag = "type")]` to `LazyRecord` to make it serializable
2. Any unserializable fields in `LazyRecord` implementations get marked with `#[serde(skip)]`
3. At the point where a `LazyRecord` normally would get serialized and sent to a plugin, I instead collect it into a regular `Value::Record` (which can be serialized)
2023-01-19 03:27:26 +00:00
|
|
|
PipelineData::Value(Value::LazyRecord { val, .. }, ..) => {
|
|
|
|
let collected = val.collect()?.into_pipeline_data();
|
|
|
|
handle_table_command(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
call,
|
|
|
|
collected,
|
|
|
|
row_offset,
|
|
|
|
table_view,
|
|
|
|
term_width,
|
|
|
|
)
|
|
|
|
}
|
2022-10-03 16:40:16 +00:00
|
|
|
PipelineData::Value(Value::Error { error }, ..) => {
|
2023-01-02 22:45:43 +00:00
|
|
|
// Propagate this error outward, so that it goes to stderr
|
|
|
|
// instead of stdout.
|
2023-03-12 08:57:27 +00:00
|
|
|
Err(*error)
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
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,
|
|
|
|
),
|
2023-01-09 05:53:52 +00:00
|
|
|
x => Ok(x),
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn supported_table_modes() -> Vec<Value> {
|
|
|
|
vec![
|
2022-12-24 09:25:38 +00:00
|
|
|
Value::test_string("basic"),
|
|
|
|
Value::test_string("compact"),
|
|
|
|
Value::test_string("compact_double"),
|
|
|
|
Value::test_string("default"),
|
|
|
|
Value::test_string("heavy"),
|
|
|
|
Value::test_string("light"),
|
|
|
|
Value::test_string("none"),
|
|
|
|
Value::test_string("reinforced"),
|
|
|
|
Value::test_string("rounded"),
|
|
|
|
Value::test_string("thin"),
|
|
|
|
Value::test_string("with_love"),
|
2022-10-03 16:40:16 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2023-02-22 16:18:33 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn handle_record(
|
|
|
|
cols: Vec<String>,
|
|
|
|
vals: Vec<Value>,
|
|
|
|
span: Span,
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
call: &Call,
|
|
|
|
table_view: TableView,
|
|
|
|
term_width: usize,
|
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
config: &Config,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
|
|
|
// Create a StyleComputer to compute styles for each value in the table.
|
|
|
|
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
2023-04-26 18:56:10 +00:00
|
|
|
let ctrlc1 = ctrlc.clone();
|
2023-02-22 16:18:33 +00:00
|
|
|
|
|
|
|
let result = if cols.is_empty() {
|
|
|
|
create_empty_placeholder("record", term_width, engine_state, stack)
|
|
|
|
} else {
|
2023-04-26 18:56:10 +00:00
|
|
|
let opts = BuildConfig::new(ctrlc, config, style_computer, span, term_width);
|
|
|
|
let result = build_table_kv(cols, vals, table_view, opts)?;
|
|
|
|
match result {
|
|
|
|
Some(output) => maybe_strip_color(output, config),
|
|
|
|
None => report_unsuccessful_output(ctrlc1, term_width),
|
|
|
|
}
|
2023-02-22 16:18:33 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let val = Value::String {
|
|
|
|
val: result,
|
|
|
|
span: call.head,
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(val.into_pipeline_data())
|
|
|
|
}
|
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
fn report_unsuccessful_output(ctrlc1: Option<Arc<AtomicBool>>, term_width: usize) -> String {
|
|
|
|
if nu_utils::ctrl_c::was_pressed(&ctrlc1) {
|
|
|
|
"".into()
|
|
|
|
} else {
|
|
|
|
// assume this failed because the table was too wide
|
|
|
|
// TODO: more robust error classification
|
|
|
|
format!("Couldn't fit table into {term_width} columns!")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_table_kv(
|
|
|
|
cols: Vec<String>,
|
|
|
|
vals: Vec<Value>,
|
|
|
|
table_view: TableView,
|
|
|
|
opts: BuildConfig<'_>,
|
|
|
|
) -> StringResult {
|
|
|
|
match table_view {
|
|
|
|
TableView::General => JustTable::kv_table(&cols, &vals, opts),
|
|
|
|
TableView::Expanded {
|
|
|
|
limit,
|
|
|
|
flatten,
|
|
|
|
flatten_separator,
|
|
|
|
} => {
|
|
|
|
let sep = flatten_separator.unwrap_or_else(|| String::from(' '));
|
|
|
|
ExpandedTable::new(limit, flatten, sep).build_map(&cols, &vals, opts)
|
|
|
|
}
|
|
|
|
TableView::Collapsed => {
|
|
|
|
let span = opts.span();
|
|
|
|
let value = Value::Record { cols, vals, span };
|
|
|
|
CollapsedTable::build(value, opts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_table_batch(
|
|
|
|
vals: Vec<Value>,
|
|
|
|
table_view: TableView,
|
|
|
|
row_offset: usize,
|
|
|
|
opts: BuildConfig<'_>,
|
|
|
|
) -> StringResult {
|
|
|
|
match table_view {
|
|
|
|
TableView::General => JustTable::table(&vals, row_offset, opts),
|
|
|
|
TableView::Expanded {
|
|
|
|
limit,
|
|
|
|
flatten,
|
|
|
|
flatten_separator,
|
|
|
|
} => {
|
|
|
|
let sep = flatten_separator.unwrap_or_else(|| String::from(' '));
|
|
|
|
ExpandedTable::new(limit, flatten, sep).build_list(&vals, opts)
|
|
|
|
}
|
|
|
|
TableView::Collapsed => {
|
|
|
|
let span = opts.span();
|
|
|
|
let value = Value::List { vals, span };
|
|
|
|
CollapsedTable::build(value, opts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-25 09:27:50 +00:00
|
|
|
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>>,
|
2023-02-11 21:35:48 +00:00
|
|
|
metadata: Option<Box<PipelineMetadata>>,
|
2023-02-05 21:17:46 +00:00
|
|
|
) -> Result<PipelineData, ShellError> {
|
2023-02-11 21:35:48 +00:00
|
|
|
let stream = match metadata.as_deref() {
|
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-12-23 18:49:19 +00:00
|
|
|
// Next, `to html -l` sources:
|
2022-11-15 17:12:56 +00:00
|
|
|
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(
|
2023-02-22 16:18:33 +00:00
|
|
|
Box::new(PagingTableCreator::new(
|
2022-02-25 09:27:50 +00:00
|
|
|
head,
|
|
|
|
stream,
|
2023-02-22 16:18:33 +00:00
|
|
|
// These are passed in as a way to have PagingTable create StyleComputers
|
|
|
|
// for the values it outputs. Because engine_state is passed in, config doesn't need to.
|
|
|
|
engine_state.clone(),
|
|
|
|
stack.clone(),
|
|
|
|
ctrlc.clone(),
|
|
|
|
row_offset,
|
2022-05-26 11:53:05 +00:00
|
|
|
width_param,
|
2023-02-22 16:18:33 +00:00
|
|
|
table_view,
|
|
|
|
)),
|
2022-02-25 09:27:50 +00:00
|
|
|
ctrlc,
|
|
|
|
head,
|
2023-01-11 01:57:48 +00:00
|
|
|
None,
|
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-12-03 06:15:23 +00:00
|
|
|
struct PagingTableCreator {
|
|
|
|
head: Span,
|
2022-01-28 18:32:33 +00:00
|
|
|
stream: ListStream,
|
color_config now accepts closures as color values (#7141)
# Description
Closes #6909. You can now add closures to your `color_config` themes.
Whenever a value would be printed with `table`, the closure is run with
the value piped-in. The closure must return either a {fg,bg,attr} record
or a color name (`'light_red'` etc.). This returned style is used to
colour the value.
This is entirely backwards-compatible with existing config.nu files.
Example code excerpt:
```
let my_theme = {
header: green_bold
bool: { if $in { 'light_cyan' } else { 'light_red' } }
int: purple_bold
filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } }
duration: purple_bold
date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } }
range: yellow_bold
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }
nothing: white
```
Example output with this in effect:
![2022-11-16 12 47 23 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952558-482de05d-69c7-4bf2-91fc-d0964bf71264.png)
![2022-11-16 12 39 41 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952580-2384bb86-b680-40fe-8192-71bae396c738.png)
![2022-11-15 09 21 54 PM - run_external
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952601-343fc15d-e4a8-4a92-ad89-9a7d17d42748.png)
Slightly important notes:
* Some color_config names, namely "separator", "empty" and "hints", pipe
in `null` instead of a value.
* Currently, doing anything non-trivial inside a closure has an
understandably big perf hit. I currently do not actually recommend
something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' }
}` for serious work, mainly because of the abundance of string-type data
in the world. Nevertheless, lesser-used types like "date" and "duration"
work well with this.
* I had to do some reorganisation in order to make it possible to call
`eval_block()` that late in table rendering. I invented a new struct
called "StyleComputer" which holds the engine_state and stack of the
initial `table` command (implicit or explicit).
* StyleComputer has a `compute()` method which takes a color_config name
and a nu value, and always returns the correct Style, so you don't have
to worry about A) the color_config value was set at all, B) whether it
was set to a closure or not, or C) which default style to use in those
cases.
* Currently, errors encountered during execution of the closures are
thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors
result in a huge perf hit when they are encountered. I think what should
be done is to assume something terrible happened to the user's config
and invalidate the StyleComputer for that `table` run, thus causing
subsequent output to just be Style::default().)
* More thorough tests are forthcoming - ran into some difficulty using
`nu!` to take an alternative config, and for some reason `let-env config
=` statements don't seem to work inside `nu!` pipelines(???)
* The default config.nu has not been updated to make use of this yet. Do
tell if you think I should incorporate that into this.
# User-Facing Changes
See above.
# 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-12-17 13:07:56 +00:00
|
|
|
engine_state: EngineState,
|
|
|
|
stack: Stack,
|
2021-12-03 06:15:23 +00:00
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
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,
|
2023-02-22 16:18:33 +00:00
|
|
|
elements_displayed: usize,
|
|
|
|
reached_end: bool,
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PagingTableCreator {
|
2023-02-22 16:18:33 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn new(
|
|
|
|
head: Span,
|
|
|
|
stream: ListStream,
|
|
|
|
engine_state: EngineState,
|
|
|
|
stack: Stack,
|
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
row_offset: usize,
|
|
|
|
width_param: Option<i64>,
|
|
|
|
view: TableView,
|
|
|
|
) -> Self {
|
|
|
|
PagingTableCreator {
|
|
|
|
head,
|
|
|
|
stream,
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
ctrlc,
|
|
|
|
row_offset,
|
|
|
|
width_param,
|
|
|
|
view,
|
|
|
|
elements_displayed: 0,
|
|
|
|
reached_end: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
fn build_extended(
|
color_config now accepts closures as color values (#7141)
# Description
Closes #6909. You can now add closures to your `color_config` themes.
Whenever a value would be printed with `table`, the closure is run with
the value piped-in. The closure must return either a {fg,bg,attr} record
or a color name (`'light_red'` etc.). This returned style is used to
colour the value.
This is entirely backwards-compatible with existing config.nu files.
Example code excerpt:
```
let my_theme = {
header: green_bold
bool: { if $in { 'light_cyan' } else { 'light_red' } }
int: purple_bold
filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } }
duration: purple_bold
date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } }
range: yellow_bold
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }
nothing: white
```
Example output with this in effect:
![2022-11-16 12 47 23 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952558-482de05d-69c7-4bf2-91fc-d0964bf71264.png)
![2022-11-16 12 39 41 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952580-2384bb86-b680-40fe-8192-71bae396c738.png)
![2022-11-15 09 21 54 PM - run_external
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952601-343fc15d-e4a8-4a92-ad89-9a7d17d42748.png)
Slightly important notes:
* Some color_config names, namely "separator", "empty" and "hints", pipe
in `null` instead of a value.
* Currently, doing anything non-trivial inside a closure has an
understandably big perf hit. I currently do not actually recommend
something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' }
}` for serious work, mainly because of the abundance of string-type data
in the world. Nevertheless, lesser-used types like "date" and "duration"
work well with this.
* I had to do some reorganisation in order to make it possible to call
`eval_block()` that late in table rendering. I invented a new struct
called "StyleComputer" which holds the engine_state and stack of the
initial `table` command (implicit or explicit).
* StyleComputer has a `compute()` method which takes a color_config name
and a nu value, and always returns the correct Style, so you don't have
to worry about A) the color_config value was set at all, B) whether it
was set to a closure or not, or C) which default style to use in those
cases.
* Currently, errors encountered during execution of the closures are
thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors
result in a huge perf hit when they are encountered. I think what should
be done is to assume something terrible happened to the user's config
and invalidate the StyleComputer for that `table` run, thus causing
subsequent output to just be Style::default().)
* More thorough tests are forthcoming - ran into some difficulty using
`nu!` to take an alternative config, and for some reason `let-env config
=` statements don't seem to work inside `nu!` pipelines(???)
* The default config.nu has not been updated to make use of this yet. Do
tell if you think I should incorporate that into this.
# User-Facing Changes
See above.
# 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-12-17 13:07:56 +00:00
|
|
|
&mut self,
|
2023-04-26 18:56:10 +00:00
|
|
|
batch: Vec<Value>,
|
2022-10-03 16:40:16 +00:00
|
|
|
limit: Option<usize>,
|
|
|
|
flatten: bool,
|
|
|
|
flatten_separator: Option<String>,
|
2023-04-26 18:56:10 +00:00
|
|
|
) -> StringResult {
|
2022-10-03 16:40:16 +00:00
|
|
|
if batch.is_empty() {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
color_config now accepts closures as color values (#7141)
# Description
Closes #6909. You can now add closures to your `color_config` themes.
Whenever a value would be printed with `table`, the closure is run with
the value piped-in. The closure must return either a {fg,bg,attr} record
or a color name (`'light_red'` etc.). This returned style is used to
colour the value.
This is entirely backwards-compatible with existing config.nu files.
Example code excerpt:
```
let my_theme = {
header: green_bold
bool: { if $in { 'light_cyan' } else { 'light_red' } }
int: purple_bold
filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } }
duration: purple_bold
date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } }
range: yellow_bold
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }
nothing: white
```
Example output with this in effect:
![2022-11-16 12 47 23 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952558-482de05d-69c7-4bf2-91fc-d0964bf71264.png)
![2022-11-16 12 39 41 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952580-2384bb86-b680-40fe-8192-71bae396c738.png)
![2022-11-15 09 21 54 PM - run_external
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952601-343fc15d-e4a8-4a92-ad89-9a7d17d42748.png)
Slightly important notes:
* Some color_config names, namely "separator", "empty" and "hints", pipe
in `null` instead of a value.
* Currently, doing anything non-trivial inside a closure has an
understandably big perf hit. I currently do not actually recommend
something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' }
}` for serious work, mainly because of the abundance of string-type data
in the world. Nevertheless, lesser-used types like "date" and "duration"
work well with this.
* I had to do some reorganisation in order to make it possible to call
`eval_block()` that late in table rendering. I invented a new struct
called "StyleComputer" which holds the engine_state and stack of the
initial `table` command (implicit or explicit).
* StyleComputer has a `compute()` method which takes a color_config name
and a nu value, and always returns the correct Style, so you don't have
to worry about A) the color_config value was set at all, B) whether it
was set to a closure or not, or C) which default style to use in those
cases.
* Currently, errors encountered during execution of the closures are
thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors
result in a huge perf hit when they are encountered. I think what should
be done is to assume something terrible happened to the user's config
and invalidate the StyleComputer for that `table` run, thus causing
subsequent output to just be Style::default().)
* More thorough tests are forthcoming - ran into some difficulty using
`nu!` to take an alternative config, and for some reason `let-env config
=` statements don't seem to work inside `nu!` pipelines(???)
* The default config.nu has not been updated to make use of this yet. Do
tell if you think I should incorporate that into this.
# User-Facing Changes
See above.
# 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-12-17 13:07:56 +00:00
|
|
|
let config = self.engine_state.get_config();
|
|
|
|
let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack);
|
2022-10-03 16:40:16 +00:00
|
|
|
let term_width = get_width_param(self.width_param);
|
2022-12-15 14:47:04 +00:00
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
let ctrlc = self.ctrlc.clone();
|
|
|
|
let span = self.head;
|
|
|
|
let opts = BuildConfig::new(ctrlc, config, &style_computer, span, term_width);
|
|
|
|
let view = TableView::Expanded {
|
2022-10-03 16:40:16 +00:00
|
|
|
limit,
|
|
|
|
flatten,
|
2023-04-26 18:56:10 +00:00
|
|
|
flatten_separator,
|
2022-11-16 14:03:56 +00:00
|
|
|
};
|
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
build_table_batch(batch, view, 0, opts)
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
fn build_collapsed(&mut self, batch: Vec<Value>) -> StringResult {
|
2022-10-03 16:40:16 +00:00
|
|
|
if batch.is_empty() {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
color_config now accepts closures as color values (#7141)
# Description
Closes #6909. You can now add closures to your `color_config` themes.
Whenever a value would be printed with `table`, the closure is run with
the value piped-in. The closure must return either a {fg,bg,attr} record
or a color name (`'light_red'` etc.). This returned style is used to
colour the value.
This is entirely backwards-compatible with existing config.nu files.
Example code excerpt:
```
let my_theme = {
header: green_bold
bool: { if $in { 'light_cyan' } else { 'light_red' } }
int: purple_bold
filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } }
duration: purple_bold
date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } }
range: yellow_bold
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }
nothing: white
```
Example output with this in effect:
![2022-11-16 12 47 23 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952558-482de05d-69c7-4bf2-91fc-d0964bf71264.png)
![2022-11-16 12 39 41 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952580-2384bb86-b680-40fe-8192-71bae396c738.png)
![2022-11-15 09 21 54 PM - run_external
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952601-343fc15d-e4a8-4a92-ad89-9a7d17d42748.png)
Slightly important notes:
* Some color_config names, namely "separator", "empty" and "hints", pipe
in `null` instead of a value.
* Currently, doing anything non-trivial inside a closure has an
understandably big perf hit. I currently do not actually recommend
something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' }
}` for serious work, mainly because of the abundance of string-type data
in the world. Nevertheless, lesser-used types like "date" and "duration"
work well with this.
* I had to do some reorganisation in order to make it possible to call
`eval_block()` that late in table rendering. I invented a new struct
called "StyleComputer" which holds the engine_state and stack of the
initial `table` command (implicit or explicit).
* StyleComputer has a `compute()` method which takes a color_config name
and a nu value, and always returns the correct Style, so you don't have
to worry about A) the color_config value was set at all, B) whether it
was set to a closure or not, or C) which default style to use in those
cases.
* Currently, errors encountered during execution of the closures are
thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors
result in a huge perf hit when they are encountered. I think what should
be done is to assume something terrible happened to the user's config
and invalidate the StyleComputer for that `table` run, thus causing
subsequent output to just be Style::default().)
* More thorough tests are forthcoming - ran into some difficulty using
`nu!` to take an alternative config, and for some reason `let-env config
=` statements don't seem to work inside `nu!` pipelines(???)
* The default config.nu has not been updated to make use of this yet. Do
tell if you think I should incorporate that into this.
# User-Facing Changes
See above.
# 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-12-17 13:07:56 +00:00
|
|
|
let config = self.engine_state.get_config();
|
|
|
|
let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack);
|
2022-10-03 16:40:16 +00:00
|
|
|
let term_width = get_width_param(self.width_param);
|
2023-04-26 18:56:10 +00:00
|
|
|
let ctrlc = self.ctrlc.clone();
|
|
|
|
let span = self.head;
|
|
|
|
let opts = BuildConfig::new(ctrlc, config, &style_computer, span, term_width);
|
2022-10-03 16:40:16 +00:00
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
build_table_batch(batch, TableView::Collapsed, 0, opts)
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
fn build_general(&mut self, batch: Vec<Value>) -> StringResult {
|
2022-10-03 16:40:16 +00:00
|
|
|
let term_width = get_width_param(self.width_param);
|
color_config now accepts closures as color values (#7141)
# Description
Closes #6909. You can now add closures to your `color_config` themes.
Whenever a value would be printed with `table`, the closure is run with
the value piped-in. The closure must return either a {fg,bg,attr} record
or a color name (`'light_red'` etc.). This returned style is used to
colour the value.
This is entirely backwards-compatible with existing config.nu files.
Example code excerpt:
```
let my_theme = {
header: green_bold
bool: { if $in { 'light_cyan' } else { 'light_red' } }
int: purple_bold
filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } }
duration: purple_bold
date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } }
range: yellow_bold
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }
nothing: white
```
Example output with this in effect:
![2022-11-16 12 47 23 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952558-482de05d-69c7-4bf2-91fc-d0964bf71264.png)
![2022-11-16 12 39 41 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952580-2384bb86-b680-40fe-8192-71bae396c738.png)
![2022-11-15 09 21 54 PM - run_external
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952601-343fc15d-e4a8-4a92-ad89-9a7d17d42748.png)
Slightly important notes:
* Some color_config names, namely "separator", "empty" and "hints", pipe
in `null` instead of a value.
* Currently, doing anything non-trivial inside a closure has an
understandably big perf hit. I currently do not actually recommend
something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' }
}` for serious work, mainly because of the abundance of string-type data
in the world. Nevertheless, lesser-used types like "date" and "duration"
work well with this.
* I had to do some reorganisation in order to make it possible to call
`eval_block()` that late in table rendering. I invented a new struct
called "StyleComputer" which holds the engine_state and stack of the
initial `table` command (implicit or explicit).
* StyleComputer has a `compute()` method which takes a color_config name
and a nu value, and always returns the correct Style, so you don't have
to worry about A) the color_config value was set at all, B) whether it
was set to a closure or not, or C) which default style to use in those
cases.
* Currently, errors encountered during execution of the closures are
thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors
result in a huge perf hit when they are encountered. I think what should
be done is to assume something terrible happened to the user's config
and invalidate the StyleComputer for that `table` run, thus causing
subsequent output to just be Style::default().)
* More thorough tests are forthcoming - ran into some difficulty using
`nu!` to take an alternative config, and for some reason `let-env config
=` statements don't seem to work inside `nu!` pipelines(???)
* The default config.nu has not been updated to make use of this yet. Do
tell if you think I should incorporate that into this.
# User-Facing Changes
See above.
# 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-12-17 13:07:56 +00:00
|
|
|
let config = &self.engine_state.get_config();
|
|
|
|
let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack);
|
2023-04-26 18:56:10 +00:00
|
|
|
let ctrlc = self.ctrlc.clone();
|
|
|
|
let span = self.head;
|
|
|
|
let row_offset = self.row_offset;
|
|
|
|
let opts = BuildConfig::new(ctrlc, config, &style_computer, span, term_width);
|
2022-12-15 14:47:04 +00:00
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
build_table_batch(batch, TableView::General, row_offset, opts)
|
2022-10-03 16:40:16 +00:00
|
|
|
}
|
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;
|
2023-02-22 16:18:33 +00:00
|
|
|
let mut reached_end = true;
|
2021-12-03 06:15:23 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2022-12-21 22:28:27 +00:00
|
|
|
// If we've been buffering over a second, go ahead and send out what we have so far
|
|
|
|
if (Instant::now() - start_time).as_secs() >= 1 {
|
2023-02-22 16:18:33 +00:00
|
|
|
reached_end = false;
|
2022-12-21 22:28:27 +00:00
|
|
|
break;
|
2021-12-03 06:15:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if idx == STREAM_PAGE_SIZE {
|
2023-02-22 16:18:33 +00:00
|
|
|
reached_end = false;
|
2021-12-03 06:15:23 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-12-15 17:39:24 +00:00
|
|
|
if nu_utils::ctrl_c::was_pressed(&self.ctrlc) {
|
|
|
|
break;
|
2021-12-03 06:15:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-22 16:18:33 +00:00
|
|
|
// Count how much elements were displayed and if end of stream was reached
|
|
|
|
self.elements_displayed += idx;
|
|
|
|
self.reached_end = self.reached_end || reached_end;
|
|
|
|
|
2022-12-17 22:16:32 +00:00
|
|
|
if batch.is_empty() {
|
2023-02-22 16:18:33 +00:00
|
|
|
// If this iterator has not displayed a single entry and reached its end (no more elements
|
|
|
|
// or interrupted by ctrl+c) display as "empty list"
|
|
|
|
return if self.elements_displayed == 0 && self.reached_end {
|
|
|
|
// Increase elements_displayed by one so on next iteration next branch of this
|
|
|
|
// if else triggers and terminates stream
|
|
|
|
self.elements_displayed = 1;
|
|
|
|
let term_width = get_width_param(self.width_param);
|
|
|
|
let result =
|
|
|
|
create_empty_placeholder("list", term_width, &self.engine_state, &self.stack);
|
|
|
|
Some(Ok(result.into_bytes()))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2022-12-17 22:16:32 +00:00
|
|
|
}
|
|
|
|
|
2022-10-03 16:40:16 +00:00
|
|
|
let table = match &self.view {
|
2023-04-26 18:56:10 +00:00
|
|
|
TableView::General => self.build_general(batch),
|
2022-10-03 16:40:16 +00:00
|
|
|
TableView::Collapsed => self.build_collapsed(batch),
|
|
|
|
TableView::Expanded {
|
|
|
|
limit,
|
|
|
|
flatten,
|
|
|
|
flatten_separator,
|
2023-04-26 18:56:10 +00:00
|
|
|
} => self.build_extended(batch, *limit, *flatten, flatten_separator.clone()),
|
2022-10-03 16:40:16 +00:00
|
|
|
};
|
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)) => {
|
2023-04-26 18:56:10 +00:00
|
|
|
let table = maybe_strip_color(table, self.engine_state.get_config());
|
2022-12-15 14:47:04 +00:00
|
|
|
|
2022-12-14 03:45:37 +00:00
|
|
|
let mut bytes = table.as_bytes().to_vec();
|
2022-12-15 14:47:04 +00:00
|
|
|
bytes.push(b'\n'); // nu-table tables don't come with a newline on the end
|
|
|
|
|
2022-12-14 03:45:37 +00:00
|
|
|
Some(Ok(bytes))
|
|
|
|
}
|
2022-12-17 22:16:32 +00:00
|
|
|
Ok(None) => {
|
2022-12-24 00:38:38 +00:00
|
|
|
let msg = if nu_utils::ctrl_c::was_pressed(&self.ctrlc) {
|
|
|
|
"".into()
|
|
|
|
} else {
|
|
|
|
// assume this failed because the table was too wide
|
|
|
|
// TODO: more robust error classification
|
|
|
|
let term_width = get_width_param(self.width_param);
|
2023-01-30 01:37:54 +00:00
|
|
|
format!("Couldn't fit table into {term_width} columns!")
|
2022-12-24 00:38:38 +00:00
|
|
|
};
|
2022-12-17 22:16:32 +00:00
|
|
|
Some(Ok(msg.as_bytes().to_vec()))
|
|
|
|
}
|
2021-12-24 07:22:11 +00:00
|
|
|
Err(err) => Some(Err(err)),
|
2021-12-03 06:15:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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() {
|
2023-02-22 16:18:33 +00:00
|
|
|
"basic" => TableTheme::basic(),
|
|
|
|
"thin" => TableTheme::thin(),
|
|
|
|
"light" => TableTheme::light(),
|
|
|
|
"compact" => TableTheme::compact(),
|
|
|
|
"with_love" => TableTheme::with_love(),
|
|
|
|
"compact_double" => TableTheme::compact_double(),
|
|
|
|
"rounded" => TableTheme::rounded(),
|
|
|
|
"reinforced" => TableTheme::reinforced(),
|
|
|
|
"heavy" => TableTheme::heavy(),
|
|
|
|
"none" => TableTheme::none(),
|
|
|
|
_ => TableTheme::rounded(),
|
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;
|
|
|
|
|
2023-04-14 20:14:57 +00:00
|
|
|
let ansi_style = style.map(Style::to_nu_ansi_term_style).unwrap_or_default();
|
2022-09-14 10:55:41 +00:00
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
|
2023-04-14 20:14:57 +00:00
|
|
|
let val = ansi_style.paint(full_path_link).to_string();
|
2022-09-14 10:55:41 +00:00
|
|
|
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>,
|
|
|
|
},
|
|
|
|
}
|
2022-12-15 14:47:04 +00:00
|
|
|
|
2023-01-26 21:31:17 +00:00
|
|
|
#[allow(clippy::manual_filter)]
|
2023-04-26 18:56:10 +00:00
|
|
|
fn maybe_strip_color(output: String, config: &Config) -> String {
|
|
|
|
// the atty is for when people do ls from vim, there should be no coloring there
|
|
|
|
if !config.use_ansi_coloring || !atty::is(atty::Stream::Stdout) {
|
|
|
|
// Draw the table without ansi colors
|
|
|
|
nu_utils::strip_ansi_string_likely(output)
|
|
|
|
} else {
|
|
|
|
// Draw the table with ansi colors
|
|
|
|
output
|
2022-12-15 14:47:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
fn create_table_config(config: &Config, comp: &StyleComputer, out: &TableOutput) -> TableConfig {
|
2022-12-15 14:47:04 +00:00
|
|
|
let theme = load_theme_from_config(config);
|
2023-04-26 18:56:10 +00:00
|
|
|
let footer = with_footer(config, out.with_header, out.table.count_rows());
|
|
|
|
let line_style = lookup_separator_color(comp);
|
|
|
|
let trim = config.trim_strategy.clone();
|
2022-12-15 14:47:04 +00:00
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
TableConfig::new()
|
|
|
|
.theme(theme)
|
|
|
|
.with_footer(footer)
|
|
|
|
.with_header(out.with_header)
|
|
|
|
.with_index(out.with_index)
|
|
|
|
.line_style(line_style)
|
|
|
|
.trim(trim)
|
2022-12-15 14:47:04 +00:00
|
|
|
}
|
|
|
|
|
color_config now accepts closures as color values (#7141)
# Description
Closes #6909. You can now add closures to your `color_config` themes.
Whenever a value would be printed with `table`, the closure is run with
the value piped-in. The closure must return either a {fg,bg,attr} record
or a color name (`'light_red'` etc.). This returned style is used to
colour the value.
This is entirely backwards-compatible with existing config.nu files.
Example code excerpt:
```
let my_theme = {
header: green_bold
bool: { if $in { 'light_cyan' } else { 'light_red' } }
int: purple_bold
filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } }
duration: purple_bold
date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } }
range: yellow_bold
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }
nothing: white
```
Example output with this in effect:
![2022-11-16 12 47 23 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952558-482de05d-69c7-4bf2-91fc-d0964bf71264.png)
![2022-11-16 12 39 41 AM - style_computer
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952580-2384bb86-b680-40fe-8192-71bae396c738.png)
![2022-11-15 09 21 54 PM - run_external
rs_-_nushell_-_VSCodium](https://user-images.githubusercontent.com/83939/201952601-343fc15d-e4a8-4a92-ad89-9a7d17d42748.png)
Slightly important notes:
* Some color_config names, namely "separator", "empty" and "hints", pipe
in `null` instead of a value.
* Currently, doing anything non-trivial inside a closure has an
understandably big perf hit. I currently do not actually recommend
something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' }
}` for serious work, mainly because of the abundance of string-type data
in the world. Nevertheless, lesser-used types like "date" and "duration"
work well with this.
* I had to do some reorganisation in order to make it possible to call
`eval_block()` that late in table rendering. I invented a new struct
called "StyleComputer" which holds the engine_state and stack of the
initial `table` command (implicit or explicit).
* StyleComputer has a `compute()` method which takes a color_config name
and a nu value, and always returns the correct Style, so you don't have
to worry about A) the color_config value was set at all, B) whether it
was set to a closure or not, or C) which default style to use in those
cases.
* Currently, errors encountered during execution of the closures are
thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors
result in a huge perf hit when they are encountered. I think what should
be done is to assume something terrible happened to the user's config
and invalidate the StyleComputer for that `table` run, thus causing
subsequent output to just be Style::default().)
* More thorough tests are forthcoming - ran into some difficulty using
`nu!` to take an alternative config, and for some reason `let-env config
=` statements don't seem to work inside `nu!` pipelines(???)
* The default config.nu has not been updated to make use of this yet. Do
tell if you think I should incorporate that into this.
# User-Facing Changes
See above.
# 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-12-17 13:07:56 +00:00
|
|
|
fn lookup_separator_color(style_computer: &StyleComputer) -> nu_ansi_term::Style {
|
|
|
|
style_computer.compute("separator", &Value::nothing(Span::unknown()))
|
2022-12-15 14:47:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn with_footer(config: &Config, with_header: bool, count_records: usize) -> bool {
|
|
|
|
with_header && need_footer(config, count_records as u64)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn need_footer(config: &Config, count_records: u64) -> bool {
|
|
|
|
matches!(config.footer_mode, FooterMode::RowCount(limit) if count_records > limit)
|
|
|
|
|| matches!(config.footer_mode, FooterMode::Always)
|
|
|
|
}
|
2023-02-20 12:59:33 +00:00
|
|
|
|
2023-02-22 16:18:33 +00:00
|
|
|
fn create_empty_placeholder(
|
|
|
|
value_type_name: &str,
|
|
|
|
termwidth: usize,
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &Stack,
|
|
|
|
) -> String {
|
|
|
|
let config = engine_state.get_config();
|
|
|
|
if !config.table_show_empty {
|
2023-04-26 18:56:10 +00:00
|
|
|
return String::new();
|
2023-02-22 16:18:33 +00:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
let cell = Cell::new(format!("empty {}", value_type_name));
|
2023-02-22 16:18:33 +00:00
|
|
|
let data = vec![vec![cell]];
|
2023-04-26 18:56:10 +00:00
|
|
|
let mut table = NuTable::from(data);
|
|
|
|
table.set_cell_style((0, 0), TextStyle::default().dimmed());
|
|
|
|
let out = TableOutput::new(table, false, false);
|
2023-02-22 16:18:33 +00:00
|
|
|
|
|
|
|
let style_computer = &StyleComputer::from_config(engine_state, stack);
|
2023-04-26 18:56:10 +00:00
|
|
|
let config = create_table_config(config, style_computer, &out);
|
2023-02-22 16:18:33 +00:00
|
|
|
|
2023-04-26 18:56:10 +00:00
|
|
|
out.table
|
2023-02-22 16:18:33 +00:00
|
|
|
.draw(config, termwidth)
|
|
|
|
.expect("Could not create empty table placeholder")
|
|
|
|
}
|