use crate::text_style::Alignment;
use crate::{color_record_to_nustyle, lookup_ansi_color_style, TextStyle};
use nu_ansi_term::{Color, Style};
use nu_engine::{env::get_config, eval_block};
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
CliError, IntoPipelineData, Value,
};
use std::collections::HashMap;
use std::fmt::{Debug, Formatter, Result};
#[derive(Debug, Clone)]
pub enum ComputableStyle {
Static(Style),
Closure(Value),
}
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(v)) => {
let span = v.span();
match v {
Value::Closure { val, .. } => {
let block = self.engine_state.get_block(val.block_id).clone();
let mut stack = self.stack.captures_to_stack(val.captures.clone());
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, value.clone());
}
}
match eval_block(
self.engine_state,
&mut stack,
&block,
value.clone().into_pipeline_data(),
false,
false,
) {
Ok(v) => {
let value = v.into_value(span);
match value {
Value::Record { .. } => color_record_to_nustyle(&value),
Value::String { val, .. } => lookup_ansi_color_style(&val),
_ => Style::default(),
}
}
Err(e) => {
eprintln!(
"Error: {:?}",
CliError(&e, &StateWorkingSet::new(self.engine_state))
);
Style::default()
}
}
}
_ => 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 { .. } | Value::Block { .. } => {
TextStyle::with_style(Left, s)
}
Value::Closure { .. }
| Value::CustomValue { .. }
| Value::Error { .. }
| Value::LazyRecord { .. } => TextStyle::basic_left(),
}
}
pub fn from_config(engine_state: &'a EngineState, stack: &'a Stack) -> StyleComputer<'a> {
let config = get_config(engine_state, stack);
#[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 {
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, .. } => {
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, "[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"));
}