summaryrefslogtreecommitdiffstats
path: root/src/style.rs
diff options
context:
space:
mode:
authorDan Davison <dandavison7@gmail.com>2020-05-27 15:26:08 -0400
committerDan Davison <dandavison7@gmail.com>2020-05-27 15:58:47 -0400
commit284392c7580e082070e0b9c05d712da65d52c943 (patch)
tree798df4207d09aa1d600345c9a455e579d9364ddc /src/style.rs
parent327792f4bc67d05d03785fe127df9990d2ffce37 (diff)
Refactor: Create color and theme modules, consolidate style code
Diffstat (limited to 'src/style.rs')
-rw-r--r--src/style.rs406
1 files changed, 308 insertions, 98 deletions
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<ansi_term::Color>,
+ background_default: Option<ansi_term::Color>,
+ 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<ansi_term::Color>,
+ background_default: Option<ansi_term::Color>,
+ 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<ansi_term::Style> {
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<u8> {
- 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<Self> {
+ 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<DecorationStyle>,
+ special_attribute: &str,
+ true_color: bool,
+ ) -> Option<DecorationStyle> {
+ 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<ansi_term::Color>,
+ background_default: Option<ansi_term::Color>,
+ 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<String>) {
+ 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
+ )
+ );
+ }
+}