use crate::{color_record_to_nustyle, lookup_ansi_color_style, text_style::Alignment, TextStyle};
use nu_ansi_term::{Color, Style};
use nu_engine::ClosureEvalOnce;
use nu_protocol::{
engine::{Closure, EngineState, Stack},
report_shell_error, Span, Value,
};
use std::{
collections::HashMap,
fmt::{Debug, Formatter, Result},
};
#[derive(Debug, Clone)]
pub enum ComputableStyle {
Static(Style),
Closure(Closure, Span),
}
pub type StyleMapping = HashMap<String, ComputableStyle>;
pub struct StyleComputer<'a> {
engine_state: &'a EngineState,
stack: &'a Stack,
map: StyleMapping,
}
impl<'a> StyleComputer<'a> {
pub fn new(
engine_state: &'a EngineState,
stack: &'a Stack,
map: StyleMapping,
) -> StyleComputer<'a> {
StyleComputer {
engine_state,
stack,
map,
}
}
pub fn compute(&self, style_name: &str, value: &Value) -> Style {
match self.map.get(style_name) {
Some(ComputableStyle::Static(s)) => *s,
Some(ComputableStyle::Closure(closure, span)) => {
let result = ClosureEvalOnce::new(self.engine_state, self.stack, closure.clone())
.debug(false)
.run_with_value(value.clone())
.and_then(|data| data.into_value(*span));
match result {
Ok(value) => {
match value {
Value::Record { .. } => color_record_to_nustyle(&value),
Value::String { val, .. } => lookup_ansi_color_style(&val),
_ => Style::default(),
}
}
Err(err) => {
report_shell_error(self.engine_state, &err);
Style::default()
}
}
}
_ => Style::default(),
}
}
pub fn style_primitive(&self, value: &Value) -> TextStyle {
use Alignment::*;
let s = self.compute(&value.get_type().get_non_specified_string(), value);
match *value {
Value::Bool { .. } => TextStyle::with_style(Left, s),
Value::Int { .. } => TextStyle::with_style(Right, s),
Value::Filesize { .. } => TextStyle::with_style(Right, s),
Value::Duration { .. } => TextStyle::with_style(Right, s),
Value::Date { .. } => TextStyle::with_style(Left, s),
Value::Range { .. } => TextStyle::with_style(Left, s),
Value::Float { .. } => TextStyle::with_style(Right, s),
Value::String { .. } => TextStyle::with_style(Left, s),
Value::Glob { .. } => TextStyle::with_style(Left, s),
Value::Nothing { .. } => TextStyle::with_style(Left, s),
Value::Binary { .. } => TextStyle::with_style(Left, s),
Value::CellPath { .. } => TextStyle::with_style(Left, s),
Value::Record { .. } | Value::List { .. } => TextStyle::with_style(Left, s),
Value::Closure { .. } | Value::Custom { .. } | Value::Error { .. } => {
TextStyle::basic_left()
}
}
}
pub fn from_config(engine_state: &'a EngineState, stack: &'a Stack) -> StyleComputer<'a> {
let config = stack.get_config(engine_state);
#[rustfmt::skip]
let mut map: StyleMapping = [
("separator".to_string(), ComputableStyle::Static(Color::White.normal())),
("leading_trailing_space_bg".to_string(), ComputableStyle::Static(Style::default().on(Color::Rgb(128, 128, 128)))),
("header".to_string(), ComputableStyle::Static(Color::Green.bold())),
("empty".to_string(), ComputableStyle::Static(Color::Blue.normal())),
("bool".to_string(), ComputableStyle::Static(Color::LightCyan.normal())),
("int".to_string(), ComputableStyle::Static(Color::White.normal())),
("filesize".to_string(), ComputableStyle::Static(Color::Cyan.normal())),
("duration".to_string(), ComputableStyle::Static(Color::White.normal())),
("date".to_string(), ComputableStyle::Static(Color::Purple.normal())),
("range".to_string(), ComputableStyle::Static(Color::White.normal())),
("float".to_string(), ComputableStyle::Static(Color::White.normal())),
("string".to_string(), ComputableStyle::Static(Color::White.normal())),
("nothing".to_string(), ComputableStyle::Static(Color::White.normal())),
("binary".to_string(), ComputableStyle::Static(Color::White.normal())),
("cell-path".to_string(), ComputableStyle::Static(Color::White.normal())),
("row_index".to_string(), ComputableStyle::Static(Color::Green.bold())),
("record".to_string(), ComputableStyle::Static(Color::White.normal())),
("list".to_string(), ComputableStyle::Static(Color::White.normal())),
("block".to_string(), ComputableStyle::Static(Color::White.normal())),
("hints".to_string(), ComputableStyle::Static(Color::DarkGray.normal())),
("search_result".to_string(), ComputableStyle::Static(Color::White.normal().on(Color::Red))),
].into_iter().collect();
for (key, value) in &config.color_config {
let span = value.span();
match value {
Value::Closure { val, .. } => {
map.insert(
key.to_string(),
ComputableStyle::Closure(*val.clone(), span),
);
}
Value::Record { .. } => {
map.insert(
key.to_string(),
ComputableStyle::Static(color_record_to_nustyle(value)),
);
}
Value::String { val, .. } => {
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));
}
}
_ => (),
}
}
StyleComputer::new(engine_state, stack, map)
}
}
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();
let dummy_engine_state = EngineState::new();
let dummy_stack = Stack::new();
let style_computer = StyleComputer::new(
&dummy_engine_state,
&dummy_stack,
[
("string".into(), ComputableStyle::Static(style1)),
("row_index".into(), ComputableStyle::Static(style2)),
]
.into_iter()
.collect(),
);
assert_eq!(
style_computer.compute("string", &Value::nothing(Span::unknown())),
style1
);
assert_eq!(
style_computer.compute("row_index", &Value::nothing(Span::unknown())),
style2
);
}
#[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#"$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, r#"["bell.obj", "book.obj", "candle.obj"]"#);
});
}
#[test]
fn test_computable_style_closure_errors() {
use nu_test_support::{nu, nu_repl_code};
let inp = [
r#"$env.config = {
color_config: {
string: {|e| $e + 2 }
}
};"#,
"[bell] | table",
];
let actual_repl = nu!(nu_repl_code(&inp));
assert!(actual_repl.err.contains("type mismatch for operator"));
assert!(actual_repl.out.contains("bell"));
}