diff options
author | Dan Davison <dandavison7@gmail.com> | 2020-05-17 18:19:57 -0400 |
---|---|---|
committer | Dan Davison <dandavison7@gmail.com> | 2020-05-22 13:57:52 -0400 |
commit | f08effc46de52655c8431498c6bc214c0eda30e5 (patch) | |
tree | 4fd806d5199b42f3ce77c87050795f59408b1a24 /src | |
parent | 54da480bd0465daf698f2566c454e0921110d69c (diff) |
Introduce style strings to replace color options
https://git-scm.com/docs/git-config#Documentation/git-config.txt-color
- Support "syntax" pseudo foreground color
- Delete the --syntax-highlight CLI option
This was never released.
Diffstat (limited to 'src')
-rw-r--r-- | src/cli.rs | 202 | ||||
-rw-r--r-- | src/config.rs | 109 | ||||
-rw-r--r-- | src/paint.rs | 11 | ||||
-rw-r--r-- | src/style.rs | 12 | ||||
-rw-r--r-- | src/tests/integration_test_utils.rs | 17 | ||||
-rw-r--r-- | src/tests/test_hunk_highlighting.rs | 60 |
6 files changed, 231 insertions, 180 deletions
@@ -2,7 +2,6 @@ use std::process; use std::str::FromStr; use std::string::ToString; -use bit_set::BitSet; use console::Term; use structopt::clap::AppSettings::{ColorAlways, ColoredHelp, DeriveDisplayOrder}; use structopt::StructOpt; @@ -10,7 +9,6 @@ use structopt::StructOpt; use crate::bat::assets::HighlightingAssets; use crate::bat::output::PagingMode; use crate::config; -use crate::delta::State; use crate::env; use crate::style; @@ -73,37 +71,37 @@ pub struct Opt { #[structopt(long = "dark")] pub dark: bool, + #[structopt(long = "minus-style")] + /// The style for removed lines. + pub minus_style: Option<String>, + + #[structopt(long = "minus-emph-style")] + /// The style for emphasized sections of removed lines. + pub minus_emph_style: Option<String>, + + #[structopt(long = "plus-style")] + /// The style for removed lines. + pub plus_style: Option<String>, + + #[structopt(long = "plus-emph-style")] + /// The style for emphasized sections of removed lines. + pub plus_emph_style: Option<String>, + #[structopt(long = "minus-color")] /// The background color for removed lines. - pub minus_color: Option<String>, + pub _deprecated_minus_color: Option<String>, #[structopt(long = "minus-emph-color")] /// The background color for emphasized sections of removed lines. - pub minus_emph_color: Option<String>, - - #[structopt(long = "minus-foreground-color")] - /// The foreground color for removed lines. - pub minus_foreground_color: Option<String>, - - #[structopt(long = "minus-emph-foreground-color")] - /// The foreground color for emphasized sections of removed lines. - pub minus_emph_foreground_color: Option<String>, + pub _deprecated_minus_emph_color: Option<String>, #[structopt(long = "plus-color")] /// The background color for added lines. - pub plus_color: Option<String>, + pub _deprecated_plus_color: Option<String>, #[structopt(long = "plus-emph-color")] /// The background color for emphasized sections of added lines. - pub plus_emph_color: Option<String>, - - #[structopt(long = "plus-foreground-color")] - /// Disable syntax highlighting and instead use this foreground color for added lines. - pub plus_foreground_color: Option<String>, - - #[structopt(long = "plus-emph-foreground-color")] - /// Disable syntax highlighting and instead use this foreground color for emphasized sections of added lines. - pub plus_emph_foreground_color: Option<String>, + pub _deprecated_plus_emph_color: Option<String>, #[structopt(long = "theme", env = "BAT_THEME")] /// The code syntax highlighting theme to use. Use --theme=none to disable syntax highlighting. @@ -113,14 +111,8 @@ pub struct Opt { /// --file-color, --hunk-color to configure the colors of other parts of the diff output. pub theme: Option<String>, - /// A string consisting only of the characters '-', '0', '+', specifying - /// which of the 3 diff hunk line-types (removed, unchanged, added) should - /// be syntax-highlighted. "all" and "none" are also valid values. - #[structopt(long = "syntax-highlight", default_value = "0+")] - pub lines_to_be_syntax_highlighted: String, - #[structopt(long = "highlight-removed")] - /// DEPRECATED: use --syntax-highlight. + /// DEPRECATED: supply 'syntax' as the foreground color in --minus-style. pub highlight_minus_lines: bool, #[structopt(long = "color-only")] @@ -249,41 +241,14 @@ impl ToString for Error { } } -pub fn process_command_line_arguments<'a>(opt: Opt) -> config::Config<'a> { +pub fn process_command_line_arguments<'a>(mut opt: Opt) -> config::Config<'a> { let assets = HighlightingAssets::new(); - if opt.light && opt.dark { - eprintln!("--light and --dark cannot be used together."); - process::exit(1); - } - match &opt.theme { - Some(theme) if !style::is_no_syntax_highlighting_theme_name(&theme) => { - if !assets.theme_set.themes.contains_key(theme.as_str()) { - eprintln!("Invalid theme: '{}'", theme); - process::exit(1); - } - let is_light_theme = style::is_light_theme(&theme); - if is_light_theme && opt.dark { - eprintln!( - "{} is a light theme, but you supplied --dark. \ - If you use --theme, you do not need to supply --light or --dark.", - theme - ); - process::exit(1); - } else if !is_light_theme && opt.light { - eprintln!( - "{} is a dark theme, but you supplied --light. \ - If you use --theme, you do not need to supply --light or --dark.", - theme - ); - process::exit(1); - } - } - _ => (), - }; + _check_validity(&opt, &assets); - // We do not use the full width, in case `less --status-column` is in effect. See #41 and #10. + _apply_rewrite_rules(&mut opt); + // We do not use the full width, in case `less --status-column` is in effect. See #41 and #10. // TODO: There seems to be some confusion in the accounting: we are actually leaving 2 // characters unused for less at the right edge of the terminal, despite the subtraction of 1 // here. @@ -295,7 +260,7 @@ pub fn process_command_line_arguments<'a>(opt: Opt) -> config::Config<'a> { "auto" => PagingMode::QuitIfOneScreen, _ => { eprintln!( - "Invalid paging value: {} (valid values are \"always\", \"never\", and \"auto\")", + "Invalid value for --paging option: {} (valid values are \"always\", \"never\", and \"auto\")", opt.paging_mode ); process::exit(1); @@ -315,8 +280,6 @@ pub fn process_command_line_arguments<'a>(opt: Opt) -> config::Config<'a> { } }; - let lines_to_be_syntax_highlighted = get_lines_to_be_syntax_highlighted(&opt); - config::get_config( opt, assets.syntax_set, @@ -324,52 +287,89 @@ pub fn process_command_line_arguments<'a>(opt: Opt) -> config::Config<'a> { true_color, available_terminal_width, paging_mode, - lines_to_be_syntax_highlighted, ) } -fn is_truecolor_terminal() -> bool { - env::get_env_var("COLORTERM") - .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit") - .unwrap_or(false) -} - -fn get_lines_to_be_syntax_highlighted(opt: &Opt) -> BitSet { - if opt.highlight_minus_lines { - eprintln!("--highlight-removed is deprecated: use --syntax-highlight."); - } - - let syntax_highlight_lines = match opt.lines_to_be_syntax_highlighted.to_lowercase().as_ref() { - "none" => "", - // This is the default value of the new option: honor the deprecated option if it has been used. - "0+" => match opt.highlight_minus_lines { - true => "-0+", - false => "0+", - }, - "all" => "-0+", - s => s, +fn _check_validity(opt: &Opt, assets: &HighlightingAssets) { + if opt.light && opt.dark { + eprintln!("--light and --dark cannot be used together."); + process::exit(1); } - .to_string(); - - let mut lines_to_be_syntax_highlighted = BitSet::new(); - for line_type in syntax_highlight_lines.chars() { - lines_to_be_syntax_highlighted.insert(match line_type { - '-' => State::HunkMinus as usize, - '0' => State::HunkZero as usize, - '+' => State::HunkPlus as usize, - s => { - eprintln!("Invalid --syntax-highlight value: {}. Valid characters are \"-\", \"0\", \"+\".", s); + if let Some(ref theme) = opt.theme { + if !style::is_no_syntax_highlighting_theme_name(&theme) { + if !assets.theme_set.themes.contains_key(theme.as_str()) { + eprintln!("Invalid theme: '{}'", theme); process::exit(1); } - }); - } - if opt.minus_foreground_color.is_some() || opt.minus_emph_foreground_color.is_some() { - lines_to_be_syntax_highlighted.remove(State::HunkMinus as usize); + let is_light_theme = style::is_light_theme(&theme); + if is_light_theme && opt.dark { + eprintln!( + "{} is a light theme, but you supplied --dark. \ + If you use --theme, you do not need to supply --light or --dark.", + theme + ); + process::exit(1); + } else if !is_light_theme && opt.light { + eprintln!( + "{} is a dark theme, but you supplied --light. \ + If you use --theme, you do not need to supply --light or --dark.", + theme + ); + process::exit(1); + } + } } - if opt.plus_foreground_color.is_some() || opt.plus_emph_foreground_color.is_some() { - lines_to_be_syntax_highlighted.remove(State::HunkPlus as usize); +} + +fn _apply_rewrite_rules(opt: &mut Opt) { + opt.minus_style = _make_style_string( + opt.minus_style.as_deref(), + opt._deprecated_minus_color.as_deref(), + "minus", + ); + opt.minus_emph_style = _make_style_string( + opt.minus_emph_style.as_deref(), + opt._deprecated_minus_emph_color.as_deref(), + "minus-emph", + ); + opt.plus_style = _make_style_string( + opt.plus_style.as_deref(), + opt._deprecated_plus_color.as_deref(), + "plus", + ); + opt.plus_emph_style = _make_style_string( + opt.plus_emph_style.as_deref(), + opt._deprecated_plus_emph_color.as_deref(), + "plus-emph", + ); +} + +pub fn _make_style_string( + style: Option<&str>, + background_color: Option<&str>, + element_name: &str, +) -> Option<String> { + match (style, background_color) { + (_, None) => style.map(str::to_string), + (None, Some(background_color)) => Some(format!("syntax {}", background_color)), + (Some(_), Some(_)) => { + eprintln!( + "--{name}-color cannot be used with --{name}-style. \ + Use --{name}-style=\"fg bg attr1 attr2 ...\" to set \ + foreground color, background color, and style attributes. \ + --{name}-color can only be used to set the background color. \ + (It is still available for backwards-compatibility.)", + name = element_name, + ); + process::exit(1); + } } - lines_to_be_syntax_highlighted +} + +fn is_truecolor_terminal() -> bool { + env::get_env_var("COLORTERM") + .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit") + .unwrap_or(false) } #[cfg(test)] diff --git a/src/config.rs b/src/config.rs index a98ee11c..773d9a61 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,8 +1,7 @@ use std::process; use std::str::FromStr; -use bit_set::BitSet; -use syntect::highlighting::{Color, Style, StyleModifier, Theme, ThemeSet}; +use syntect::highlighting::{Color, FontStyle, Style, StyleModifier, Theme, ThemeSet}; use syntect::parsing::SyntaxSet; use crate::bat::output::PagingMode; @@ -24,7 +23,6 @@ pub struct Config<'a> { pub plus_emph_style_modifier: StyleModifier, pub minus_line_marker: &'a str, pub plus_line_marker: &'a str, - pub lines_to_be_syntax_highlighted: BitSet, pub commit_style: cli::SectionStyle, pub commit_color: Color, pub file_style: cli::SectionStyle, @@ -50,25 +48,29 @@ use ColorLayer::*; use State::*; impl<'a> Config<'a> { - #[allow(dead_code)] - pub fn get_color(&self, state: &State, layer: ColorLayer) -> Option<Color> { - let modifier = match state { + pub fn get_style(&self, state: &State) -> Option<StyleModifier> { + match state { HunkMinus => Some(self.minus_style_modifier), HunkZero => None, HunkPlus => Some(self.plus_style_modifier), _ => panic!("Invalid"), - }; - match (modifier, layer) { - (Some(modifier), Background) => modifier.background, - (Some(modifier), Foreground) => modifier.foreground, - (None, _) => None, } } #[allow(dead_code)] + pub fn get_color(&self, state: &State, layer: ColorLayer) -> Option<Color> { + match (self.get_style(state), layer) { + (Some(style), Background) => style.background, + (Some(style), Foreground) => style.foreground, + (None, _) => None, + } + } + pub fn should_syntax_highlight(&self, state: &State) -> bool { - self.lines_to_be_syntax_highlighted - .contains((*state).clone() as usize) + match self.get_style(state) { + Some(style) => style.foreground == Some(style::SYNTAX_HIGHLIGHTING_COLOR), + None => false, + } } } @@ -79,7 +81,6 @@ pub fn get_config<'a>( true_color: bool, terminal_width: usize, paging_mode: PagingMode, - lines_to_be_syntax_highlighted: BitSet, ) -> Config<'a> { // Implement --color-only let keep_plus_minus_markers = if opt.color_only { @@ -139,7 +140,6 @@ pub fn get_config<'a>( minus_emph_style_modifier, plus_style_modifier, plus_emph_style_modifier, - lines_to_be_syntax_highlighted, minus_line_marker, plus_line_marker, commit_style, @@ -220,52 +220,98 @@ fn make_styles<'a>( true_color: bool, ) -> (StyleModifier, StyleModifier, StyleModifier, StyleModifier) { let minus_style = make_style( - opt.minus_color.as_deref(), + opt.minus_style.as_deref(), Some(style::get_minus_color_default(is_light_mode, true_color)), - opt.minus_foreground_color.as_deref(), None, ); let minus_emph_style = make_style( - opt.minus_emph_color.as_deref(), + opt.minus_emph_style.as_deref(), Some(style::get_minus_emph_color_default( is_light_mode, true_color, )), - opt.minus_emph_foreground_color.as_deref(), minus_style.foreground, ); let plus_style = make_style( - opt.plus_color.as_deref(), + opt.plus_style.as_deref(), Some(style::get_plus_color_default(is_light_mode, true_color)), - opt.plus_foreground_color.as_deref(), None, ); let plus_emph_style = make_style( - opt.plus_emph_color.as_deref(), + opt.plus_emph_style.as_deref(), Some(style::get_plus_emph_color_default( is_light_mode, true_color, )), - opt.plus_emph_foreground_color.as_deref(), plus_style.foreground, ); (minus_style, minus_emph_style, plus_style, plus_emph_style) } +/// Construct syntect StyleModifier from background and foreground strings, +/// together with their defaults. The background string is handled specially in +/// that it may be a single color, or it may be a space-separated "style string". fn make_style( - background: Option<&str>, + style_string: Option<&str>, background_default: Option<Color>, - foreground: Option<&str>, foreground_default: Option<Color>, ) -> StyleModifier { + if let Some(s) = style_string { + parse_style_string(s, background_default, foreground_default) + } else { + StyleModifier { + background: background_default, + foreground: foreground_default, + font_style: None, + } + } +} + +fn parse_style_string( + style_string: &str, + background_default: Option<Color>, + foreground_default: Option<Color>, +) -> StyleModifier { + let mut foreground = foreground_default; + let mut background = background_default; + let mut font_style = FontStyle::empty(); + let mut seen_foreground = false; + let mut seen_background = false; + for s in style_string.to_lowercase().split_whitespace() { + if s == "bold" { + font_style.set(FontStyle::BOLD, true) + } else if s == "italic" { + font_style.set(FontStyle::ITALIC, true) + } else if s == "underline" { + font_style.set(FontStyle::UNDERLINE, true) + } else if !seen_foreground { + foreground = color_from_rgb_or_ansi_code_with_default(Some(s), None); + seen_foreground = true; + } else if !seen_background { + background = color_from_rgb_or_ansi_code_with_default(Some(s), None); + seen_background = true; + } else { + eprintln!( + "Invalid style string: {}.\n\ + A style string may contain a foreground color string. \ + If it contains a foreground color string it may subsequently \ + contain a background color string. Font style attributes \ + 'bold', 'italic', and 'underline' may occur in any position. \ + All strings must be separated by spaces. \ + See delta --help for how to specify colors.", + s + ); + process::exit(1); + } + } StyleModifier { - background: color_from_rgb_or_ansi_code_with_default(background, background_default), - foreground: color_from_rgb_or_ansi_code_with_default(foreground, foreground_default), - font_style: None, + background, + foreground, + font_style: Some(font_style), } } @@ -289,9 +335,10 @@ fn color_from_rgb_or_ansi_code_with_default( arg: Option<&str>, default: Option<Color>, ) -> Option<Color> { - match arg { - Some(string) if string.to_lowercase() == "none" => None, - Some(string) => Some(color_from_rgb_or_ansi_code(&string)), + match arg.map(str::to_lowercase) { + Some(s) if s == "none" => None, + Some(s) if s == "syntax" => Some(style::SYNTAX_HIGHLIGHTING_COLOR), + Some(s) => Some(color_from_rgb_or_ansi_code(&s)), None => default, } } diff --git a/src/paint.rs b/src/paint.rs index 2b9969eb..f3ebf9de 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use ansi_term; use syntect::easy::HighlightLines; -use syntect::highlighting::{Color, Style, StyleModifier}; +use syntect::highlighting::{Color, FontStyle, Style, StyleModifier}; use syntect::parsing::{SyntaxReference, SyntaxSet}; use crate::bat::terminal::to_ansi_color; @@ -223,6 +223,15 @@ pub fn to_ansi_style(style: Style, true_color: bool) -> ansi_term::Style { if style.foreground != style::NO_COLOR { ansi_style = ansi_style.fg(to_ansi_color(style.foreground, true_color)); } + if style.font_style.contains(FontStyle::BOLD) { + ansi_style.is_bold = true; + } + if style.font_style.contains(FontStyle::ITALIC) { + ansi_style.is_italic = true; + } + if style.font_style.contains(FontStyle::UNDERLINE) { + ansi_style.is_underline = true; + } ansi_style } diff --git a/src/style.rs b/src/style.rs index 80256805..339e3f02 100644 --- a/src/style.rs +++ b/src/style.rs @@ -170,6 +170,18 @@ const DARK_THEME_PLUS_EMPH_COLOR_256: Color = Color { /// A special color to specify that no color escape codes should be emitted. pub const NO_COLOR: Color = Color::BLACK; +/// A special color value to signify that the foreground color of a style should be derived from +/// syntax highlighting. +// alpha is 0, which is how the 256-palette colors are encoded (see bat::terminal::to_ansi_color). +// So if painted, this would be black. However, the presence of a non-zero bit in the blue channel +// distinguishes this from any 256-palette color. +pub const SYNTAX_HIGHLIGHTING_COLOR: Color = Color { + r: 0x00, + g: 0x00, + b: 0x01, + a: 0x00, +}; + pub fn get_no_style() -> Style { Style { foreground: NO_COLOR, diff --git a/src/tests/integration_test_utils.rs b/src/tests/integration_test_utils.rs index 6836d85d..cc4dc22b 100644 --- a/src/tests/integration_test_utils.rs +++ b/src/tests/integration_test_utils.rs @@ -12,18 +12,17 @@ pub mod integration_test_utils { cli::Opt { light: false, dark: false, - minus_color: None, - minus_emph_color: None, - minus_foreground_color: None, - minus_emph_foreground_color: None, - plus_color: None, - plus_emph_color: None, - plus_foreground_color: None, - plus_emph_foreground_color: None, + minus_style: None, + minus_emph_style: None, + plus_style: None, + plus_emph_style: None, + _deprecated_minus_color: None, + _deprecated_minus_emph_color: None, + _deprecated_plus_color: None, + _deprecated_plus_emph_color: None, color_only: false, keep_plus_minus_markers: false, theme: None, - lines_to_be_syntax_highlighted: "0+".to_string(), highlight_minus_lines: false, commit_style: cli::SectionStyle::Plain, commit_color: "Yellow".to_string(), diff --git a/src/tests/test_hunk_highlighting.rs b/src/tests/test_hunk_highlighting.rs index 730218c0..8c2bbe35 100644 --- a/src/tests/test_hunk_highlighting.rs +++ b/src/tests/test_hunk_highlighting.rs @@ -16,45 +16,29 @@ mod tests { let mut options = integration_test_utils::get_command_line_options(); options.theme = Some("GitHub".to_string()); options.max_line_distance = 1.0; - options.minus_emph_color = Some("#ffa0a0".to_string()); - options.plus_emph_color = Some("#80ef80".to_string()); - for minus_foreground_color in vec![None, Some("green".to_string())] { - options.minus_foreground_color = minus_foreground_color; - for minus_emph_foreground_color in vec![None, Some("#80ef80".to_string())] { - options.minus_emph_foreground_color = minus_emph_foreground_color; - for plus_foreground_color in vec![None, Some("red".to_string())] { - options.plus_foreground_color = plus_foreground_color; - for plus_emph_foreground_color in vec![None, Some("#ffa0a0".to_string())] { - options.plus_emph_foreground_color = plus_emph_foreground_color; - for lines_to_be_syntax_highlighted in vec!["none", "all"] { - options.lines_to_be_syntax_highlighted = - lines_to_be_syntax_highlighted.to_string(); - if VERBOSE { - println!(); - print!( - " --syntax-highlight {:?}", - options.lines_to_be_syntax_highlighted - ); - print!( - " --minus-foreground-color {:?}", - options.minus_foreground_color - ); - print!( - " --minus-emph-foreground-color {:?}", - options.minus_emph_foreground_color - ); - print!( - " --plus-foreground-color {:?}", - options.plus_foreground_color - ); - print!( - " --plus-emph-foreground-color {:?}", - options.plus_emph_foreground_color - ); - println!(); - } - _do_hunk_color_test(options.clone()); + let minus_emph_background = "#ffa0a0"; + let plus_emph_background = "#80ef80"; + for minus_foreground in vec!["none", "green"] { + for minus_emph_foreground in vec!["none", "#80ef80"] { + for plus_foreground in vec!["none", "red"] { + for plus_emph_foreground in vec!["none", "#ffa0a0"] { + options.minus_style = Some(minus_foreground.to_string()); + options.minus_emph_style = Some(format!( + "{} {}", + minus_emph_foreground, minus_emph_background + )); + options.plus_style = Some(plus_foreground.to_string()); + options.plus_emph_style = + Some(format!("{} {}", plus_emph_foreground, plus_emph_background)); + if VERBOSE { + println!(); + print!(" --minus-style {:?}", options.minus_style); + print!(" --minus-emph-style {:?}", options.minus_emph_style); + print!(" --plus-style {:?}", options.plus_style); + print!(" --plus-emph-style {:?}", options.plus_emph_style); + println!(); } + _do_hunk_color_test(options.clone()); } } } |