From 284392c7580e082070e0b9c05d712da65d52c943 Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Wed, 27 May 2020 15:26:08 -0400 Subject: Refactor: Create color and theme modules, consolidate style code --- src/style.rs | 406 ++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 308 insertions(+), 98 deletions(-) (limited to 'src/style.rs') diff --git a/src/style.rs b/src/style.rs index 41b04b4d..9de37cc9 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,4 +1,9 @@ -use ansi_term::{self, Color}; +use std::process; + +use ansi_term; + +use crate::cli::unreachable; +use crate::color; #[derive(Clone, Copy, Debug, PartialEq)] pub struct Style { @@ -25,6 +30,89 @@ impl Style { } } + /// Construct Style from style and decoration-style strings supplied on command line, together with + /// defaults. + pub fn from_str( + style_string: &str, + foreground_default: Option, + background_default: Option, + decoration_style_string: Option<&str>, + true_color: bool, + ) -> Self { + let (style_string, special_attribute_from_style_string) = + extract_special_decoration_attribute(style_string); + let (ansi_term_style, is_syntax_highlighted) = parse_ansi_term_style( + &style_string, + foreground_default, + background_default, + true_color, + ); + let decoration_style = match decoration_style_string { + Some(s) if s != "" => DecorationStyle::from_str(s, true_color), + _ => None, + }; + let mut style = Style { + ansi_term_style, + is_syntax_highlighted, + decoration_style, + }; + if let Some(special_attribute) = special_attribute_from_style_string { + if let Some(decoration_style) = DecorationStyle::apply_special_decoration_attribute( + style.decoration_style, + &special_attribute, + true_color, + ) { + style.decoration_style = Some(decoration_style) + } + } + style + } + + pub fn from_str_respecting_deprecated_foreground_color_arg( + style_string: &str, + foreground_default: Option, + background_default: Option, + decoration_style_string: Option<&str>, + deprecated_foreground_color_arg: Option<&str>, + true_color: bool, + ) -> Self { + let mut style = Self::from_str( + style_string, + foreground_default, + background_default, + decoration_style_string, + true_color, + ); + if let Some(s) = deprecated_foreground_color_arg { + // The deprecated --{commit,file,hunk}-color args functioned to set the decoration + // foreground color. In the case of file, it set the text foreground color also. + let foreground_from_deprecated_arg = parse_ansi_term_style(s, None, None, true_color) + .0 + .foreground; + style.ansi_term_style.foreground = foreground_from_deprecated_arg; + style.decoration_style = match style.decoration_style { + Some(DecorationStyle::Box(mut ansi_term_style)) => { + ansi_term_style.foreground = foreground_from_deprecated_arg; + Some(DecorationStyle::Box(ansi_term_style)) + } + Some(DecorationStyle::Underline(mut ansi_term_style)) => { + ansi_term_style.foreground = foreground_from_deprecated_arg; + Some(DecorationStyle::Underline(ansi_term_style)) + } + Some(DecorationStyle::Overline(mut ansi_term_style)) => { + ansi_term_style.foreground = foreground_from_deprecated_arg; + Some(DecorationStyle::Overline(ansi_term_style)) + } + Some(DecorationStyle::Underoverline(mut ansi_term_style)) => { + ansi_term_style.foreground = foreground_from_deprecated_arg; + Some(DecorationStyle::Underoverline(ansi_term_style)) + } + _ => style.decoration_style, + }; + } + style + } + pub fn decoration_ansi_term_style(&self) -> Option { match self.decoration_style { Some(DecorationStyle::Box(style)) => Some(style), @@ -36,115 +124,237 @@ impl Style { } } -// See -// https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit -pub fn ansi_color_name_to_number(name: &str) -> Option { - match name.to_lowercase().as_ref() { - "black" => Some(0), - "red" => Some(1), - "green" => Some(2), - "yellow" => Some(3), - "blue" => Some(4), - "magenta" => Some(5), - "purple" => Some(5), - "cyan" => Some(6), - "white" => Some(7), - "bright-black" => Some(8), - "bright-red" => Some(9), - "bright-green" => Some(10), - "bright-yellow" => Some(11), - "bright-blue" => Some(12), - "bright-magenta" => Some(13), - "bright-purple" => Some(13), - "bright-cyan" => Some(14), - "bright-white" => Some(15), - _ => None, +impl DecorationStyle { + pub fn from_str(style_string: &str, true_color: bool) -> Option { + let (style_string, special_attribute) = extract_special_decoration_attribute(&style_string); + let special_attribute = special_attribute.unwrap_or_else(|| { + eprintln!( + "Invalid decoration style: '{}'. To specify a decoration style, you must supply one of \ + the special attributes: 'box', 'underline', 'overline', 'underoverline', or 'omit'.", + style_string + ); + process::exit(1); + }); + let (style, is_syntax_highlighted): (ansi_term::Style, bool) = + parse_ansi_term_style(&style_string, None, None, true_color); + if is_syntax_highlighted { + eprintln!("'syntax' may not be used as a color name in a decoration style."); + process::exit(1); + }; + match special_attribute.as_ref() { + "box" => Some(DecorationStyle::Box(style)), + "underline" => Some(DecorationStyle::Underline(style)), + "overline" => Some(DecorationStyle::Overline(style)), + "underoverline" => Some(DecorationStyle::Underoverline(style)), + "omit" => Some(DecorationStyle::Omit), + "plain" => None, + _ => unreachable("Unreachable code path reached in parse_decoration_style."), + } } -} - -pub const LIGHT_THEMES: [&str; 5] = [ - "GitHub", - "Monokai Extended Light", - "OneHalfLight", - "ansi-light", - "Solarized (light)", -]; - -pub const DEFAULT_LIGHT_THEME: &str = "GitHub"; -pub const DEFAULT_DARK_THEME: &str = "Monokai Extended"; - -pub fn is_light_theme(theme: &str) -> bool { - LIGHT_THEMES.contains(&theme) -} -pub fn is_no_syntax_highlighting_theme_name(theme_name: &str) -> bool { - theme_name.to_lowercase() == "none" -} - -pub fn get_minus_background_color_default(is_light_mode: bool, is_true_color: bool) -> Color { - match (is_light_mode, is_true_color) { - (true, true) => LIGHT_THEME_MINUS_COLOR, - (true, false) => LIGHT_THEME_MINUS_COLOR_256, - (false, true) => DARK_THEME_MINUS_COLOR, - (false, false) => DARK_THEME_MINUS_COLOR_256, - } -} - -pub fn get_minus_emph_background_color_default(is_light_mode: bool, is_true_color: bool) -> Color { - match (is_light_mode, is_true_color) { - (true, true) => LIGHT_THEME_MINUS_EMPH_COLOR, - (true, false) => LIGHT_THEME_MINUS_EMPH_COLOR_256, - (false, true) => DARK_THEME_MINUS_EMPH_COLOR, - (false, false) => DARK_THEME_MINUS_EMPH_COLOR_256, + fn apply_special_decoration_attribute( + decoration_style: Option, + special_attribute: &str, + true_color: bool, + ) -> Option { + let ansi_term_style = match decoration_style { + None => ansi_term::Style::new(), + Some(DecorationStyle::Box(ansi_term_style)) => ansi_term_style, + Some(DecorationStyle::Underline(ansi_term_style)) => ansi_term_style, + Some(DecorationStyle::Overline(ansi_term_style)) => ansi_term_style, + Some(DecorationStyle::Underoverline(ansi_term_style)) => ansi_term_style, + Some(DecorationStyle::Omit) => ansi_term::Style::new(), + }; + match DecorationStyle::from_str(special_attribute, true_color) { + Some(DecorationStyle::Box(_)) => Some(DecorationStyle::Box(ansi_term_style)), + Some(DecorationStyle::Underline(_)) => { + Some(DecorationStyle::Underline(ansi_term_style)) + } + Some(DecorationStyle::Overline(_)) => Some(DecorationStyle::Overline(ansi_term_style)), + Some(DecorationStyle::Underoverline(_)) => { + Some(DecorationStyle::Underoverline(ansi_term_style)) + } + Some(DecorationStyle::Omit) => Some(DecorationStyle::Omit), + None => None, + } } } -pub fn get_plus_background_color_default(is_light_mode: bool, is_true_color: bool) -> Color { - match (is_light_mode, is_true_color) { - (true, true) => LIGHT_THEME_PLUS_COLOR, - (true, false) => LIGHT_THEME_PLUS_COLOR_256, - (false, true) => DARK_THEME_PLUS_COLOR, - (false, false) => DARK_THEME_PLUS_COLOR_256, +fn parse_ansi_term_style( + s: &str, + foreground_default: Option, + background_default: Option, + true_color: bool, +) -> (ansi_term::Style, bool) { + let mut style = ansi_term::Style::new(); + let mut seen_foreground = false; + let mut seen_background = false; + let mut is_syntax_highlighted = false; + for word in s + .to_lowercase() + .split_whitespace() + .map(|word| word.trim_matches(|c| c == '"' || c == '\'')) + { + if word == "blink" { + style.is_blink = true; + } else if word == "bold" { + style.is_bold = true; + } else if word == "dimmed" { + style.is_dimmed = true; + } else if word == "hidden" { + style.is_hidden = true; + } else if word == "italic" { + style.is_italic = true; + } else if word == "reverse" { + style.is_reverse = true; + } else if word == "strikethrough" { + style.is_strikethrough = true; + } else if !seen_foreground { + if word == "syntax" { + is_syntax_highlighted = true; + } else { + style.foreground = color::color_from_rgb_or_ansi_code_with_default( + word, + foreground_default, + true_color, + ); + } + seen_foreground = true; + } else if !seen_background { + if word == "syntax" { + eprintln!( + "You have used the special color 'syntax' as a background color \ + (second color in a style string). It may only be used as a foreground \ + color (first color in a style string)." + ); + process::exit(1); + } else { + style.background = color::color_from_rgb_or_ansi_code_with_default( + word, + background_default, + true_color, + ); + } + seen_background = true; + } else { + eprintln!( + "Invalid style string: {}. See the STYLES section of delta --help.", + s + ); + process::exit(1); + } } + (style, is_syntax_highlighted) } -pub fn get_plus_emph_background_color_default(is_light_mode: bool, is_true_color: bool) -> Color { - match (is_light_mode, is_true_color) { - (true, true) => LIGHT_THEME_PLUS_EMPH_COLOR, - (true, false) => LIGHT_THEME_PLUS_EMPH_COLOR_256, - (false, true) => DARK_THEME_PLUS_EMPH_COLOR, - (false, false) => DARK_THEME_PLUS_EMPH_COLOR_256, +/// If the style string contains a 'special decoration attribute' then extract it and return it +/// along with the modified style string. +fn extract_special_decoration_attribute(style_string: &str) -> (String, Option) { + let style_string = style_string.to_lowercase(); + let (special_attributes, standard_attributes): (Vec<&str>, Vec<&str>) = style_string + .split_whitespace() + .map(|word| word.trim_matches(|c| c == '"' || c == '\'')) + .partition(|&token| { + // TODO: This should be tied to the enum + token == "box" + || token == "underline" + || token == "overline" + || token == "underoverline" + || token == "omit" + || token == "plain" + }); + match special_attributes { + attrs if attrs.len() == 0 => (style_string.to_string(), None), + attrs if attrs.len() == 1 => (standard_attributes.join(" "), Some(attrs[0].to_string())), + attrs => { + eprintln!( + "Encountered multiple special attributes: {:?}. \ + You may supply no more than one of the special attributes 'box', 'underline', \ + and 'omit'.", + attrs.join(", ") + ); + process::exit(1); + } } } -const LIGHT_THEME_MINUS_COLOR: Color = Color::RGB(0xff, 0xe0, 0xe0); - -const LIGHT_THEME_MINUS_COLOR_256: Color = Color::Fixed(224); - -const LIGHT_THEME_MINUS_EMPH_COLOR: Color = Color::RGB(0xff, 0xc0, 0xc0); - -const LIGHT_THEME_MINUS_EMPH_COLOR_256: Color = Color::Fixed(217); - -const LIGHT_THEME_PLUS_COLOR: Color = Color::RGB(0xd0, 0xff, 0xd0); - -const LIGHT_THEME_PLUS_COLOR_256: Color = Color::Fixed(194); +#[cfg(test)] +mod tests { + use super::*; -const LIGHT_THEME_PLUS_EMPH_COLOR: Color = Color::RGB(0xa0, 0xef, 0xa0); + use ansi_term; -const LIGHT_THEME_PLUS_EMPH_COLOR_256: Color = Color::Fixed(157); + use crate::color::ansi_color_name_to_number; -const DARK_THEME_MINUS_COLOR: Color = Color::RGB(0x3f, 0x00, 0x01); - -const DARK_THEME_MINUS_COLOR_256: Color = Color::Fixed(52); - -const DARK_THEME_MINUS_EMPH_COLOR: Color = Color::RGB(0x90, 0x10, 0x11); - -const DARK_THEME_MINUS_EMPH_COLOR_256: Color = Color::Fixed(124); - -const DARK_THEME_PLUS_COLOR: Color = Color::RGB(0x00, 0x28, 0x00); - -const DARK_THEME_PLUS_COLOR_256: Color = Color::Fixed(22); - -const DARK_THEME_PLUS_EMPH_COLOR: Color = Color::RGB(0x00, 0x60, 0x00); + #[test] + fn test_parse_ansi_term_style() { + assert_eq!( + parse_ansi_term_style("", None, None, false), + (ansi_term::Style::new(), false) + ); + assert_eq!( + parse_ansi_term_style("red", None, None, false), + ( + ansi_term::Style { + foreground: Some(ansi_term::Color::Fixed( + ansi_color_name_to_number("red").unwrap() + )), + ..ansi_term::Style::new() + }, + false + ) + ); + assert_eq!( + parse_ansi_term_style("red green", None, None, false), + ( + ansi_term::Style { + foreground: Some(ansi_term::Color::Fixed( + ansi_color_name_to_number("red").unwrap() + )), + background: Some(ansi_term::Color::Fixed( + ansi_color_name_to_number("green").unwrap() + )), + ..ansi_term::Style::new() + }, + false + ) + ); + } -const DARK_THEME_PLUS_EMPH_COLOR_256: Color = Color::Fixed(28); + #[test] + fn test_parse_ansi_term_style_with_special_syntax_color() { + assert_eq!( + parse_ansi_term_style("syntax", None, None, false), + (ansi_term::Style::new(), true) + ); + assert_eq!( + parse_ansi_term_style("syntax italic white hidden", None, None, false), + ( + ansi_term::Style { + background: Some(ansi_term::Color::Fixed( + ansi_color_name_to_number("white").unwrap() + )), + is_italic: true, + is_hidden: true, + ..ansi_term::Style::new() + }, + true + ) + ); + assert_eq!( + parse_ansi_term_style("bold syntax italic white hidden", None, None, false), + ( + ansi_term::Style { + background: Some(ansi_term::Color::Fixed( + ansi_color_name_to_number("white").unwrap() + )), + is_bold: true, + is_italic: true, + is_hidden: true, + ..ansi_term::Style::new() + }, + true + ) + ); + } +} -- cgit v1.2.3