add ability to specify an ansi style (#595)

* add ability to specify an ansi style

* remove comments

* remove more debug code

* some cleanup and refactoring
This commit is contained in:
Darren Schroeder 2021-12-27 08:59:55 -06:00 committed by GitHub
parent 1dbf351425
commit 1837acfc70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 88 additions and 37 deletions

View File

@ -2,13 +2,13 @@ use nu_ansi_term::{Color, Style};
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize, PartialEq, Debug)] #[derive(Deserialize, PartialEq, Debug)]
struct NuStyle { pub struct NuStyle {
fg: Option<String>, pub fg: Option<String>,
bg: Option<String>, pub bg: Option<String>,
attr: Option<String>, pub attr: Option<String>,
} }
fn parse_nustyle(nu_style: NuStyle) -> Style { pub fn parse_nustyle(nu_style: NuStyle) -> Style {
// get the nu_ansi_term::Color foreground color // get the nu_ansi_term::Color foreground color
let fg_color = match nu_style.fg { let fg_color = match nu_style.fg {
Some(fg) => color_from_hex(&fg).expect("error with foreground color"), Some(fg) => color_from_hex(&fg).expect("error with foreground color"),

View File

@ -193,7 +193,7 @@ impl Command for AnsiCommand {
Signature::build("ansi") Signature::build("ansi")
.optional( .optional(
"code", "code",
SyntaxShape::String, SyntaxShape::Any,
"the name of the code to use like 'green' or 'reset' to reset the color", "the name of the code to use like 'green' or 'reset' to reset the color",
) )
.switch( .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 There can be multiple text formatting sequence numbers
separated by a ; and ending with an m where the # is of the separated by a ; and ending with an m where the # is of the
following values: following values:
attributes attribute_number, abbreviation, description
0 reset / normal display 0 reset / normal display
1 bold or increased intensity 1 b bold or increased intensity
2 faint or decreased intensity 2 d faint or decreased intensity
3 italic on (non-mono font) 3 i italic on (non-mono font)
4 underline on 4 u underline on
5 slow blink on 5 l slow blink on
6 fast blink on 6 fast blink on
7 reverse video on 7 r reverse video on
8 nondisplayed (invisible) on 8 h nondisplayed (invisible) on
9 strike-through on 9 s strike-through on
foreground/bright colors background/bright colors foreground/bright colors background/bright colors
30/90 black 40/100 black 30/90 black 40/100 black
@ -273,17 +273,23 @@ Format: #
Example { Example {
description: description:
"Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)", "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( 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 { Example {
description: description: "Use ansi to color text (italic bright yellow on red 'Hello' with green bold 'Nu' and purble bold 'World')",
"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 (ansi reset)] | str collect"#,
example: r#"echo [(ansi -e '3;93;41m') Hello (ansi reset) " " (ansi gb) Nu " " (ansi pb) World] | str collect"#,
result: Some(Value::test_string( 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 list: bool = call.has_flag("list");
let escape: bool = call.has_flag("escape"); let escape: bool = call.has_flag("escape");
let osc: bool = call.has_flag("osc"); let osc: bool = call.has_flag("osc");
if list { if list {
return generate_ansi_code_list(engine_state, call.head); return generate_ansi_code_list(engine_state, call.head);
} }
let code: String = match call.opt::<String>(engine_state, stack, 0)? {
Some(x) => x, // The code can now be one of the ansi abbreviations like green_bold
None => { // or it can be a record like this: { fg: "#ff0000" bg: "#00ff00" attr: bli }
return Err(ShellError::MissingParameter("code".into(), call.head)); // 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 { if escape && osc {
return Err(ShellError::IncompatibleParameters { return Err(ShellError::IncompatibleParameters {
left_message: "escape".into(), left_message: "escape".into(),
@ -322,8 +334,17 @@ Format: #
.span, .span,
}); });
} }
if escape || osc {
let code_vec: Vec<char> = 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<char> = code_string.chars().collect();
if code_vec[0] == '\\' { if code_vec[0] == '\\' {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
String::from("no need for escape characters"), String::from("no need for escape characters"),
@ -333,14 +354,15 @@ Format: #
)); ));
} }
} }
let output = if escape {
format!("\x1b[{}", code) let output = if escape && param_is_valid_string {
} else if osc { format!("\x1b[{}", code_string)
//Operating system command aka osc ESC ] <- note the right brace, not left brace for osc } 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 // OCS's need to end with a bell '\x07' char
format!("\x1b]{};", code) format!("\x1b]{};", code_string)
} else { } else if param_is_valid_string {
match str_to_ansi(&code) { match str_to_ansi(&code_string) {
Some(c) => c, Some(c) => c,
None => { None => {
return Err(ShellError::UnsupportedInput( 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()) Ok(Value::string(output, call.head).into_pipeline_data())
} }
} }