diff --git a/Cargo.lock b/Cargo.lock index 1d72ef8ea..11db0f192 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2550,6 +2550,7 @@ dependencies = [ "nu-parser", "nu-path", "nu-protocol", + "nu-table", "nu-test-support", "nu-utils", "percent-encoding", @@ -2564,10 +2565,13 @@ name = "nu-color-config" version = "0.72.2" dependencies = [ "nu-ansi-term", - "nu-json", + "nu-engine", + "nu-path", "nu-protocol", - "nu-table", + "nu-test-support", + "nu-utils", "serde", + "tabled", ] [[package]] @@ -2812,9 +2816,14 @@ dependencies = [ name = "nu-table" version = "0.72.2" dependencies = [ + "atty", "json_to_table", "nu-ansi-term", + "nu-color-config", + "nu-engine", + "nu-path", "nu-protocol", + "nu-test-support", "nu-utils", "serde_json", "tabled", diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 8b4d9afa2..d082be6cd 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -18,6 +18,7 @@ nu-path = { path = "../nu-path", version = "0.72.2" } nu-parser = { path = "../nu-parser", version = "0.72.2" } nu-protocol = { path = "../nu-protocol", version = "0.72.2" } nu-utils = { path = "../nu-utils", version = "0.72.2" } +nu-table = { path = "../nu-table", version = "0.72.2" } nu-ansi-term = "0.46.0" nu-color-config = { path = "../nu-color-config", version = "0.72.2" } reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]} diff --git a/crates/nu-cli/src/reedline_config.rs b/crates/nu-cli/src/reedline_config.rs index f27a832b7..c7cbffb08 100644 --- a/crates/nu-cli/src/reedline_config.rs +++ b/crates/nu-cli/src/reedline_config.rs @@ -1,11 +1,11 @@ use super::DescriptionMenu; use crate::{menus::NuMenuCompleter, NuHelpCompleter}; use crossterm::event::{KeyCode, KeyModifiers}; -use nu_color_config::lookup_ansi_color_style; +use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style}; use nu_engine::eval_block; use nu_parser::parse; use nu_protocol::{ - color_value_string, create_menus, + create_menus, engine::{EngineState, Stack, StateWorkingSet}, extract_value, Config, IntoPipelineData, ParsedKeybinding, ParsedMenu, PipelineData, ShellError, Span, Value, @@ -159,14 +159,11 @@ macro_rules! add_style { ($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => { $menu = match extract_value($name, $cols, $vals, $span) { Ok(text) => { - let text = match text { - Value::String { val, .. } => val.clone(), - Value::Record { cols, vals, span } => { - color_value_string(span, cols, vals, $config).into_string("", $config) - } - _ => "green".to_string(), + let style = match text { + Value::String { val, .. } => lookup_ansi_color_style(&val), + Value::Record { .. } => color_record_to_nustyle(&text), + _ => lookup_ansi_color_style("green"), }; - let style = lookup_ansi_color_style(&text); $f($menu, style) } Err(_) => $menu, diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 2fb12d162..283a96c07 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -9,7 +9,7 @@ use fancy_regex::Regex; use lazy_static::lazy_static; use log::{info, trace, warn}; use miette::{IntoDiagnostic, Result}; -use nu_color_config::get_color_config; +use nu_color_config::StyleComputer; use nu_engine::{convert_env_values, eval_block, eval_block_with_early_return}; use nu_parser::{lex, parse, trim_quotes_str}; use nu_protocol::{ @@ -174,8 +174,6 @@ pub fn evaluate_repl( info!("setup colors {}:{}:{}", file!(), line!(), column!()); - let color_hm = get_color_config(config); - info!("update reedline {}:{}:{}", file!(), line!(), column!()); let engine_reference = std::sync::Arc::new(engine_state.clone()); line_editor = line_editor @@ -194,10 +192,14 @@ pub fn evaluate_repl( .with_partial_completions(config.partial_completions) .with_ansi_colors(config.use_ansi_coloring); + let style_computer = StyleComputer::from_config(engine_state, stack); + line_editor = if config.use_ansi_coloring { - line_editor.with_hinter(Box::new( - DefaultHinter::default().with_style(color_hm["hints"]), - )) + line_editor.with_hinter(Box::new({ + // As of Nov 2022, "hints" color_config closures only get `null` passed in. + let style = style_computer.compute("hints", &Value::nothing(Span::unknown())); + DefaultHinter::default().with_style(style) + })) } else { line_editor.disable_hints() }; @@ -928,7 +930,7 @@ pub fn eval_hook( Ok(output) } -pub fn run_hook_block( +fn run_hook_block( engine_state: &EngineState, stack: &mut Stack, block_id: BlockId, diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index d211b215e..04e9c2b62 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -8,8 +8,14 @@ name = "nu-color-config" version = "0.72.2" [dependencies] +serde = { version="1.0.123", features=["derive"] } +# used only for text_style Alignments +tabled = { version = "0.10.0", features = ["color"], default-features = false } + nu-protocol = { path = "../nu-protocol", version = "0.72.2" } nu-ansi-term = "0.46.0" -nu-json = { path = "../nu-json", version = "0.72.2" } -nu-table = { path = "../nu-table", version = "0.72.2" } -serde = { version="1.0.123", features=["derive"] } +nu-utils = { path = "../nu-utils", version = "0.72.2" } +nu-engine = { path = "../nu-engine", version = "0.72.2" } +nu-test-support = { path="../nu-test-support", version = "0.72.2" } +# nu-path is used only for test support +nu-path = { path="../nu-path", version = "0.72.2" } \ No newline at end of file diff --git a/crates/nu-color-config/src/color_config.rs b/crates/nu-color-config/src/color_config.rs index 52dc45d45..56d182871 100644 --- a/crates/nu-color-config/src/color_config.rs +++ b/crates/nu-color-config/src/color_config.rs @@ -1,7 +1,6 @@ -use crate::nu_style::{color_from_hex, color_string_to_nustyle, lookup_style}; -use nu_ansi_term::{Color, Style}; -use nu_protocol::{Config, Value}; -use nu_table::{Alignment, TextStyle}; +use crate::nu_style::{color_from_hex, lookup_style}; +use nu_ansi_term::Style; +use nu_protocol::Value; use std::collections::HashMap; pub fn lookup_ansi_color_style(s: &str) -> Style { @@ -10,13 +9,12 @@ pub fn lookup_ansi_color_style(s: &str) -> Style { .ok() .and_then(|c| c.map(|c| c.normal())) .unwrap_or_default() - } else if s.starts_with('{') { - color_string_to_nustyle(s.to_string()) } else { lookup_style(s) } } +// These two are used only for Explore's very limited color config fn update_hashmap(key: &str, val: &str, hm: &mut HashMap) { // eprintln!("key: {}, val: {}", &key, &val); let color = lookup_ansi_color_style(val); @@ -27,49 +25,6 @@ fn update_hashmap(key: &str, val: &str, hm: &mut HashMap) { } } -pub fn get_color_config(config: &Config) -> HashMap { - let config = config; - - // create the hashmap - let mut hm: HashMap = HashMap::new(); - // set some defaults - // hm.insert("primitive_line".to_string(), Color::White.normal()); - // hm.insert("primitive_pattern".to_string(), Color::White.normal()); - // hm.insert("primitive_path".to_string(), Color::White.normal()); - hm.insert("separator".to_string(), Color::White.normal()); - hm.insert( - "leading_trailing_space_bg".to_string(), - Style::default().on(Color::Rgb(128, 128, 128)), - ); - hm.insert("header".to_string(), Color::Green.bold()); - hm.insert("empty".to_string(), Color::Blue.normal()); - hm.insert("bool".to_string(), Color::White.normal()); - hm.insert("int".to_string(), Color::White.normal()); - hm.insert("filesize".to_string(), Color::White.normal()); - hm.insert("duration".to_string(), Color::White.normal()); - hm.insert("date".to_string(), Color::White.normal()); - hm.insert("range".to_string(), Color::White.normal()); - hm.insert("float".to_string(), Color::White.normal()); - hm.insert("string".to_string(), Color::White.normal()); - hm.insert("nothing".to_string(), Color::White.normal()); - hm.insert("binary".to_string(), Color::White.normal()); - hm.insert("cellpath".to_string(), Color::White.normal()); - hm.insert("row_index".to_string(), Color::Green.bold()); - hm.insert("record".to_string(), Color::White.normal()); - hm.insert("list".to_string(), Color::White.normal()); - hm.insert("block".to_string(), Color::White.normal()); - hm.insert("hints".to_string(), Color::DarkGray.normal()); - - for (key, value) in &config.color_config { - match value.as_string() { - Ok(value) => update_hashmap(key, &value, &mut hm), - Err(_) => continue, - } - } - - hm -} - pub fn get_color_map(colors: &HashMap) -> HashMap { let mut hm: HashMap = HashMap::new(); @@ -81,189 +36,3 @@ pub fn get_color_map(colors: &HashMap) -> HashMap hm } - -// This function will assign a text style to a primitive, or really any string that's -// in the hashmap. The hashmap actually contains the style to be applied. -pub fn style_primitive(primitive: &str, color_hm: &HashMap) -> TextStyle { - match primitive { - "bool" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Left, *s), - None => TextStyle::basic_left(), - } - } - - "int" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Right, *s), - None => TextStyle::basic_right(), - } - } - - "filesize" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Right, *s), - None => TextStyle::basic_right(), - } - } - - "duration" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Left, *s), - None => TextStyle::basic_left(), - } - } - - "date" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Left, *s), - None => TextStyle::basic_left(), - } - } - - "range" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Left, *s), - None => TextStyle::basic_left(), - } - } - - "float" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Right, *s), - None => TextStyle::basic_right(), - } - } - - "string" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Left, *s), - None => TextStyle::basic_left(), - } - } - - "nothing" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Left, *s), - None => TextStyle::basic_left(), - } - } - - // not sure what to do with error - // "error" => {} - "binary" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Left, *s), - None => TextStyle::basic_left(), - } - } - - "cellpath" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Left, *s), - None => TextStyle::basic_left(), - } - } - - "row_index" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Right, *s), - None => TextStyle::new() - .alignment(Alignment::Right) - .fg(Color::Green) - .bold(Some(true)), - } - } - - "record" | "list" | "block" => { - let style = color_hm.get(primitive); - match style { - Some(s) => TextStyle::with_style(Alignment::Left, *s), - None => TextStyle::basic_left(), - } - } - - // types in nushell but not in engine-q - // "Line" => { - // let style = color_hm.get("Primitive::Line"); - // match style { - // Some(s) => TextStyle::with_style(Alignment::Left, *s), - // None => TextStyle::basic_left(), - // } - // } - // "GlobPattern" => { - // let style = color_hm.get("Primitive::GlobPattern"); - // match style { - // Some(s) => TextStyle::with_style(Alignment::Left, *s), - // None => TextStyle::basic_left(), - // } - // } - // "FilePath" => { - // let style = color_hm.get("Primitive::FilePath"); - // match style { - // Some(s) => TextStyle::with_style(Alignment::Left, *s), - // None => TextStyle::basic_left(), - // } - // } - // "BeginningOfStream" => { - // let style = color_hm.get("Primitive::BeginningOfStream"); - // match style { - // Some(s) => TextStyle::with_style(Alignment::Left, *s), - // None => TextStyle::basic_left(), - // } - // } - // "EndOfStream" => { - // let style = color_hm.get("Primitive::EndOfStream"); - // match style { - // Some(s) => TextStyle::with_style(Alignment::Left, *s), - // None => TextStyle::basic_left(), - // } - // } - _ => TextStyle::basic_left(), - } -} - -#[test] -fn test_hm() { - use nu_ansi_term::{Color, Style}; - - let mut hm: HashMap = HashMap::new(); - hm.insert("primitive_int".to_string(), Color::White.normal()); - hm.insert("primitive_decimal".to_string(), Color::White.normal()); - hm.insert("primitive_filesize".to_string(), Color::White.normal()); - hm.insert("primitive_string".to_string(), Color::White.normal()); - hm.insert("primitive_line".to_string(), Color::White.normal()); - hm.insert("primitive_columnpath".to_string(), Color::White.normal()); - hm.insert("primitive_pattern".to_string(), Color::White.normal()); - hm.insert("primitive_boolean".to_string(), Color::White.normal()); - hm.insert("primitive_date".to_string(), Color::White.normal()); - hm.insert("primitive_duration".to_string(), Color::White.normal()); - hm.insert("primitive_range".to_string(), Color::White.normal()); - hm.insert("primitive_path".to_string(), Color::White.normal()); - hm.insert("primitive_binary".to_string(), Color::White.normal()); - hm.insert("separator".to_string(), Color::White.normal()); - hm.insert("header_align".to_string(), Color::Green.bold()); - hm.insert("header".to_string(), Color::Green.bold()); - hm.insert("header_style".to_string(), Style::default()); - hm.insert("row_index".to_string(), Color::Green.bold()); - hm.insert( - "leading_trailing_space_bg".to_string(), - Style::default().on(Color::Rgb(128, 128, 128)), - ); - - update_hashmap("primitive_int", "green", &mut hm); - - assert_eq!(hm["primitive_int"], Color::Green.normal()); -} diff --git a/crates/nu-color-config/src/lib.rs b/crates/nu-color-config/src/lib.rs index b5441979e..3bd4dc9e4 100644 --- a/crates/nu-color-config/src/lib.rs +++ b/crates/nu-color-config/src/lib.rs @@ -2,8 +2,12 @@ mod color_config; mod matching_brackets_style; mod nu_style; mod shape_color; +mod style_computer; +mod text_style; pub use color_config::*; pub use matching_brackets_style::*; pub use nu_style::*; pub use shape_color::*; +pub use style_computer::*; +pub use text_style::*; diff --git a/crates/nu-color-config/src/nu_style.rs b/crates/nu-color-config/src/nu_style.rs index ef6626716..777582cd3 100644 --- a/crates/nu-color-config/src/nu_style.rs +++ b/crates/nu-color-config/src/nu_style.rs @@ -1,4 +1,5 @@ use nu_ansi_term::{Color, Style}; +use nu_protocol::Value; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, PartialEq, Eq, Debug)] @@ -83,22 +84,29 @@ pub fn parse_nustyle(nu_style: NuStyle) -> Style { style } -pub fn color_string_to_nustyle(color_string: String) -> Style { - // eprintln!("color_string: {}", &color_string); - if color_string.chars().count() < 1 { - Style::default() - } else { - let nu_style = match nu_json::from_str::(&color_string) { - Ok(s) => s, - Err(_) => NuStyle { - fg: None, - bg: None, - attr: None, - }, - }; +// Converts the color_config records, { fg, bg, attr }, into a Style. +pub fn color_record_to_nustyle(value: &Value) -> Style { + let mut fg = None; + let mut bg = None; + let mut attr = None; + let v = value.as_record(); + if let Ok((cols, inner_vals)) = v { + for (k, v) in cols.iter().zip(inner_vals) { + // Because config already type-checked the color_config records, this doesn't bother giving errors + // if there are unrecognised keys or bad values. + if let Ok(v) = v.as_string() { + match k.as_str() { + "fg" => fg = Some(v), - parse_nustyle(nu_style) + "bg" => bg = Some(v), + + "attr" => attr = Some(v), + _ => (), + } + } + } } + parse_nustyle(NuStyle { fg, bg, attr }) } pub fn color_from_hex( diff --git a/crates/nu-color-config/src/style_computer.rs b/crates/nu-color-config/src/style_computer.rs new file mode 100644 index 000000000..b8c37a249 --- /dev/null +++ b/crates/nu-color-config/src/style_computer.rs @@ -0,0 +1,285 @@ +use crate::{color_record_to_nustyle, lookup_ansi_color_style, TextStyle}; +use nu_ansi_term::{Color, Style}; +use nu_engine::eval_block; +use nu_protocol::{ + engine::{EngineState, Stack, StateWorkingSet}, + CliError, IntoPipelineData, Value, +}; +use tabled::alignment::AlignmentHorizontal; + +use std::{ + collections::HashMap, + fmt::{Debug, Formatter, Result}, +}; + +// ComputableStyle represents the valid user style types: a single color value, or a closure which +// takes an input value and produces a color value. The latter represents a value which +// is computed at use-time. +#[derive(Debug, Clone)] +pub enum ComputableStyle { + Static(Style), + Closure(Value), +} + +// macro used for adding initial values to the style hashmap +macro_rules! initial { + ($a:expr, $b:expr) => { + ($a.to_string(), ComputableStyle::Static($b)) + }; +} + +// An alias for the mapping used internally by StyleComputer. +pub type StyleMapping = HashMap; +// +// A StyleComputer is an all-in-one way to compute styles. A nu command can +// simply create it with from_config(), and then use it with compute(). +// It stores the engine state and stack needed to run closures that +// may be defined as a user style. +// +pub struct StyleComputer<'a> { + engine_state: &'a EngineState, + stack: &'a Stack, + map: StyleMapping, +} + +impl<'a> StyleComputer<'a> { + // This is NOT meant to be used in most cases - please use from_config() instead. + // This only exists for testing purposes. + pub fn new( + engine_state: &'a EngineState, + stack: &'a Stack, + map: StyleMapping, + ) -> StyleComputer<'a> { + StyleComputer { + engine_state, + stack, + map, + } + } + // The main method. Takes a string name which maps to a color_config style name, + // and a Nu value to pipe into any closures that may have been defined there. + pub fn compute(&self, style_name: &str, value: &Value) -> Style { + match self.map.get(style_name) { + // Static values require no computation. + Some(ComputableStyle::Static(s)) => *s, + // Closures are run here. + Some(ComputableStyle::Closure(Value::Closure { + val: block_id, + captures, + span, + })) => { + let block = self.engine_state.get_block(*block_id).clone(); + // Because captures_to_stack() clones, we don't need to use with_env() here + // (contrast with_env() usage in `each` or `do`). + let mut stack = self.stack.captures_to_stack(captures); + + // Support 1-argument blocks as well as 0-argument blocks. + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, value.clone()); + } + } + + // Run the block. + match eval_block( + self.engine_state, + &mut stack, + &block, + value.clone().into_pipeline_data(), + false, + false, + ) { + Ok(v) => { + let value = v.into_value(*span); + // These should be the same color data forms supported by color_config. + match value { + Value::Record { .. } => color_record_to_nustyle(&value), + Value::String { val, .. } => lookup_ansi_color_style(&val), + _ => Style::default(), + } + } + // This is basically a copy of nu_cli::report_error(), but that isn't usable due to + // dependencies. While crudely spitting out a bunch of errors like this is not ideal, + // currently hook closure errors behave roughly the same. + Err(e) => { + eprintln!( + "Error: {:?}", + CliError(&e, &StateWorkingSet::new(self.engine_state)) + ); + Style::default() + } + } + } + // There should be no other kinds of values (due to create_map() in config.rs filtering them out) + // so this is just a fallback. + _ => Style::default(), + } + } + + // Used only by the `table` command. + pub fn style_primitive(&self, value: &Value) -> TextStyle { + let s = self.compute(&value.get_type().to_string(), value); + match *value { + Value::Bool { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s), + + Value::Int { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s), + + Value::Filesize { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s), + + Value::Duration { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s), + + Value::Date { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s), + + Value::Range { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s), + + Value::Float { .. } => TextStyle::with_style(AlignmentHorizontal::Right, s), + + Value::String { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s), + + Value::Nothing { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s), + + Value::Binary { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s), + + Value::CellPath { .. } => TextStyle::with_style(AlignmentHorizontal::Left, s), + + Value::Record { .. } | Value::List { .. } | Value::Block { .. } => { + TextStyle::with_style(AlignmentHorizontal::Left, s) + } + _ => TextStyle::basic_left(), + } + } + + // The main constructor. + pub fn from_config(engine_state: &'a EngineState, stack: &'a Stack) -> StyleComputer<'a> { + let config = engine_state.get_config(); + + // Create the hashmap + let mut map: StyleMapping = HashMap::from([ + initial!("separator", Color::White.normal()), + initial!( + "leading_trailing_space_bg", + Style::default().on(Color::Rgb(128, 128, 128)) + ), + initial!("header", Color::White.normal()), + initial!("empty", Color::White.normal()), + initial!("bool", Color::White.normal()), + initial!("int", Color::White.normal()), + initial!("filesize", Color::White.normal()), + initial!("duration", Color::White.normal()), + initial!("date", Color::White.normal()), + initial!("range", Color::White.normal()), + initial!("float", Color::White.normal()), + initial!("string", Color::White.normal()), + initial!("nothing", Color::White.normal()), + initial!("binary", Color::White.normal()), + initial!("cellpath", Color::White.normal()), + initial!("row_index", Color::Green.bold()), + initial!("record", Color::White.normal()), + initial!("list", Color::White.normal()), + initial!("block", Color::White.normal()), + initial!("hints", Color::DarkGray.normal()), + ]); + + for (key, value) in &config.color_config { + match value { + Value::Closure { .. } => { + map.insert(key.to_string(), ComputableStyle::Closure(value.clone())); + } + Value::Record { .. } => { + map.insert( + key.to_string(), + ComputableStyle::Static(color_record_to_nustyle(value)), + ); + } + Value::String { val, .. } => { + // update the stylemap with the found key + let color = lookup_ansi_color_style(val.as_str()); + if let Some(v) = map.get_mut(key) { + *v = ComputableStyle::Static(color); + } else { + map.insert(key.to_string(), ComputableStyle::Static(color)); + } + } + // This should never occur. + _ => (), + } + } + StyleComputer::new(engine_state, stack, map) + } +} + +// Because EngineState doesn't have Debug (Dec 2022), +// this incomplete representation must be used. +impl<'a> Debug for StyleComputer<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_struct("StyleComputer") + .field("map", &self.map) + .finish() + } +} + +#[test] +fn test_computable_style_static() { + use nu_protocol::Span; + + let style1 = Style::default().italic(); + let style2 = Style::default().underline(); + // Create a "dummy" style_computer for this test. + let dummy_engine_state = EngineState::new(); + let mut dummy_stack = Stack::new(); + let style_computer = StyleComputer::new( + &dummy_engine_state, + &mut dummy_stack, + HashMap::from([ + ("string".into(), ComputableStyle::Static(style1)), + ("row_index".into(), ComputableStyle::Static(style2)), + ]), + ); + assert_eq!( + style_computer.compute("string", &Value::nothing(Span::unknown())), + style1 + ); + assert_eq!( + style_computer.compute("row_index", &Value::nothing(Span::unknown())), + style2 + ); +} + +// Because each closure currently runs in a separate environment, checks that the closures have run +// must use the filesystem. +#[test] +fn test_computable_style_closure_basic() { + use nu_test_support::{nu, nu_repl_code, playground::Playground}; + Playground::setup("computable_style_closure_basic", |dirs, _| { + let inp = [ + r#"let-env config = { + color_config: { + string: {|e| touch ($e + '.obj'); 'red' } + } + };"#, + "[bell book candle] | table | ignore", + "ls | get name | to nuon", + ]; + let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual_repl.err, ""); + assert_eq!(actual_repl.out, "[bell.obj, book.obj, candle.obj]"); + }); +} + +#[test] +fn test_computable_style_closure_errors() { + use nu_test_support::{nu, nu_repl_code}; + let inp = [ + r#"let-env config = { + color_config: { + string: {|e| $e + 2 } + } + };"#, + "[bell] | table", + ]; + let actual_repl = nu!(cwd: ".", nu_repl_code(&inp)); + // Check that the error was printed + assert!(actual_repl.err.contains("type mismatch for operator")); + // Check that the value was printed + assert!(actual_repl.out.contains("bell")); +} diff --git a/crates/nu-table/src/textstyle.rs b/crates/nu-color-config/src/text_style.rs similarity index 92% rename from crates/nu-table/src/textstyle.rs rename to crates/nu-color-config/src/text_style.rs index 8fbe7228b..cba23923b 100644 --- a/crates/nu-table/src/textstyle.rs +++ b/crates/nu-color-config/src/text_style.rs @@ -1,4 +1,5 @@ use nu_ansi_term::{Color, Style}; +use std::fmt::Display; pub type Alignment = tabled::alignment::AlignmentHorizontal; @@ -239,3 +240,23 @@ impl Default for TextStyle { Self::new() } } + +impl tabled::papergrid::Color for TextStyle { + fn fmt_prefix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(color) = &self.color_style { + color.prefix().fmt(f)?; + } + + Ok(()) + } + + fn fmt_suffix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(color) = &self.color_style { + if !color.is_plain() { + f.write_str("\u{1b}[0m")?; + } + } + + Ok(()) + } +} diff --git a/crates/nu-command/src/core_commands/help.rs b/crates/nu-command/src/core_commands/help.rs index 53c78b24c..983374254 100644 --- a/crates/nu-command/src/core_commands/help.rs +++ b/crates/nu-command/src/core_commands/help.rs @@ -1,9 +1,9 @@ use fancy_regex::Regex; use nu_ansi_term::{ - Color::{Default, Red, White}, + Color::{Red, White}, Style, }; -use nu_color_config::get_color_config; +use nu_color_config::StyleComputer; use nu_engine::{get_full_help, CallExt}; use nu_protocol::{ ast::Call, @@ -86,13 +86,13 @@ fn help( let find: Option> = call.get_flag(engine_state, stack, "find")?; let rest: Vec> = call.rest(engine_state, stack, 0)?; let commands = engine_state.get_decl_ids_sorted(false); - let config = engine_state.get_config(); - let color_hm = get_color_config(config); - let default_style = Style::new().fg(Default).on(Default); - let string_style = match color_hm.get("string") { - Some(style) => style, - None => &default_style, - }; + + // 🚩The following two-lines are copied from filters/find.rs: + let style_computer = StyleComputer::from_config(engine_state, stack); + // Currently, search results all use the same style. + // Also note that this sample string is passed into user-written code (the closure that may or may not be + // defined for "string"). + let string_style = style_computer.compute("string", &Value::string("search result", head)); if let Some(f) = find { let org_search_string = f.item.clone(); @@ -123,7 +123,7 @@ fn help( cols.push("name".into()); vals.push(Value::String { val: if key_match { - highlight_search_string(&key, &org_search_string, string_style)? + highlight_search_string(&key, &org_search_string, &string_style)? } else { key }, @@ -142,7 +142,7 @@ fn help( cols.push("usage".into()); vals.push(Value::String { val: if usage_match { - highlight_search_string(&usage, &org_search_string, string_style)? + highlight_search_string(&usage, &org_search_string, &string_style)? } else { usage }, @@ -172,7 +172,7 @@ fn help( match highlight_search_string( term, &org_search_string, - string_style, + &string_style, ) { Ok(s) => s, Err(_) => { diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs index 25fd03d87..48566c60b 100644 --- a/crates/nu-command/src/filters/find.rs +++ b/crates/nu-command/src/filters/find.rs @@ -2,8 +2,8 @@ use crate::help::highlight_search_string; use fancy_regex::Regex; use lscolors::{Color as LsColors_Color, LsColors, Style as LsColors_Style}; -use nu_ansi_term::{Color, Color::Default, Style}; -use nu_color_config::get_color_config; +use nu_ansi_term::{Color, Style}; +use nu_color_config::StyleComputer; use nu_engine::{env_to_string, CallExt}; use nu_protocol::{ ast::Call, @@ -308,12 +308,12 @@ fn find_with_rest_and_highlight( }) .collect::>(); - let color_hm = get_color_config(&config); - let default_style = Style::new().fg(Default).on(Default); - let string_style = match color_hm.get("string") { - Some(style) => *style, - None => default_style, - }; + let style_computer = StyleComputer::from_config(&engine_state, stack); + // Currently, search results all use the same style. + // Also note that this sample string is passed into user-written code (the closure that may or may not be + // defined for "string"). + let string_style = style_computer.compute("string", &Value::string("search result", span)); + 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, diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 0afaf42ea..2a5615a2a 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -58,7 +58,7 @@ impl Command for External { // Translate environment variables from Values to Strings let env_vars_str = env_to_strings(engine_state, stack)?; - fn value_as_spanned(value: Value, name: &String) -> Result, ShellError> { + fn value_as_spanned(value: Value) -> Result, ShellError> { let span = value.span()?; value @@ -66,11 +66,7 @@ impl Command for External { .map(|item| Spanned { item, span }) .map_err(|_| { ShellError::ExternalCommand( - format!( - "Cannot convert {} to a string argument for '{}'", - value.get_type(), - name - ), + format!("Cannot convert {} to a string", value.get_type()), "All arguments to an external command need to be string-compatible".into(), span, ) @@ -87,13 +83,13 @@ impl Command for External { // Example: one_arg may be something like ["ls" "-a"] // convert it to "ls" "-a" for v in vals { - spanned_args.push(value_as_spanned(v, &name.item)?); + spanned_args.push(value_as_spanned(v)?); // for arguments in list, it's always treated as a whole arguments arg_keep_raw.push(true); } } val => { - spanned_args.push(value_as_spanned(val, &name.item)?); + spanned_args.push(value_as_spanned(val)?); match one_arg_expr.expr { // refer to `parse_dollar_expr` function // the expression type of $variable_name, $"($variable_name)" diff --git a/crates/nu-command/src/viewers/explore.rs b/crates/nu-command/src/viewers/explore.rs index dc8872aa4..2831b7d90 100644 --- a/crates/nu-command/src/viewers/explore.rs +++ b/crates/nu-command/src/viewers/explore.rs @@ -1,7 +1,5 @@ -use std::collections::HashMap; - use nu_ansi_term::{Color, Style}; -use nu_color_config::{get_color_config, get_color_map}; +use nu_color_config::{get_color_map, StyleComputer}; use nu_engine::CallExt; use nu_explore::{ run_pager, @@ -13,6 +11,7 @@ use nu_protocol::{ engine::{Command, EngineState, Stack}, Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; +use std::collections::HashMap; /// A `less` like program to render a [Value] as a table. #[derive(Clone)] @@ -70,7 +69,7 @@ impl Command for Explore { let ctrlc = engine_state.ctrlc.clone(); let nu_config = engine_state.get_config(); - let color_hm = get_color_config(nu_config); + let style_computer = StyleComputer::from_config(engine_state, stack); let mut config = nu_config.explore.clone(); prepare_default_config(&mut config); @@ -81,7 +80,7 @@ impl Command for Explore { let style = style_from_config(&config); - let mut config = PagerConfig::new(nu_config, &color_hm, config); + let mut config = PagerConfig::new(nu_config, &style_computer, config); config.style = style; config.reverse = is_reverse; config.peek_value = peek_value; @@ -89,7 +88,7 @@ impl Command for Explore { config.exit_esc = exit_esc; config.show_banner = show_banner; - let result = run_pager(engine_state, stack, ctrlc, input, config); + let result = run_pager(engine_state, &mut stack.clone(), ctrlc, input, config); match result { Ok(Some(value)) => Ok(PipelineData::Value(value, None)), @@ -128,6 +127,8 @@ impl Command for Explore { } } +// For now, this doesn't use StyleComputer. +// As such, closures can't be given as styles for Explore. fn is_need_banner(config: &HashMap) -> Option { config.get("help_banner").and_then(|v| v.as_bool().ok()) } @@ -170,10 +171,6 @@ fn style_from_config(config: &HashMap) -> StyleConfig { style.cmd_bar_background = *s; } - if let Some(s) = colors.get("highlight") { - style.highlight = *s; - } - if let Some(hm) = config.get("status").and_then(create_map) { let colors = get_color_map(&hm); diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 27bc09c32..972572878 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -1,5 +1,6 @@ use lscolors::{LsColors, Style}; -use nu_color_config::{color_from_hex, get_color_config, style_primitive}; +use nu_color_config::color_from_hex; +use nu_color_config::{Alignment, StyleComputer, TextStyle}; use nu_engine::{column::get_columns, env_to_string, CallExt}; use nu_protocol::{ ast::{Call, PathMember}, @@ -8,11 +9,12 @@ use nu_protocol::{ PipelineData, PipelineMetadata, RawStream, ShellError, Signature, Span, SyntaxShape, 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 nu_utils::get_ls_colors; +use rayon::prelude::*; use std::sync::Arc; use std::time::Instant; -use std::{cmp::max, collections::HashMap, path::PathBuf, sync::atomic::AtomicBool}; +use std::{cmp::max, path::PathBuf, sync::atomic::AtomicBool}; use terminal_size::{Height, Width}; use url::Url; @@ -21,7 +23,6 @@ const STREAM_TIMEOUT_CHECK_INTERVAL: usize = 100; const INDEX_COLUMN_NAME: &str = "index"; type NuText = (String, TextStyle); -type NuColorMap = HashMap; fn get_width_param(width_param: Option) -> usize { if let Some(col) = width_param { @@ -253,6 +254,7 @@ fn handle_table_command( metadata: None, trim_end_newline: false, }), + // None of these two receive a StyleComputer because handle_row_stream() can produce it by itself using engine_state and stack. PipelineData::Value(Value::List { vals, .. }, metadata) => handle_row_stream( engine_state, stack, @@ -272,10 +274,17 @@ fn handle_table_command( metadata, ), PipelineData::Value(Value::Record { cols, vals, span }, ..) => { + // Create a StyleComputer to compute styles for each value in the table. + let style_computer = &StyleComputer::from_config(engine_state, stack); let result = match table_view { - TableView::General => { - build_general_table2(cols, vals, ctrlc.clone(), config, term_width) - } + TableView::General => build_general_table2( + style_computer, + cols, + vals, + ctrlc.clone(), + config, + term_width, + ), TableView::Expanded { limit, flatten, @@ -288,13 +297,16 @@ fn handle_table_command( span, ctrlc.clone(), config, + style_computer, term_width, limit, flatten, sep, ) } - TableView::Collapsed => build_collapsed_table(cols, vals, config, term_width), + TableView::Collapsed => { + build_collapsed_table(style_computer, cols, vals, config, term_width) + } }?; let result = strip_output_color(result, config); @@ -358,6 +370,7 @@ fn supported_table_modes() -> Vec { } fn build_collapsed_table( + style_computer: &StyleComputer, cols: Vec, vals: Vec, config: &Config, @@ -369,9 +382,16 @@ fn build_collapsed_table( span: Span::new(0, 0), }; - let color_hm = get_color_config(config); let theme = load_theme_from_config(config); - let table = nu_table::NuTable::new(value, true, term_width, config, &color_hm, &theme, false); + let table = nu_table::NuTable::new( + value, + true, + term_width, + config, + style_computer, + &theme, + false, + ); let table = table.draw(); @@ -379,6 +399,7 @@ fn build_collapsed_table( } fn build_general_table2( + style_computer: &StyleComputer, cols: Vec, vals: Vec, ctrlc: Option>, @@ -400,8 +421,7 @@ fn build_general_table2( } let data_len = data.len(); - let color_hm = get_color_config(config); - 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 table = NuTable::new(data, (data_len, 2)); @@ -410,6 +430,7 @@ fn build_general_table2( Ok(table) } +// The table produced by `table -e` #[allow(clippy::too_many_arguments)] fn build_expanded_table( cols: Vec, @@ -417,12 +438,12 @@ fn build_expanded_table( span: Span, ctrlc: Option>, config: &Config, + style_computer: &StyleComputer, term_width: usize, expand_limit: Option, flatten: bool, flatten_sep: &str, ) -> Result, 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. @@ -431,7 +452,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)) @@ -454,7 +475,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); @@ -466,7 +487,7 @@ fn build_expanded_table( ctrlc.clone(), config, span, - &color_hm, + style_computer, deep, flatten, flatten_sep, @@ -476,14 +497,13 @@ fn build_expanded_table( match table { Some((mut table, with_header, with_index)) => { // controll width via removing table columns. - let theme = load_theme_from_config(config); table.truncate(remaining_width, &theme); is_expanded = true; let table_config = create_table_config( config, - &color_hm, + style_computer, table.count_rows(), with_header, with_index, @@ -499,7 +519,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 } } } @@ -510,6 +530,7 @@ fn build_expanded_table( span, ctrlc.clone(), config, + style_computer, remaining_width, deep, flatten, @@ -525,7 +546,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) @@ -533,7 +554,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) } } @@ -555,7 +576,7 @@ fn build_expanded_table( } let data_len = data.len(); - 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 table = NuTable::new(data, (data_len, 2)); let table_s = table.clone().draw(table_config.clone(), term_width); @@ -700,7 +721,10 @@ fn handle_row_stream( stdout: Some(RawStream::new( Box::new(PagingTableCreator { row_offset, - config: engine_state.get_config().clone(), + // 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: engine_state.clone(), + stack: stack.clone(), ctrlc: ctrlc.clone(), head, stream, @@ -742,18 +766,71 @@ fn make_clickable_link( } } -#[allow(clippy::too_many_arguments)] +// convert_to_table() defers all its style computations so that they can be run in parallel using par_extend(). +// This structure holds the intermediate computations. +// Currently, the other table forms don't use this. +// Because of how table-specific this is, I don't think this can be pushed into StyleComputer itself. +enum DeferredStyleComputation { + Value { value: Value }, + Header { text: String }, + RowIndex { text: String }, + Empty {}, +} + +impl DeferredStyleComputation { + // This is only run inside a par_extend(). + fn compute(&self, config: &Config, style_computer: &StyleComputer) -> NuText { + match self { + DeferredStyleComputation::Value { value } => { + match value { + // Float precision is required here. + Value::Float { val, .. } => ( + format!("{:.prec$}", val, prec = config.float_precision as usize), + style_computer.style_primitive(value), + ), + _ => ( + value.into_abbreviated_string(config), + style_computer.style_primitive(value), + ), + } + } + DeferredStyleComputation::Header { text } => ( + text.clone(), + TextStyle::with_style( + Alignment::Center, + style_computer + .compute("header", &Value::string(text.as_str(), Span::unknown())), + ), + ), + DeferredStyleComputation::RowIndex { text } => ( + text.clone(), + TextStyle::with_style( + Alignment::Right, + style_computer + .compute("row_index", &Value::string(text.as_str(), Span::unknown())), + ), + ), + DeferredStyleComputation::Empty {} => ( + "❎".into(), + TextStyle::with_style( + Alignment::Right, + style_computer.compute("empty", &Value::nothing(Span::unknown())), + ), + ), + } + } +} + fn convert_to_table( row_offset: usize, input: &[Value], ctrlc: Option>, config: &Config, head: Span, - color_hm: &NuColorMap, + style_computer: &StyleComputer, ) -> Result, ShellError> { let mut headers = get_columns(input); let mut input = input.iter().peekable(); - let float_precision = config.float_precision as usize; let with_index = match config.table_index_mode { TableIndexMode::Always => true, TableIndexMode::Never => false, @@ -764,7 +841,9 @@ fn convert_to_table( return Ok(None); } - if !headers.is_empty() && with_index { + let with_header = !headers.is_empty(); + + if with_header && with_index { headers.insert(0, "#".into()); } @@ -773,26 +852,18 @@ fn convert_to_table( let headers: Vec<_> = headers .into_iter() .filter(|header| header != INDEX_COLUMN_NAME) - .map(|text| { - NuTable::create_cell( - text, - TextStyle { - alignment: Alignment::Center, - color_style: Some(color_hm["header"]), - }, - ) - }) + .map(|text| DeferredStyleComputation::Header { text }) .collect(); - let with_header = !headers.is_empty(); let mut count_columns = headers.len(); - let mut data: Vec> = if headers.is_empty() { + let mut data: Vec> = if !with_header { Vec::new() } else { vec![headers] }; + // Turn each item of each row into a DeferredStyleComputation for that item. for (row_num, item) in input.enumerate() { if nu_utils::ctrl_c::was_pressed(&ctrlc) { return Ok(None); @@ -812,26 +883,36 @@ fn convert_to_table( } .unwrap_or_else(|| (row_num + row_offset).to_string()); - let value = make_index_string(text, color_hm); - let value = NuTable::create_cell(value.0, value.1); - - row.push(value); + row.push(DeferredStyleComputation::RowIndex { text }); } if !with_header { - let text = item.into_abbreviated_string(config); - let text_type = item.get_type().to_string(); - let value = make_styled_string(text, &text_type, color_hm, float_precision); - let value = NuTable::create_cell(value.0, value.1); - - row.push(value); + row.push(DeferredStyleComputation::Value { + value: item.clone(), + }); } else { let skip_num = usize::from(with_index); + // data[0] is used here because headers (the direct reference to it) has been moved. for header in data[0].iter().skip(skip_num) { - let value = - create_table2_entry_basic(item, header.as_ref(), head, config, color_hm); - let value = NuTable::create_cell(value.0, value.1); - row.push(value); + if let DeferredStyleComputation::Header { text } = header { + row.push(match item { + Value::Record { .. } => { + let path = PathMember::String { + val: text.clone(), + span: head, + }; + let val = item.clone().follow_cell_path(&[path], false); + + match val { + Ok(val) => DeferredStyleComputation::Value { value: val }, + Err(_) => DeferredStyleComputation::Empty {}, + } + } + _ => DeferredStyleComputation::Value { + value: item.clone(), + }, + }); + } } } @@ -840,8 +921,25 @@ fn convert_to_table( data.push(row); } - let count_rows = data.len(); - let table = NuTable::new(data, (count_rows, count_columns)); + // All the computations are parallelised here. + // NOTE: It's currently not possible to Ctrl-C out of this... + let mut cells: Vec> = Vec::with_capacity(data.len()); + data.into_par_iter() + .map(|row| { + let mut new_row = Vec::with_capacity(row.len()); + row.into_par_iter() + .map(|deferred| { + let pair = deferred.compute(config, style_computer); + + NuTable::create_cell(pair.0, pair.1) + }) + .collect_into_vec(&mut new_row); + new_row + }) + .collect_into_vec(&mut cells); + + let count_rows = cells.len(); + let table = NuTable::new(cells, (count_rows, count_columns)); Ok(Some((table, with_header, with_index))) } @@ -854,7 +952,7 @@ fn convert_to_table2<'a>( ctrlc: Option>, config: &Config, head: Span, - color_hm: &NuColorMap, + style_computer: &StyleComputer, deep: Option, flatten: bool, flatten_sep: &str, @@ -903,7 +1001,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() { @@ -920,7 +1021,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); @@ -952,7 +1053,7 @@ fn convert_to_table2<'a>( item, config, &ctrlc, - color_hm, + style_computer, deep, flatten, flatten_sep, @@ -994,7 +1095,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.clone(), + header_style(style_computer, header.clone()), + )); for (row, item) in input.clone().into_iter().enumerate() { if nu_utils::ctrl_c::was_pressed(&ctrlc) { @@ -1007,11 +1111,11 @@ fn convert_to_table2<'a>( let value = create_table2_entry( item, - &header, + header.as_str(), head, config, &ctrlc, - color_hm, + style_computer, deep, flatten, flatten_sep, @@ -1039,7 +1143,7 @@ fn convert_to_table2<'a>( return Ok(None); } - 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); @@ -1064,7 +1168,7 @@ fn convert_to_table2<'a>( return Ok(None); } - 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); @@ -1134,10 +1238,11 @@ fn lookup_index_value(item: &Value, config: &Config) -> Option { .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), } } @@ -1147,7 +1252,7 @@ fn create_table2_entry_basic( header: &str, head: Span, config: &Config, - color_hm: &NuColorMap, + style_computer: &StyleComputer, ) -> NuText { match item { Value::Record { .. } => { @@ -1156,11 +1261,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), } } @@ -1171,7 +1276,7 @@ fn create_table2_entry( head: Span, config: &Config, ctrlc: &Option>, - color_hm: &NuColorMap, + style_computer: &StyleComputer, deep: Option, flatten: bool, flatten_sep: &str, @@ -1188,20 +1293,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, @@ -1210,8 +1315,8 @@ fn create_table2_entry( } } -fn error_sign(color_hm: &HashMap) -> (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 { @@ -1224,7 +1329,9 @@ fn convert_to_table2_entry( item: &Value, config: &Config, ctrlc: &Option>, - 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, flatten: bool, flatten_sep: &str, @@ -1232,13 +1339,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, @@ -1246,7 +1353,7 @@ fn convert_to_table2_entry( ctrlc.clone(), config, *span, - color_hm, + style_computer, deep.map(|i| i - 1), flatten, flatten_sep, @@ -1257,7 +1364,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, @@ -1272,7 +1379,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) } } } @@ -1283,7 +1390,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 { @@ -1293,7 +1400,7 @@ fn convert_to_table2_entry( ctrlc.clone(), config, *span, - color_hm, + style_computer, deep.map(|i| i - 1), flatten, flatten_sep, @@ -1304,7 +1411,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, @@ -1320,23 +1427,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); } @@ -1344,39 +1453,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 { @@ -1399,8 +1527,9 @@ fn convert_with_precision(val: &str, precision: usize) -> Result>, - config: Config, row_offset: usize, width_param: Option, view: TableView, @@ -1408,7 +1537,7 @@ struct PagingTableCreator { impl PagingTableCreator { fn build_extended( - &self, + &mut self, batch: &[Value], limit: Option, flatten: bool, @@ -1418,17 +1547,18 @@ impl PagingTableCreator { return Ok(None); } + let config = self.engine_state.get_config(); + let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack); let term_width = get_width_param(self.width_param); - let color_hm = get_color_config(&self.config); - let theme = load_theme_from_config(&self.config); + let theme = load_theme_from_config(config); let table = convert_to_table2( self.row_offset, batch.iter(), self.ctrlc.clone(), - &self.config, + config, self.head, - &color_hm, + &style_computer, limit, flatten, flatten_separator.as_deref().unwrap_or(" "), @@ -1443,8 +1573,8 @@ impl PagingTableCreator { table.truncate(term_width, &theme); let table_config = create_table_config( - &self.config, - &color_hm, + config, + &style_computer, table.count_rows(), with_header, with_index, @@ -1476,16 +1606,17 @@ impl PagingTableCreator { Ok(table) } - fn build_collapsed(&self, batch: Vec) -> Result, ShellError> { + fn build_collapsed(&mut self, batch: Vec) -> Result, ShellError> { if batch.is_empty() { return Ok(None); } - let color_hm = get_color_config(&self.config); - let theme = load_theme_from_config(&self.config); + let config = self.engine_state.get_config(); + let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack); + let theme = load_theme_from_config(config); let term_width = get_width_param(self.width_param); - let need_footer = matches!(self.config.footer_mode, FooterMode::RowCount(limit) if batch.len() as u64 > limit) - || matches!(self.config.footer_mode, FooterMode::Always); + let need_footer = matches!(config.footer_mode, FooterMode::RowCount(limit) if batch.len() as u64 > limit) + || matches!(config.footer_mode, FooterMode::Always); let value = Value::List { vals: batch, span: Span::new(0, 0), @@ -1495,8 +1626,8 @@ impl PagingTableCreator { value, true, term_width, - &self.config, - &color_hm, + config, + &style_computer, &theme, need_footer, ); @@ -1504,17 +1635,17 @@ impl PagingTableCreator { Ok(table.draw()) } - fn build_general(&self, batch: &[Value]) -> Result, ShellError> { + fn build_general(&mut self, batch: &[Value]) -> Result, ShellError> { let term_width = get_width_param(self.width_param); - let color_hm = get_color_config(&self.config); - + let config = &self.engine_state.get_config(); + let style_computer = StyleComputer::from_config(&self.engine_state, &self.stack); let table = convert_to_table( self.row_offset, batch, self.ctrlc.clone(), - &self.config, + config, self.head, - &color_hm, + &style_computer, )?; let (table, with_header, with_index) = match table { @@ -1523,8 +1654,8 @@ impl PagingTableCreator { }; let table_config = create_table_config( - &self.config, - &color_hm, + config, + &style_computer, table.count_rows(), with_header, with_index, @@ -1584,8 +1715,8 @@ impl Iterator for PagingTableCreator { match table { Ok(Some(table)) => { - let table = - strip_output_color(Some(table), &self.config).expect("must never happen"); + let table = strip_output_color(Some(table), self.engine_state.get_config()) + .expect("must never happen"); let mut bytes = table.as_bytes().to_vec(); bytes.push(b'\n'); // nu-table tables don't come with a newline on the end @@ -1686,7 +1817,7 @@ fn strip_output_color(output: Option, config: &Config) -> Option fn create_table_config( config: &Config, - color_hm: &HashMap, + style_computer: &StyleComputer, count_records: usize, with_header: bool, with_index: bool, @@ -1697,10 +1828,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(); @@ -1709,10 +1837,8 @@ fn create_table_config( table_cfg.trim(config.trim_strategy.clone()) } -fn lookup_separator_color( - color_hm: &HashMap, -) -> Option { - 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 { diff --git a/crates/nu-explore/src/commands/config_show.rs b/crates/nu-explore/src/commands/config_show.rs index e95a6c938..97b744297 100644 --- a/crates/nu-explore/src/commands/config_show.rs +++ b/crates/nu-explore/src/commands/config_show.rs @@ -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(), } diff --git a/crates/nu-explore/src/commands/expand.rs b/crates/nu-explore/src/commands/expand.rs index 8827c35e0..6a50fdc56 100644 --- a/crates/nu-explore/src/commands/expand.rs +++ b/crates/nu-explore/src/commands/expand.rs @@ -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, ) -> Result { 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) } } diff --git a/crates/nu-explore/src/commands/preview.rs b/crates/nu-explore/src/commands/preview.rs new file mode 100644 index 000000000..7be8215a7 --- /dev/null +++ b/crates/nu-explore/src/commands/preview.rs @@ -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 { + 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, + ) -> Result { + 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)) + } +} diff --git a/crates/nu-explore/src/nu_common/mod.rs b/crates/nu-explore/src/nu_common/mod.rs index b88041716..bdc382dc9 100644 --- a/crates/nu-explore/src/nu_common/mod.rs +++ b/crates/nu-explore/src/nu_common/mod.rs @@ -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>; -pub type NuStyleTable = HashMap; pub use command::{is_ignored_command, run_command_with_value, run_nu_command}; pub use string::truncate_str; diff --git a/crates/nu-explore/src/nu_common/table.rs b/crates/nu-explore/src/nu_common/table.rs index 3d3f06b80..56cb1dc50 100644 --- a/crates/nu-explore/src/nu_common/table.rs +++ b/crates/nu-explore/src/nu_common/table.rs @@ -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; -use crate::nu_common::{NuConfig, NuStyleTable}; +use crate::nu_common::NuConfig; pub fn try_build_table( ctrlc: Option>, 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, vals: Vec, span: Span, + style_computer: &StyleComputer, ctrlc: Option>, config: &NuConfig, - color_hm: &HashMap, ) -> 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>, config: &NuConfig, span: Span, - color_hm: &HashMap, + 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>, config: &Config, + style_computer: &StyleComputer, term_width: usize, expand_limit: Option, flatten: bool, flatten_sep: &str, ) -> Result, 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>, config: &Config, head: Span, - color_hm: &NuColorMap, + style_computer: &StyleComputer, deep: Option, 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 { .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>, - color_hm: &NuColorMap, + style_computer: &StyleComputer, deep: Option, 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, 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>, - 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, 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 { @@ -857,7 +889,7 @@ fn load_theme_from_config(config: &Config) -> TableTheme { fn create_table_config( config: &Config, - color_hm: &HashMap, + 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, -) -> Option { - 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 { diff --git a/crates/nu-explore/src/pager/mod.rs b/crates/nu-explore/src/pager/mod.rs index 7fc564fb6..1f7f08dcc 100644 --- a/crates/nu-explore/src/pager/mod.rs +++ b/crates/nu-explore/src/pager/mod.rs @@ -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, )); diff --git a/crates/nu-explore/src/views/information.rs b/crates/nu-explore/src/views/information.rs index e7c1bbb1e..014c55690 100644 --- a/crates/nu-explore/src/views/information.rs +++ b/crates/nu-explore/src/views/information.rs @@ -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::{ diff --git a/crates/nu-explore/src/views/mod.rs b/crates/nu-explore/src/views/mod.rs index 381a392e9..b87d24378 100644 --- a/crates/nu-explore/src/views/mod.rs +++ b/crates/nu-explore/src/views/mod.rs @@ -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, } } diff --git a/crates/nu-explore/src/views/preview.rs b/crates/nu-explore/src/views/preview.rs index 342a04749..cf0f76350 100644 --- a/crates/nu-explore/src/views/preview.rs +++ b/crates/nu-explore/src/views/preview.rs @@ -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::{ diff --git a/crates/nu-explore/src/views/record/mod.rs b/crates/nu-explore/src/views/record/mod.rs index 6b87f6706..c552b1bc9 100644 --- a/crates/nu-explore/src/views/record/mod.rs +++ b/crates/nu-explore/src/views/record/mod.rs @@ -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 { + // 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], cfg: &NuConfig, - color_hm: &NuStyleTable, + style_computer: &StyleComputer, ) -> Vec> { 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::>() }) diff --git a/crates/nu-explore/src/views/record/tablew.rs b/crates/nu-explore/src/views/record/tablew.rs index f3e1afd92..b44f84e8d 100644 --- a/crates/nu-explore/src/views/record/tablew.rs +++ b/crates/nu-explore/src/views/record/tablew.rs @@ -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>, data: impl Into]>>, - 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::>(); 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())), + ), ) } diff --git a/crates/nu-explore/src/views/util.rs b/crates/nu-explore/src/views/util.rs index c8fea1848..db92cab35 100644 --- a/crates/nu-explore/src/views/util.rs +++ b/crates/nu-explore/src/views/util.rs @@ -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) -> 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 { // vall will always be a f64 so convert it with precision formatting - match val.trim().parse::() { - Ok(f) => format!("{:.prec$}", f, prec = precision), - Err(err) => format!("error converting string [{}] to f64; {}", &val, err), - } + let val_float = match val.trim().parse::() { + 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)) } diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index f29e66f40..22ca7ad6d 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -712,7 +712,7 @@ impl Value { } } "trim" => { - match try_parse_trim_strategy(value, &config, &mut errors) { + match try_parse_trim_strategy(value, &mut errors) { Ok(v) => config.trim_strategy = v, Err(e) => { // try_parse_trim_strategy() already adds its own errors @@ -792,7 +792,7 @@ impl Value { } } "explore" => { - if let Ok(map) = create_map(value, &config) { + if let Ok(map) = create_map(value) { config.explore = map; } else { invalid!(vals[index].span().ok(), "should be a record"); @@ -802,7 +802,7 @@ impl Value { } // Misc. options "color_config" => { - if let Ok(map) = create_map(value, &config) { + if let Ok(map) = create_map(value) { config.color_config = map; } else { invalid!(vals[index].span().ok(), "should be a record"); @@ -1118,7 +1118,7 @@ impl Value { } "table_trim" => { legacy_options_used = true; - match try_parse_trim_strategy(value, &config, &mut errors) { + match try_parse_trim_strategy(value, &mut errors) { Ok(v) => config.trim_strategy = v, Err(e) => { // try_parse_trim_strategy() already calls eprintln!() on error @@ -1205,10 +1205,9 @@ Please consult https://www.nushell.sh/blog/2022-11-29-nushell-0.72.html for deta fn try_parse_trim_strategy( value: &Value, - config: &Config, errors: &mut Vec, ) -> Result { - let map = create_map(value, config).map_err(|e| { + let map = create_map(value).map_err(|e| { ShellError::GenericError( "Error while applying config changes".into(), "$env.config.table.trim is not a record".into(), @@ -1288,55 +1287,17 @@ fn try_parse_trim_methodology(value: &Value) -> Option { None } -fn create_map(value: &Value, config: &Config) -> Result, ShellError> { +fn create_map(value: &Value) -> Result, ShellError> { let (cols, inner_vals) = value.as_record()?; let mut hm: HashMap = HashMap::new(); for (k, v) in cols.iter().zip(inner_vals) { - match &v { - Value::Record { - cols: inner_cols, - vals: inner_vals, - span, - } => { - let val = color_value_string(span, inner_cols, inner_vals, config); - hm.insert(k.to_string(), val); - } - _ => { - hm.insert(k.to_string(), v.clone()); - } - } + hm.insert(k.to_string(), v.clone()); } Ok(hm) } -pub fn color_value_string( - span: &Span, - inner_cols: &[String], - inner_vals: &[Value], - config: &Config, -) -> Value { - // make a string from our config.color_config section that - // looks like this: { fg: "#rrggbb" bg: "#rrggbb" attr: "abc", } - // the real key here was to have quotes around the values but not - // require them around the keys. - - // maybe there's a better way to generate this but i'm not sure - // what it is. - let val: String = inner_cols - .iter() - .zip(inner_vals) - .map(|(x, y)| format!("{}: \"{}\" ", x, y.into_string(", ", config))) - .collect(); - - // now insert the braces at the front and the back to fake the json string - Value::String { - val: format!("{{{}}}", val), - span: *span, - } -} - // Parse the hooks to find the blocks to run when the hooks fire fn create_hooks(value: &Value) -> Result { match value { diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index abb6ea901..cf2d68365 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -20,7 +20,7 @@ static PWD_ENV: &str = "PWD"; // TODO: move to different file? where? /// An operation to be performed with the current buffer of the interactive shell. -#[derive(Clone)] +#[derive(Debug, Clone)] pub enum ReplOperation { Append(String), Insert(String), diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index bcf3850d6..847ae376c 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -11,6 +11,12 @@ version = "0.72.2" nu-ansi-term = "0.46.0" nu-protocol = { path = "../nu-protocol", version = "0.72.2" } nu-utils = { path = "../nu-utils", version = "0.72.2" } +nu-engine = { path = "../nu-engine", version = "0.72.2" } +nu-color-config = { path = "../nu-color-config", version = "0.72.2" } +nu-test-support = { path="../nu-test-support", version = "0.72.2" } +# nu-path is used only for test support +nu-path = { path="../nu-path", version = "0.72.2" } +atty = "0.2.14" tabled = { version = "0.10.0", features = ["color"], default-features = false } json_to_table = { version = "0.3.1", features = ["color"] } serde_json = "1" diff --git a/crates/nu-table/examples/table_demo.rs b/crates/nu-table/examples/table_demo.rs index bc5671f6e..a420eb6e0 100644 --- a/crates/nu-table/examples/table_demo.rs +++ b/crates/nu-table/examples/table_demo.rs @@ -1,4 +1,5 @@ -use nu_table::{Table, TableConfig, TableTheme, TextStyle}; +use nu_color_config::TextStyle; +use nu_table::{Table, TableConfig, TableTheme}; use tabled::papergrid::records::{cell_info::CellInfo, tcell::TCell}; fn main() { diff --git a/crates/nu-table/src/lib.rs b/crates/nu-table/src/lib.rs index 82a8f5139..5022ddb5b 100644 --- a/crates/nu-table/src/lib.rs +++ b/crates/nu-table/src/lib.rs @@ -1,11 +1,10 @@ mod nu_protocol_table; mod table; mod table_theme; -mod textstyle; mod util; +pub use nu_color_config::TextStyle; pub use nu_protocol_table::NuTable; pub use table::{Alignments, Table, TableConfig}; pub use table_theme::TableTheme; -pub use textstyle::{Alignment, TextStyle}; pub use util::*; diff --git a/crates/nu-table/src/nu_protocol_table.rs b/crates/nu-table/src/nu_protocol_table.rs index 688150864..0619b3751 100644 --- a/crates/nu-table/src/nu_protocol_table.rs +++ b/crates/nu-table/src/nu_protocol_table.rs @@ -1,16 +1,16 @@ use std::collections::HashMap; +use crate::{table::TrimStrategyModifier, Alignments, TableTheme}; +use nu_color_config::StyleComputer; use nu_protocol::{Config, Span, Value}; use tabled::{ color::Color, formatting::AlignmentStrategy, object::Segment, papergrid::records::Records, Alignment, Modify, Table, }; -use crate::{table::TrimStrategyModifier, Alignments, TableTheme}; - -/// NuTable has a recursive table representation of nu_prorocol::Value. +/// NuTable has a recursive table representation of nu_protocol::Value. /// -/// It doesn't support alignement and a proper width controll. +/// It doesn't support alignement and a proper width control. pub struct NuTable { inner: tabled::Table, } @@ -21,12 +21,12 @@ impl NuTable { collapse: bool, termwidth: usize, config: &Config, - color_hm: &HashMap, + style_computer: &StyleComputer, theme: &TableTheme, with_footer: bool, ) -> Self { let mut table = tabled::Table::new([""]); - load_theme(&mut table, color_hm, theme); + load_theme(&mut table, style_computer, theme); let cfg = table.get_config().clone(); let val = nu_protocol_value_to_json(value, config, with_footer); @@ -200,11 +200,9 @@ fn connect_maps(map: &mut serde_json::Map, value: ser } } -fn load_theme( - table: &mut tabled::Table, - color_hm: &HashMap, - theme: &TableTheme, -) where +// +fn load_theme(table: &mut tabled::Table, style_computer: &StyleComputer, theme: &TableTheme) +where R: Records, { let mut theme = theme.theme.clone(); @@ -212,11 +210,11 @@ fn load_theme( table.with(theme); - if let Some(color) = color_hm.get("separator") { - let color = color.paint(" ").to_string(); - if let Ok(color) = Color::try_from(color) { - table.with(color); - } + // color_config closures for "separator" are just given a null. + let color = style_computer.compute("separator", &Value::nothing(Span::unknown())); + let color = color.paint(" ").to_string(); + if let Ok(color) = Color::try_from(color) { + table.with(color); } table.with( diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index e57da5c56..d97195508 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -1,7 +1,8 @@ -use std::{cmp::min, collections::HashMap, fmt::Display}; - +use crate::table_theme::TableTheme; use nu_ansi_term::Style; +use nu_color_config::TextStyle; use nu_protocol::TrimStrategy; +use std::{cmp::min, collections::HashMap}; use tabled::{ alignment::AlignmentHorizontal, builder::Builder, @@ -9,7 +10,6 @@ use tabled::{ formatting::AlignmentStrategy, object::{Cell, Columns, Rows, Segment}, papergrid::{ - self, records::{ cell_info::CellInfo, tcell::TCell, vec_records::VecRecords, Records, RecordsMut, }, @@ -21,8 +21,6 @@ use tabled::{ Alignment, Modify, ModifyObject, TableOption, Width, }; -use crate::{table_theme::TableTheme, TextStyle}; - /// Table represent a table view. #[derive(Debug, Clone)] pub struct Table { @@ -577,26 +575,6 @@ fn truncate_columns_by_columns(data: &mut Data, theme: &TableTheme, termwidth: u false } -impl papergrid::Color for TextStyle { - fn fmt_prefix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(color) = &self.color_style { - color.prefix().fmt(f)?; - } - - Ok(()) - } - - fn fmt_suffix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(color) = &self.color_style { - if !color.is_plain() { - f.write_str("\u{1b}[0m")?; - } - } - - Ok(()) - } -} - /// The same as [`tabled::peaker::PriorityMax`] but prioritizes left columns first in case of equal width. #[derive(Debug, Default, Clone)] pub struct PriorityMax; diff --git a/crates/nu-table/tests/constrains.rs b/crates/nu-table/tests/constrains.rs index 944514c39..975d8b836 100644 --- a/crates/nu-table/tests/constrains.rs +++ b/crates/nu-table/tests/constrains.rs @@ -1,10 +1,9 @@ mod common; +use common::{create_row, styled_str, test_table, TestCase, VecCells}; use nu_protocol::TrimStrategy; use nu_table::{Table, TableConfig, TableTheme as theme}; -use common::{create_row, styled_str, test_table, TestCase, VecCells}; - #[test] fn data_and_header_has_different_size() { let table = Table::new(vec![create_row(3), create_row(5), create_row(5)], (3, 5)); diff --git a/crates/nu-table/tests/expand.rs b/crates/nu-table/tests/expand.rs index 5b1a69be8..168d38e47 100644 --- a/crates/nu-table/tests/expand.rs +++ b/crates/nu-table/tests/expand.rs @@ -1,6 +1,7 @@ mod common; use common::{create_row, create_table}; + use nu_table::{TableConfig, TableTheme as theme}; #[test] diff --git a/crates/nu-table/tests/style.rs b/crates/nu-table/tests/style.rs index 1226b653b..7fd3ab0bd 100644 --- a/crates/nu-table/tests/style.rs +++ b/crates/nu-table/tests/style.rs @@ -1,8 +1,7 @@ mod common; -use nu_table::{TableConfig, TableTheme as theme}; - use common::{create_row as row, VecCells}; +use nu_table::{TableConfig, TableTheme as theme}; #[test] fn test_rounded() { diff --git a/crates/nu-utils/src/lib.rs b/crates/nu-utils/src/lib.rs index e51852d17..fae1a42c9 100644 --- a/crates/nu-utils/src/lib.rs +++ b/crates/nu-utils/src/lib.rs @@ -3,7 +3,6 @@ mod deansi; pub mod locale; pub mod utils; -pub use ctrl_c::was_pressed; pub use locale::get_system_locale; pub use utils::{ enable_vt_processing, get_default_config, get_default_env, get_ls_colors, diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index 89bc0d25c..1fbaa55e5 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -127,7 +127,7 @@ module completions { # Get just the extern definitions without the custom completion commands use completions * -# for more information on themes see +# For more information on themes, see # https://www.nushell.sh/book/coloring_and_theming.html let dark_theme = { # color for nushell primitives @@ -135,11 +135,35 @@ let dark_theme = { leading_trailing_space_bg: { attr: n } # no fg, no bg, attr none effectively turns this off header: green_bold empty: blue - bool: white + # Closures can be used to choose colors for specific values. + # The value (in this case, a bool) is piped into the closure. + bool: { if $in { 'light_cyan' } else { 'light_gray' } } int: white - filesize: white + filesize: {|e| + if $e == 0b { + 'white' + } else if $e < 1mb { + 'cyan' + } else { 'blue' } + } duration: white - date: white + date: { (date now) - $in | + if $in < 1hr { + '#e61919' + } else if $in < 6hr { + '#e68019' + } else if $in < 1day { + '#e5e619' + } else if $in < 3day { + '#80e619' + } else if $in < 1wk { + '#19e619' + } else if $in < 6wk { + '#19e5e6' + } else if $in < 52wk { + '#197fe6' + } else { 'light_gray' } + } range: white float: white string: white @@ -192,11 +216,35 @@ let light_theme = { leading_trailing_space_bg: { attr: n } # no fg, no bg, attr none effectively turns this off header: green_bold empty: blue - bool: dark_gray + # Closures can be used to choose colors for specific values. + # The value (in this case, a bool) is piped into the closure. + bool: { if $in { 'dark_cyan' } else { 'dark_gray' } } int: dark_gray - filesize: dark_gray + filesize: {|e| + if $e == 0b { + 'dark_gray' + } else if $e < 1mb { + 'cyan_bold' + } else { 'blue_bold' } + } duration: dark_gray - date: dark_gray + date: { (date now) - $in | + if $in < 1hr { + '#e61919' + } else if $in < 6hr { + '#e68019' + } else if $in < 1day { + '#e5e619' + } else if $in < 3day { + '#80e619' + } else if $in < 1wk { + '#19e619' + } else if $in < 6wk { + '#19e5e6' + } else if $in < 52wk { + '#197fe6' + } else { 'dark_gray' } + } range: dark_gray float: dark_gray string: dark_gray