diff --git a/crates/nu-color-config/src/nu_style.rs b/crates/nu-color-config/src/nu_style.rs index 3d9f2b447b..7d2b3d6859 100644 --- a/crates/nu-color-config/src/nu_style.rs +++ b/crates/nu-color-config/src/nu_style.rs @@ -2,13 +2,13 @@ use nu_ansi_term::{Color, Style}; use serde::Deserialize; #[derive(Deserialize, PartialEq, Debug)] -struct NuStyle { - fg: Option, - bg: Option, - attr: Option, +pub struct NuStyle { + pub fg: Option, + pub bg: Option, + pub attr: Option, } -fn parse_nustyle(nu_style: NuStyle) -> Style { +pub fn parse_nustyle(nu_style: NuStyle) -> Style { // get the nu_ansi_term::Color foreground color let fg_color = match nu_style.fg { Some(fg) => color_from_hex(&fg).expect("error with foreground color"), diff --git a/crates/nu-command/src/platform/ansi/command.rs b/crates/nu-command/src/platform/ansi/command.rs index 7b180eae4c..4bf298f9ca 100644 --- a/crates/nu-command/src/platform/ansi/command.rs +++ b/crates/nu-command/src/platform/ansi/command.rs @@ -193,7 +193,7 @@ impl Command for AnsiCommand { Signature::build("ansi") .optional( "code", - SyntaxShape::String, + SyntaxShape::Any, "the name of the code to use like 'green' or 'reset' to reset the color", ) .switch( @@ -222,17 +222,17 @@ Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg There can be multiple text formatting sequence numbers separated by a ; and ending with an m where the # is of the following values: - attributes - 0 reset / normal display - 1 bold or increased intensity - 2 faint or decreased intensity - 3 italic on (non-mono font) - 4 underline on - 5 slow blink on - 6 fast blink on - 7 reverse video on - 8 nondisplayed (invisible) on - 9 strike-through on + attribute_number, abbreviation, description + 0 reset / normal display + 1 b bold or increased intensity + 2 d faint or decreased intensity + 3 i italic on (non-mono font) + 4 u underline on + 5 l slow blink on + 6 fast blink on + 7 r reverse video on + 8 h nondisplayed (invisible) on + 9 s strike-through on foreground/bright colors background/bright colors 30/90 black 40/100 black @@ -273,17 +273,23 @@ Format: # Example { description: "Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)", - example: r#"echo [(ansi rb) Hello " " (ansi gb) Nu " " (ansi pb) World] | str collect"#, + example: r#"echo [(ansi rb) Hello " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str collect"#, result: Some(Value::test_string( - "\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld", + "\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld\u{1b}[0m", )), }, Example { - description: - "Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)", - example: r#"echo [(ansi -e '3;93;41m') Hello (ansi reset) " " (ansi gb) Nu " " (ansi pb) World] | str collect"#, + description: "Use ansi to color text (italic bright yellow on red 'Hello' with green bold 'Nu' and purble bold 'World')", + example: r#"echo [(ansi -e '3;93;41m') Hello (ansi reset) " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str collect"#, result: Some(Value::test_string( - "\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld", + "\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld\u{1b}[0m", + )), + }, + Example { + description: "Use ansi to color text with a style (blue on red in bold)", + example: r#"$"(ansi -e { fg: '#0000ff' bg: '#ff0000' attr: b })Hello Nu World(ansi reset)""#, + result: Some(Value::test_string( + "\u{1b}[1;48;2;255;0;0;38;2;0;0;255mHello Nu World\u{1b}[0m", )), }, ] @@ -299,15 +305,21 @@ Format: # let list: bool = call.has_flag("list"); let escape: bool = call.has_flag("escape"); let osc: bool = call.has_flag("osc"); + if list { return generate_ansi_code_list(engine_state, call.head); } - let code: String = match call.opt::(engine_state, stack, 0)? { - Some(x) => x, - None => { - return Err(ShellError::MissingParameter("code".into(), call.head)); - } + + // The code can now be one of the ansi abbreviations like green_bold + // or it can be a record like this: { fg: "#ff0000" bg: "#00ff00" attr: bli } + // this record is defined in nu-color-config crate + let code: Value = match call.opt(engine_state, stack, 0)? { + Some(c) => c, + None => return Err(ShellError::MissingParameter("code".into(), call.head)), }; + + let param_is_string = matches!(code, Value::String { val: _, span: _ }); + if escape && osc { return Err(ShellError::IncompatibleParameters { left_message: "escape".into(), @@ -322,8 +334,17 @@ Format: # .span, }); } - if escape || osc { - let code_vec: Vec = code.chars().collect(); + + let code_string = if param_is_string { + code.as_string().expect("error getting code as string") + } else { + "".to_string() + }; + + let param_is_valid_string = param_is_string && !code_string.is_empty(); + + if (escape || osc) && (param_is_valid_string) { + let code_vec: Vec = code_string.chars().collect(); if code_vec[0] == '\\' { return Err(ShellError::UnsupportedInput( String::from("no need for escape characters"), @@ -333,14 +354,15 @@ Format: # )); } } - let output = if escape { - format!("\x1b[{}", code) - } else if osc { - //Operating system command aka osc ESC ] <- note the right brace, not left brace for osc + + let output = if escape && param_is_valid_string { + format!("\x1b[{}", code_string) + } else if osc && param_is_valid_string { + // Operating system command aka osc ESC ] <- note the right brace, not left brace for osc // OCS's need to end with a bell '\x07' char - format!("\x1b]{};", code) - } else { - match str_to_ansi(&code) { + format!("\x1b]{};", code_string) + } else if param_is_valid_string { + match str_to_ansi(&code_string) { Some(c) => c, None => { return Err(ShellError::UnsupportedInput( @@ -349,7 +371,36 @@ Format: # )) } } + } else { + // This is a record that should look like + // { fg: "#ff0000" bg: "#00ff00" attr: bli } + let record = code.as_record()?; + // create a NuStyle to parse the information into + let mut nu_style = nu_color_config::NuStyle { + fg: None, + bg: None, + attr: None, + }; + // Iterate and populate NuStyle with real values + for (k, v) in record.0.iter().zip(record.1) { + match k.as_str() { + "fg" => nu_style.fg = Some(v.as_string()?), + "bg" => nu_style.bg = Some(v.as_string()?), + "attr" => nu_style.attr = Some(v.as_string()?), + _ => { + return Err(ShellError::IncompatibleParametersSingle( + format!("problem with key: {}", k.to_string()), + code.span().expect("error with span"), + )) + } + } + } + // Now create a nu_ansi_term::Style from the NuStyle + let style = nu_color_config::parse_nustyle(nu_style); + // Return the prefix string. The prefix is the Ansi String. The suffix would be 0m, reset/stop coloring. + style.prefix().to_string() }; + Ok(Value::string(output, call.head).into_pipeline_data()) } }