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.
This commit is contained in:
Leon
2022-12-17 23:07:56 +10:00
committed by GitHub
parent e72cecf457
commit 774769a7ad
39 changed files with 1075 additions and 729 deletions

View File

@ -129,7 +129,7 @@ impl ConfigView {
match self.format {
ConfigFormat::Table => {
let value = map_into_value(config.config.clone());
try_build_table(None, config.nu_config, config.color_hm, value)
try_build_table(None, config.nu_config, config.style_computer, value)
}
ConfigFormat::Nu => nu_json::to_string(&config.config).unwrap_or_default(),
}

View File

@ -1,6 +1,6 @@
use std::{io::Result, vec};
use nu_color_config::get_color_config;
use nu_color_config::StyleComputer;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
@ -71,18 +71,18 @@ impl ViewCommand for ExpandCmd {
fn spawn(
&mut self,
engine_state: &EngineState,
_stack: &mut Stack,
stack: &mut Stack,
value: Option<Value>,
) -> Result<Self::View> {
let value = value
.map(|v| convert_value_to_string(v, engine_state))
.map(|v| convert_value_to_string(v, engine_state, stack))
.unwrap_or_default();
Ok(Preview::new(&value))
}
}
fn convert_value_to_string(value: Value, engine_state: &EngineState) -> String {
fn convert_value_to_string(value: Value, engine_state: &EngineState, stack: &mut Stack) -> String {
let (cols, vals) = collect_input(value.clone());
let has_no_head = cols.is_empty() || (cols.len() == 1 && cols[0].is_empty());
@ -93,8 +93,8 @@ fn convert_value_to_string(value: Value, engine_state: &EngineState) -> String {
} else {
let ctrlc = engine_state.ctrlc.clone();
let config = engine_state.get_config();
let color_hm = get_color_config(config);
let style_computer = StyleComputer::from_config(engine_state, stack);
nu_common::try_build_table(ctrlc, config, &color_hm, value)
nu_common::try_build_table(ctrlc, config, &style_computer, value)
}
}

View File

@ -0,0 +1,79 @@
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use std::io::Result;
use crate::{
nu_common::{self, collect_input},
views::Preview,
};
use super::{HelpManual, ViewCommand};
#[derive(Default, Clone)]
pub struct PreviewCmd;
impl PreviewCmd {
pub fn new() -> Self {
Self
}
}
impl PreviewCmd {
pub const NAME: &'static str = "preview";
}
impl ViewCommand for PreviewCmd {
type View = Preview;
fn name(&self) -> &'static str {
Self::NAME
}
fn usage(&self) -> &'static str {
""
}
fn help(&self) -> Option<HelpManual> {
Some(HelpManual {
name: "preview",
description:
"View the currently selected cell's data using the `table` Nushell command",
arguments: vec![],
examples: vec![],
})
}
fn parse(&mut self, _: &str) -> Result<()> {
Ok(())
}
fn spawn(
&mut self,
engine_state: &EngineState,
stack: &mut Stack,
value: Option<Value>,
) -> Result<Self::View> {
let value = match value {
Some(value) => {
let (cols, vals) = collect_input(value.clone());
let has_no_head = cols.is_empty() || (cols.len() == 1 && cols[0].is_empty());
let has_single_value = vals.len() == 1 && vals[0].len() == 1;
if !has_no_head && has_single_value {
let config = engine_state.get_config();
vals[0][0].into_abbreviated_string(config)
} else {
let ctrlc = engine_state.ctrlc.clone();
let config = engine_state.get_config();
nu_common::try_build_table(engine_state, stack, ctrlc, config, value)
}
}
None => String::new(),
};
Ok(Preview::new(&value))
}
}

View File

@ -3,20 +3,16 @@ mod string;
mod table;
mod value;
use std::{
collections::HashMap,
sync::{atomic::AtomicBool, Arc},
};
use std::sync::{atomic::AtomicBool, Arc};
use nu_color_config::TextStyle;
use nu_protocol::Value;
use nu_table::TextStyle;
pub use nu_ansi_term::{Color as NuColor, Style as NuStyle};
pub use nu_protocol::{Config as NuConfig, Span as NuSpan};
pub type NuText = (String, TextStyle);
pub type CtrlC = Option<Arc<AtomicBool>>;
pub type NuStyleTable = HashMap<String, NuStyle>;
pub use command::{is_ignored_command, run_command_with_value, run_nu_command};
pub use string::truncate_str;

View File

@ -1,33 +1,31 @@
use nu_color_config::{get_color_config, style_primitive};
use nu_color_config::{Alignment, StyleComputer, TextStyle};
use nu_engine::column::get_columns;
use nu_protocol::FooterMode;
use nu_protocol::{ast::PathMember, Config, ShellError, Span, TableIndexMode, Value};
use nu_table::{string_width, Alignment, Table as NuTable, TableConfig, TableTheme, TextStyle};
use nu_table::{string_width, Table as NuTable, TableConfig, TableTheme};
use std::sync::Arc;
use std::{
cmp::max,
collections::HashMap,
sync::atomic::{AtomicBool, Ordering},
};
const INDEX_COLUMN_NAME: &str = "index";
type NuText = (String, TextStyle);
type NuColorMap = HashMap<String, nu_ansi_term::Style>;
use crate::nu_common::{NuConfig, NuStyleTable};
use crate::nu_common::NuConfig;
pub fn try_build_table(
ctrlc: Option<Arc<AtomicBool>>,
config: &NuConfig,
color_hm: &NuStyleTable,
style_computer: &StyleComputer,
value: Value,
) -> String {
match value {
Value::List { vals, span } => try_build_list(vals, &ctrlc, config, span, color_hm),
Value::List { vals, span } => try_build_list(vals, &ctrlc, config, span, style_computer),
Value::Record { cols, vals, span } => {
try_build_map(cols, vals, span, ctrlc, config, color_hm)
try_build_map(cols, vals, span, style_computer, ctrlc, config)
}
val => value_to_styled_string(&val, config, color_hm).0,
val => value_to_styled_string(&val, config, style_computer).0,
}
}
@ -35,9 +33,9 @@ fn try_build_map(
cols: Vec<String>,
vals: Vec<Value>,
span: Span,
style_computer: &StyleComputer,
ctrlc: Option<Arc<AtomicBool>>,
config: &NuConfig,
color_hm: &HashMap<String, nu_ansi_term::Style>,
) -> String {
let result = build_expanded_table(
cols.clone(),
@ -45,6 +43,7 @@ fn try_build_map(
span,
ctrlc,
config,
style_computer,
usize::MAX,
None,
false,
@ -53,7 +52,7 @@ fn try_build_map(
match result {
Ok(Some(result)) => result,
Ok(None) | Err(_) => {
value_to_styled_string(&Value::Record { cols, vals, span }, config, color_hm).0
value_to_styled_string(&Value::Record { cols, vals, span }, config, style_computer).0
}
}
}
@ -63,7 +62,7 @@ fn try_build_list(
ctrlc: &Option<Arc<AtomicBool>>,
config: &NuConfig,
span: Span,
color_hm: &HashMap<String, nu_ansi_term::Style>,
style_computer: &StyleComputer,
) -> String {
let table = convert_to_table2(
0,
@ -71,7 +70,7 @@ fn try_build_list(
ctrlc.clone(),
config,
span,
color_hm,
style_computer,
None,
false,
"",
@ -81,7 +80,7 @@ fn try_build_list(
Ok(Some((table, with_header, with_index))) => {
let table_config = create_table_config(
config,
color_hm,
style_computer,
table.count_rows(),
with_header,
with_index,
@ -92,12 +91,14 @@ fn try_build_list(
match val {
Some(result) => result,
None => value_to_styled_string(&Value::List { vals, span }, config, color_hm).0,
None => {
value_to_styled_string(&Value::List { vals, span }, config, style_computer).0
}
}
}
Ok(None) | Err(_) => {
// it means that the list is empty
value_to_styled_string(&Value::List { vals, span }, config, color_hm).0
value_to_styled_string(&Value::List { vals, span }, config, style_computer).0
}
}
}
@ -109,12 +110,12 @@ fn build_expanded_table(
span: Span,
ctrlc: Option<Arc<AtomicBool>>,
config: &Config,
style_computer: &StyleComputer,
term_width: usize,
expand_limit: Option<usize>,
flatten: bool,
flatten_sep: &str,
) -> Result<Option<String>, ShellError> {
let color_hm = get_color_config(config);
let theme = load_theme_from_config(config);
// calculate the width of a key part + the rest of table so we know the rest of the table width available for value.
@ -123,7 +124,7 @@ fn build_expanded_table(
let key_table = NuTable::new(vec![vec![key]], (1, 2));
let key_width = key_table
.draw(
create_table_config(config, &color_hm, 1, false, false, false),
create_table_config(config, style_computer, 1, false, false, false),
usize::MAX,
)
.map(|table| string_width(&table))
@ -149,7 +150,7 @@ fn build_expanded_table(
let is_limited = matches!(expand_limit, Some(0));
let mut is_expanded = false;
let value = if is_limited {
value_to_styled_string(&value, config, &color_hm).0
value_to_styled_string(&value, config, style_computer).0
} else {
let deep = expand_limit.map(|i| i - 1);
@ -161,7 +162,7 @@ fn build_expanded_table(
ctrlc.clone(),
config,
span,
&color_hm,
style_computer,
deep,
flatten,
flatten_sep,
@ -178,7 +179,7 @@ fn build_expanded_table(
let table_config = create_table_config(
config,
&color_hm,
style_computer,
table.count_rows(),
with_header,
with_index,
@ -194,7 +195,7 @@ fn build_expanded_table(
None => {
// it means that the list is empty
let value = Value::List { vals, span };
value_to_styled_string(&value, config, &color_hm).0
value_to_styled_string(&value, config, style_computer).0
}
}
}
@ -205,6 +206,7 @@ fn build_expanded_table(
span,
ctrlc.clone(),
config,
style_computer,
remaining_width,
deep,
flatten,
@ -220,7 +222,7 @@ fn build_expanded_table(
let failed_value = value_to_styled_string(
&Value::Record { cols, vals, span },
config,
&color_hm,
style_computer,
);
nu_table::wrap_string(&failed_value.0, remaining_width)
@ -228,7 +230,7 @@ fn build_expanded_table(
}
}
val => {
let text = value_to_styled_string(&val, config, &color_hm).0;
let text = value_to_styled_string(&val, config, style_computer).0;
nu_table::wrap_string(&text, remaining_width)
}
}
@ -249,7 +251,7 @@ fn build_expanded_table(
data.push(row);
}
let table_config = create_table_config(config, &color_hm, data.len(), false, false, false);
let table_config = create_table_config(config, style_computer, data.len(), false, false, false);
let data_len = data.len();
let table = NuTable::new(data, (data_len, 2));
@ -287,7 +289,7 @@ fn convert_to_table2<'a>(
ctrlc: Option<Arc<AtomicBool>>,
config: &Config,
head: Span,
color_hm: &NuColorMap,
style_computer: &StyleComputer,
deep: Option<usize>,
flatten: bool,
flatten_sep: &str,
@ -336,7 +338,10 @@ fn convert_to_table2<'a>(
let mut column_width = 0;
if with_header {
data[0].push(NuTable::create_cell("#", header_style(color_hm)));
data[0].push(NuTable::create_cell(
"#",
header_style(style_computer, String::from("#")),
));
}
for (row, item) in input.clone().into_iter().enumerate() {
@ -355,7 +360,7 @@ fn convert_to_table2<'a>(
.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 value = make_index_string(text, style_computer);
let width = string_width(&value.0);
column_width = max(column_width, width);
@ -389,7 +394,7 @@ fn convert_to_table2<'a>(
item,
config,
&ctrlc,
color_hm,
style_computer,
deep,
flatten,
flatten_sep,
@ -431,7 +436,10 @@ fn convert_to_table2<'a>(
let mut column_width = string_width(&header);
data[0].push(NuTable::create_cell(&header, header_style(color_hm)));
data[0].push(NuTable::create_cell(
&header,
header_style(style_computer, header.clone()),
));
for (row, item) in input.clone().into_iter().enumerate() {
if let Some(ctrlc) = &ctrlc {
@ -450,7 +458,7 @@ fn convert_to_table2<'a>(
head,
config,
&ctrlc,
color_hm,
style_computer,
deep,
flatten,
flatten_sep,
@ -480,7 +488,7 @@ fn convert_to_table2<'a>(
}
}
let value = create_table2_entry_basic(item, &header, head, config, color_hm);
let value = create_table2_entry_basic(item, &header, head, config, style_computer);
let value = wrap_nu_text(value, available_width);
let value_width = string_width(&value.0);
@ -507,7 +515,7 @@ fn convert_to_table2<'a>(
}
}
let value = create_table2_entry_basic(item, &header, head, config, color_hm);
let value = create_table2_entry_basic(item, &header, head, config, style_computer);
let value = wrap_nu_text(value, OK_CELL_CONTENT_WIDTH);
let value = NuTable::create_cell(value.0, value.1);
@ -577,10 +585,11 @@ fn lookup_index_value(item: &Value, config: &Config) -> Option<String> {
.map(|value| value.into_string("", config))
}
fn header_style(color_hm: &NuColorMap) -> TextStyle {
fn header_style(style_computer: &StyleComputer, header: String) -> TextStyle {
let style = style_computer.compute("header", &Value::string(header.as_str(), Span::unknown()));
TextStyle {
alignment: Alignment::Center,
color_style: Some(color_hm["header"]),
color_style: Some(style),
}
}
@ -590,7 +599,7 @@ fn create_table2_entry_basic(
header: &str,
head: Span,
config: &Config,
color_hm: &NuColorMap,
style_computer: &StyleComputer,
) -> NuText {
match item {
Value::Record { .. } => {
@ -599,11 +608,11 @@ fn create_table2_entry_basic(
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),
Ok(val) => value_to_styled_string(&val, config, style_computer),
Err(_) => error_sign(style_computer),
}
}
_ => value_to_styled_string(item, config, color_hm),
_ => value_to_styled_string(item, config, style_computer),
}
}
@ -614,7 +623,7 @@ fn create_table2_entry(
head: Span,
config: &Config,
ctrlc: &Option<Arc<AtomicBool>>,
color_hm: &NuColorMap,
style_computer: &StyleComputer,
deep: Option<usize>,
flatten: bool,
flatten_sep: &str,
@ -631,20 +640,20 @@ fn create_table2_entry(
&val,
config,
ctrlc,
color_hm,
style_computer,
deep,
flatten,
flatten_sep,
width,
),
Err(_) => wrap_nu_text(error_sign(color_hm), width),
Err(_) => wrap_nu_text(error_sign(style_computer), width),
}
}
_ => convert_to_table2_entry(
item,
config,
ctrlc,
color_hm,
style_computer,
deep,
flatten,
flatten_sep,
@ -653,8 +662,8 @@ fn create_table2_entry(
}
}
fn error_sign(color_hm: &HashMap<String, nu_ansi_term::Style>) -> (String, TextStyle) {
make_styled_string(String::from(""), "empty", color_hm, 0)
fn error_sign(style_computer: &StyleComputer) -> (String, TextStyle) {
make_styled_string(style_computer, String::from(""), None, 0)
}
fn wrap_nu_text(mut text: NuText, width: usize) -> NuText {
@ -667,7 +676,9 @@ fn convert_to_table2_entry(
item: &Value,
config: &Config,
ctrlc: &Option<Arc<AtomicBool>>,
color_hm: &NuColorMap,
// This is passed in, even though it could be retrieved from config,
// to save reallocation (because it's presumably being used upstream).
style_computer: &StyleComputer,
deep: Option<usize>,
flatten: bool,
flatten_sep: &str,
@ -675,13 +686,13 @@ fn convert_to_table2_entry(
) -> NuText {
let is_limit_reached = matches!(deep, Some(0));
if is_limit_reached {
return wrap_nu_text(value_to_styled_string(item, config, color_hm), width);
return wrap_nu_text(value_to_styled_string(item, config, style_computer), width);
}
match &item {
Value::Record { span, cols, vals } => {
if cols.is_empty() && vals.is_empty() {
wrap_nu_text(value_to_styled_string(item, config, color_hm), width)
wrap_nu_text(value_to_styled_string(item, config, style_computer), width)
} else {
let table = convert_to_table2(
0,
@ -689,7 +700,7 @@ fn convert_to_table2_entry(
ctrlc.clone(),
config,
*span,
color_hm,
style_computer,
deep.map(|i| i - 1),
flatten,
flatten_sep,
@ -700,7 +711,7 @@ fn convert_to_table2_entry(
table.and_then(|(table, with_header, with_index)| {
let table_config = create_table_config(
config,
color_hm,
style_computer,
table.count_rows(),
with_header,
with_index,
@ -715,7 +726,7 @@ fn convert_to_table2_entry(
(table, TextStyle::default())
} else {
// error so back down to the default
wrap_nu_text(value_to_styled_string(item, config, color_hm), width)
wrap_nu_text(value_to_styled_string(item, config, style_computer), width)
}
}
}
@ -726,7 +737,7 @@ fn convert_to_table2_entry(
if flatten && is_simple_list {
wrap_nu_text(
convert_value_list_to_string(vals, config, color_hm, flatten_sep),
convert_value_list_to_string(vals, config, style_computer, flatten_sep),
width,
)
} else {
@ -736,7 +747,7 @@ fn convert_to_table2_entry(
ctrlc.clone(),
config,
*span,
color_hm,
style_computer,
deep.map(|i| i - 1),
flatten,
flatten_sep,
@ -747,7 +758,7 @@ fn convert_to_table2_entry(
table.and_then(|(table, with_header, with_index)| {
let table_config = create_table_config(
config,
color_hm,
style_computer,
table.count_rows(),
with_header,
with_index,
@ -763,23 +774,25 @@ fn convert_to_table2_entry(
} else {
// error so back down to the default
wrap_nu_text(value_to_styled_string(item, config, color_hm), width)
wrap_nu_text(value_to_styled_string(item, config, style_computer), width)
}
}
}
_ => wrap_nu_text(value_to_styled_string(item, config, color_hm), width), // unknown type.
_ => wrap_nu_text(value_to_styled_string(item, config, style_computer), width), // unknown type.
}
}
fn convert_value_list_to_string(
vals: &[Value],
config: &Config,
color_hm: &NuColorMap,
// This is passed in, even though it could be retrieved from config,
// to save reallocation (because it's presumably being used upstream).
style_computer: &StyleComputer,
flatten_sep: &str,
) -> NuText {
let mut buf = Vec::new();
for value in vals {
let (text, _) = value_to_styled_string(value, config, color_hm);
let (text, _) = value_to_styled_string(value, config, style_computer);
buf.push(text);
}
@ -787,39 +800,58 @@ fn convert_value_list_to_string(
(text, TextStyle::default())
}
fn value_to_styled_string(value: &Value, config: &Config, color_hm: &NuColorMap) -> NuText {
fn value_to_styled_string(
value: &Value,
config: &Config,
// This is passed in, even though it could be retrieved from config,
// to save reallocation (because it's presumably being used upstream).
style_computer: &StyleComputer,
) -> NuText {
let float_precision = config.float_precision as usize;
make_styled_string(
style_computer,
value.into_abbreviated_string(config),
&value.get_type().to_string(),
color_hm,
Some(value),
float_precision,
)
}
fn make_styled_string(
style_computer: &StyleComputer,
text: String,
text_type: &str,
color_hm: &NuColorMap,
value: Option<&Value>, // None represents table holes.
float_precision: usize,
) -> NuText {
if text_type == "float" {
// set dynamic precision from config
let precise_number = match convert_with_precision(&text, float_precision) {
Ok(num) => num,
Err(e) => e.to_string(),
};
(precise_number, style_primitive(text_type, color_hm))
} else {
(text, style_primitive(text_type, color_hm))
match value {
Some(value) => {
match value {
Value::Float { .. } => {
// set dynamic precision from config
let precise_number = match convert_with_precision(&text, float_precision) {
Ok(num) => num,
Err(e) => e.to_string(),
};
(precise_number, style_computer.style_primitive(value))
}
_ => (text, style_computer.style_primitive(value)),
}
}
None => {
// Though holes are not the same as null, the closure for "empty" is passed a null anyway.
(
text,
TextStyle::with_style(
Alignment::Center,
style_computer.compute("empty", &Value::nothing(Span::unknown())),
),
)
}
}
}
fn make_index_string(text: String, color_hm: &NuColorMap) -> NuText {
let style = TextStyle::new()
.alignment(Alignment::Right)
.style(color_hm["row_index"]);
(text, style)
fn make_index_string(text: String, style_computer: &StyleComputer) -> NuText {
let style = style_computer.compute("row_index", &Value::string(text.as_str(), Span::unknown()));
(text, TextStyle::with_style(Alignment::Right, style))
}
fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellError> {
@ -857,7 +889,7 @@ fn load_theme_from_config(config: &Config) -> TableTheme {
fn create_table_config(
config: &Config,
color_hm: &HashMap<String, nu_ansi_term::Style>,
style_computer: &StyleComputer,
count_records: usize,
with_header: bool,
with_index: bool,
@ -868,10 +900,7 @@ fn create_table_config(
let mut table_cfg = TableConfig::new(theme, with_header, with_index, append_footer);
let sep_color = lookup_separator_color(color_hm);
if let Some(color) = sep_color {
table_cfg = table_cfg.splitline_style(color);
}
table_cfg = table_cfg.splitline_style(lookup_separator_color(style_computer));
if expand {
table_cfg = table_cfg.expand();
@ -880,10 +909,8 @@ fn create_table_config(
table_cfg.trim(config.trim_strategy.clone())
}
fn lookup_separator_color(
color_hm: &HashMap<String, nu_ansi_term::Style>,
) -> Option<nu_ansi_term::Style> {
color_hm.get("separator").cloned()
fn lookup_separator_color(style_computer: &StyleComputer) -> nu_ansi_term::Style {
style_computer.compute("separator", &Value::nothing(Span::unknown()))
}
fn with_footer(config: &Config, with_header: bool, count_records: usize) -> bool {

View File

@ -18,7 +18,7 @@ use crossterm::{
LeaveAlternateScreen,
},
};
use nu_color_config::lookup_ansi_color_style;
use nu_color_config::{lookup_ansi_color_style, StyleComputer};
use nu_protocol::{
engine::{EngineState, Stack},
Value,
@ -26,7 +26,7 @@ use nu_protocol::{
use tui::{backend::CrosstermBackend, layout::Rect, widgets::Block};
use crate::{
nu_common::{CtrlC, NuColor, NuConfig, NuSpan, NuStyle, NuStyleTable},
nu_common::{CtrlC, NuColor, NuConfig, NuSpan, NuStyle},
registry::{Command, CommandRegistry},
util::map_into_value,
views::{util::nu_style_to_tui, ViewConfig},
@ -140,7 +140,7 @@ impl<'a> Pager<'a> {
if let Some(page) = &mut view {
page.view.setup(ViewConfig::new(
self.config.nu_config,
self.config.color_hm,
self.config.style_computer,
&self.config.config,
))
}
@ -160,7 +160,7 @@ pub enum Transition {
#[derive(Debug, Clone)]
pub struct PagerConfig<'a> {
pub nu_config: &'a NuConfig,
pub color_hm: &'a NuStyleTable,
pub style_computer: &'a StyleComputer<'a>,
pub config: ConfigMap,
pub style: StyleConfig,
pub peek_value: bool,
@ -170,10 +170,14 @@ pub struct PagerConfig<'a> {
}
impl<'a> PagerConfig<'a> {
pub fn new(nu_config: &'a NuConfig, color_hm: &'a NuStyleTable, config: ConfigMap) -> Self {
pub fn new(
nu_config: &'a NuConfig,
style_computer: &'a StyleComputer,
config: ConfigMap,
) -> Self {
Self {
nu_config,
color_hm,
style_computer,
config,
peek_value: false,
exit_esc: false,
@ -261,7 +265,7 @@ fn render_ui(
if let Some(page) = &mut view {
let cfg = ViewConfig::new(
pager.config.nu_config,
pager.config.color_hm,
pager.config.style_computer,
&pager.config.config,
);
@ -416,7 +420,7 @@ fn run_command(
if let Some(page) = view.as_mut() {
page.view.setup(ViewConfig::new(
pager.config.nu_config,
pager.config.color_hm,
pager.config.style_computer,
&pager.config.config,
));
}
@ -424,7 +428,7 @@ fn run_command(
for page in view_stack {
page.view.setup(ViewConfig::new(
pager.config.nu_config,
pager.config.color_hm,
pager.config.style_computer,
&pager.config.config,
));
}
@ -452,7 +456,7 @@ fn run_command(
new_view.setup(ViewConfig::new(
pager.config.nu_config,
pager.config.color_hm,
pager.config.style_computer,
&pager.config.config,
));

View File

@ -1,6 +1,6 @@
use crossterm::event::KeyEvent;
use nu_color_config::TextStyle;
use nu_protocol::engine::{EngineState, Stack};
use nu_table::TextStyle;
use tui::{layout::Rect, widgets::Paragraph};
use crate::{

View File

@ -7,16 +7,14 @@ mod record;
pub mod util;
use crossterm::event::KeyEvent;
use nu_color_config::StyleComputer;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use tui::layout::Rect;
use crate::{
nu_common::{NuConfig, NuStyleTable},
pager::ConfigMap,
};
use crate::{nu_common::NuConfig, pager::ConfigMap};
use super::{
nu_common::NuText,
@ -61,15 +59,19 @@ impl ElementInfo {
#[derive(Debug, Clone, Copy)]
pub struct ViewConfig<'a> {
pub nu_config: &'a NuConfig,
pub color_hm: &'a NuStyleTable,
pub style_computer: &'a StyleComputer<'a>,
pub config: &'a ConfigMap,
}
impl<'a> ViewConfig<'a> {
pub fn new(nu_config: &'a NuConfig, color_hm: &'a NuStyleTable, config: &'a ConfigMap) -> Self {
pub fn new(
nu_config: &'a NuConfig,
style_computer: &'a StyleComputer<'a>,
config: &'a ConfigMap,
) -> Self {
Self {
nu_config,
color_hm,
style_computer,
config,
}
}

View File

@ -1,11 +1,11 @@
use std::cmp::max;
use crossterm::event::{KeyCode, KeyEvent};
use nu_color_config::TextStyle;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use nu_table::TextStyle;
use tui::layout::Rect;
use crate::{

View File

@ -3,7 +3,7 @@ mod tablew;
use std::{borrow::Cow, collections::HashMap};
use crossterm::event::{KeyCode, KeyEvent};
use nu_color_config::get_color_map;
use nu_color_config::{get_color_map, StyleComputer};
use nu_protocol::{
engine::{EngineState, Stack},
Value,
@ -11,7 +11,7 @@ use nu_protocol::{
use tui::{layout::Rect, widgets::Block};
use crate::{
nu_common::{collect_input, NuConfig, NuSpan, NuStyle, NuStyleTable, NuText},
nu_common::{collect_input, NuConfig, NuSpan, NuStyle, NuText},
pager::{
report::{Report, Severity},
ConfigMap, Frame, Transition, ViewInfo,
@ -208,16 +208,16 @@ impl<'a> RecordView<'a> {
fn create_tablew(&'a self, cfg: ViewConfig<'a>) -> TableW<'a> {
let layer = self.get_layer_last();
let data = convert_records_to_string(&layer.records, cfg.nu_config, cfg.color_hm);
let data = convert_records_to_string(&layer.records, cfg.nu_config, cfg.style_computer);
let headers = layer.columns.as_ref();
let color_hm = cfg.color_hm;
let style_computer = cfg.style_computer;
let (row, column) = self.get_current_offset();
TableW::new(
headers,
data,
color_hm,
style_computer,
row,
column,
self.theme.table,
@ -301,10 +301,15 @@ impl View for RecordView<'_> {
}
fn collect_data(&self) -> Vec<NuText> {
// Create a "dummy" style_computer.
let dummy_engine_state = EngineState::new();
let dummy_stack = Stack::new();
let style_computer = StyleComputer::new(&dummy_engine_state, &dummy_stack, HashMap::new());
let data = convert_records_to_string(
&self.get_layer_last().records,
&NuConfig::default(),
&HashMap::default(),
&style_computer,
);
data.iter().flatten().cloned().collect()
@ -595,7 +600,7 @@ fn state_reverse_data(state: &mut RecordView<'_>, page_size: usize) {
fn convert_records_to_string(
records: &[Vec<Value>],
cfg: &NuConfig,
color_hm: &NuStyleTable,
style_computer: &StyleComputer,
) -> Vec<Vec<NuText>> {
records
.iter()
@ -603,10 +608,9 @@ fn convert_records_to_string(
row.iter()
.map(|value| {
let text = value.clone().into_abbreviated_string(cfg);
let tp = value.get_type().to_string();
let float_precision = cfg.float_precision as usize;
make_styled_string(text, &tp, 0, false, color_hm, float_precision)
make_styled_string(style_computer, text, Some(value), float_precision)
})
.collect::<Vec<_>>()
})

View File

@ -3,7 +3,9 @@ use std::{
cmp::{max, Ordering},
};
use nu_table::{string_width, Alignment, TextStyle};
use nu_color_config::{Alignment, StyleComputer, TextStyle};
use nu_protocol::Value;
use nu_table::string_width;
use tui::{
buffer::Buffer,
layout::Rect,
@ -12,7 +14,7 @@ use tui::{
};
use crate::{
nu_common::{truncate_str, NuStyle, NuStyleTable, NuText},
nu_common::{truncate_str, NuStyle, NuText},
views::util::{nu_style_to_tui, text_style_to_tui_style},
};
@ -26,7 +28,7 @@ pub struct TableW<'a> {
index_column: usize,
style: TableStyle,
head_position: Orientation,
color_hm: &'a NuStyleTable,
style_computer: &'a StyleComputer<'a>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -58,7 +60,7 @@ impl<'a> TableW<'a> {
pub fn new(
columns: impl Into<Cow<'a, [String]>>,
data: impl Into<Cow<'a, [Vec<NuText>]>>,
color_hm: &'a NuStyleTable,
style_computer: &'a StyleComputer<'a>,
index_row: usize,
index_column: usize,
style: TableStyle,
@ -67,11 +69,11 @@ impl<'a> TableW<'a> {
Self {
columns: columns.into(),
data: data.into(),
style_computer,
index_row,
index_column,
style,
head_position,
color_hm,
}
}
}
@ -190,7 +192,7 @@ impl<'a> TableW<'a> {
width += render_index(
buf,
area,
self.color_hm,
self.style_computer,
self.index_row,
padding_index_l,
padding_index_r,
@ -246,7 +248,7 @@ impl<'a> TableW<'a> {
}
if show_head {
let mut header = [head_row_text(&head, self.color_hm)];
let mut header = [head_row_text(&head, self.style_computer)];
if head_width > use_space as usize {
truncate_str(&mut header[0].0, use_space as usize)
}
@ -330,7 +332,7 @@ impl<'a> TableW<'a> {
left_w += render_index(
buf,
area,
self.color_hm,
self.style_computer,
self.index_row,
padding_index_l,
padding_index_r,
@ -358,7 +360,7 @@ impl<'a> TableW<'a> {
let columns = columns
.iter()
.map(|s| head_row_text(s, self.color_hm))
.map(|s| head_row_text(s, self.style_computer))
.collect::<Vec<_>>();
if is_head_left {
@ -540,13 +542,16 @@ fn check_column_width(
}
struct IndexColumn<'a> {
color_hm: &'a NuStyleTable,
style_computer: &'a StyleComputer<'a>,
start: usize,
}
impl<'a> IndexColumn<'a> {
fn new(color_hm: &'a NuStyleTable, start: usize) -> Self {
Self { color_hm, start }
fn new(style_computer: &'a StyleComputer, start: usize) -> Self {
Self {
style_computer,
start,
}
}
fn estimate_width(&self, height: u16) -> usize {
@ -557,11 +562,13 @@ impl<'a> IndexColumn<'a> {
impl Widget for IndexColumn<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let style = nu_style_to_tui(self.color_hm["row_index"]);
for row in 0..area.height {
let i = 1 + row as usize + self.start;
let text = i.to_string();
let style = nu_style_to_tui(self.style_computer.compute(
"row_index",
&Value::string(text.as_str(), nu_protocol::Span::unknown()),
));
let p = Paragraph::new(text)
.style(style)
@ -606,15 +613,17 @@ fn render_header_borders(
fn render_index(
buf: &mut Buffer,
area: Rect,
color_hm: &NuStyleTable,
style_computer: &StyleComputer,
start_index: usize,
padding_left: u16,
padding_right: u16,
) -> u16 {
let mut width = render_space(buf, area.x, area.y, area.height, padding_left);
let index = IndexColumn::new(color_hm, start_index);
let index = IndexColumn::new(style_computer, start_index);
let w = index.estimate_width(area.height) as u16;
let area = Rect::new(area.x + width, area.y, w, area.height);
@ -769,12 +778,12 @@ fn strip_string(text: &str) -> String {
.unwrap_or_else(|| text.to_owned())
}
fn head_row_text(head: &str, color_hm: &NuStyleTable) -> NuText {
fn head_row_text(head: &str, style_computer: &StyleComputer) -> NuText {
(
String::from(head),
TextStyle {
alignment: Alignment::Center,
color_style: Some(color_hm["header"]),
},
TextStyle::with_style(
Alignment::Center,
style_computer.compute("header", &Value::string(head, nu_protocol::Span::unknown())),
),
)
}

View File

@ -1,14 +1,15 @@
use std::borrow::Cow;
use nu_color_config::style_primitive;
use nu_table::{string_width, Alignment, TextStyle};
use nu_color_config::{Alignment, StyleComputer};
use nu_protocol::{ShellError, Value};
use nu_table::{string_width, TextStyle};
use tui::{
buffer::Buffer,
style::{Color, Modifier, Style},
text::Span,
};
use crate::nu_common::{truncate_str, NuColor, NuStyle, NuStyleTable, NuText};
use crate::nu_common::{truncate_str, NuColor, NuStyle, NuText};
pub fn set_span(
buf: &mut Buffer,
@ -118,39 +119,53 @@ pub fn text_style_to_tui_style(style: TextStyle) -> tui::style::Style {
out
}
// This is identical to the same function in nu-explore/src/nu_common
pub fn make_styled_string(
style_computer: &StyleComputer,
text: String,
text_type: &str,
col: usize,
with_index: bool,
color_hm: &NuStyleTable,
value: Option<&Value>, // None represents table holes.
float_precision: usize,
) -> NuText {
if col == 0 && with_index {
return (text, index_text_style(color_hm));
}
let style = style_primitive(text_type, color_hm);
let mut text = text;
if text_type == "float" {
text = convert_with_precision(&text, float_precision);
}
(text, style)
}
fn index_text_style(color_hm: &std::collections::HashMap<String, NuStyle>) -> TextStyle {
TextStyle {
alignment: Alignment::Right,
color_style: Some(color_hm["row_index"]),
match value {
Some(value) => {
match value {
Value::Float { .. } => {
// set dynamic precision from config
let precise_number = match convert_with_precision(&text, float_precision) {
Ok(num) => num,
Err(e) => e.to_string(),
};
(precise_number, style_computer.style_primitive(value))
}
_ => (text, style_computer.style_primitive(value)),
}
}
None => {
// Though holes are not the same as null, the closure for "empty" is passed a null anyway.
(
text,
TextStyle::with_style(
Alignment::Center,
style_computer.compute("empty", &Value::nothing(nu_protocol::Span::unknown())),
),
)
}
}
}
fn convert_with_precision(val: &str, precision: usize) -> String {
fn convert_with_precision(val: &str, precision: usize) -> Result<String, ShellError> {
// vall will always be a f64 so convert it with precision formatting
match val.trim().parse::<f64>() {
Ok(f) => format!("{:.prec$}", f, prec = precision),
Err(err) => format!("error converting string [{}] to f64; {}", &val, err),
}
let val_float = match val.trim().parse::<f64>() {
Ok(f) => f,
Err(e) => {
return Err(ShellError::GenericError(
format!("error converting string [{}] to f64", &val),
"".to_string(),
None,
Some(e.to_string()),
Vec::new(),
));
}
};
Ok(format!("{:.prec$}", val_float, prec = precision))
}