diff options
author | Dan Davison <dandavison7@gmail.com> | 2020-02-27 14:26:59 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-27 14:26:59 -0600 |
commit | cc8ffaa9b4fd1fc91ccfd73b4da258ed4a433ec8 (patch) | |
tree | dac68c21e9df071052d30d75e86c30ab01b5d6cb | |
parent | 73edbf7bc0d9dc27d24b86d379c920c967a102e5 (diff) | |
parent | 3a73fff4341734b58922b1f6d7747ae98a4b28c4 (diff) |
Merge pull request #107 from dandavison/99-103-color-options
Provide CLI options for all colors
-rw-r--r-- | src/cli.rs | 78 | ||||
-rw-r--r-- | src/config.rs | 38 | ||||
-rw-r--r-- | src/delta.rs | 22 | ||||
-rw-r--r-- | src/draw.rs | 48 | ||||
-rw-r--r-- | src/paint.rs | 57 |
5 files changed, 184 insertions, 59 deletions
@@ -10,38 +10,74 @@ use crate::config; use crate::style; #[derive(StructOpt, Clone, Debug)] -#[structopt(name = "delta", about = "A syntax-highlighting pager for git")] +#[structopt( + name = "delta", + about = "A syntax-highlighter for git and diff output", + after_help = "\ +Colors +------ + +All delta color options work the same way. There are two ways to specify a color: + +1. RGB hex code + + An example of passing an RGB hex code is: + --file-color=\"#0e7c0e\" + +2. ANSI color name + + There are 8 ANSI color names: + black, red, green, yellow, blue, magenta, cyan, white. + + In addition, all of them have a bright form: + bright-black, bright-red, bright-green, bright-yellow, bright-blue, bright-magenta, bright-cyan, bright-white + + An example is: + --file-color=\"green\" + + Unlike RGB hex codes, ANSI color names are just names: you can choose the exact color that each + name corresponds to in the settings of your terminal application (the application you use to run + command line programs). This means that if you use ANSI color names, and you change the color + theme used by your terminal, then delta's colors will respond automatically, without needing to + change the delta command line. + + \"purple\" is accepted as a synonym for \"magenta\". Color names and codes are case-insensitive. +" +)] pub struct Opt { - /// Use colors appropriate for a light terminal background. For - /// more control, see --theme, --plus-color, and --minus-color. + /// Use default colors appropriate for a light terminal background. For more control, see the other + /// color options. #[structopt(long = "light")] pub light: bool, - /// Use colors appropriate for a dark terminal background. For - /// more control, see --theme, --plus-color, and --minus-color. + /// Use default colors appropriate for a dark terminal background. For more control, see the + /// other color options. #[structopt(long = "dark")] pub dark: bool, #[structopt(long = "minus-color")] - /// The background color (RGB hex) to use for removed lines. + /// The background color to use for removed lines. pub minus_color: Option<String>, #[structopt(long = "minus-emph-color")] - /// The background color (RGB hex) to use for emphasized sections of removed lines. + /// The background color to use for emphasized sections of removed lines. pub minus_emph_color: Option<String>, #[structopt(long = "plus-color")] - /// The background color (RGB hex) to use for added lines. + /// The background color to use for added lines. pub plus_color: Option<String>, #[structopt(long = "plus-emph-color")] - /// The background color (RGB hex) to use for emphasized sections of added lines. + /// The background color to use for emphasized sections of added lines. pub plus_emph_color: Option<String>, #[structopt(long = "theme", env = "BAT_THEME")] - /// The syntax highlighting theme to use. Use --theme=none to disable syntax highlighting. - /// If the theme is not set using this option, it will be taken from the BAT_THEME environment variable, - /// if that contains a valid theme name. Use --list-themes and --compare-themes to view available themes. + /// The code syntax highlighting theme to use. Use --theme=none to disable syntax highlighting. + /// If the theme is not set using this option, it will be taken from the BAT_THEME environment + /// variable, if that contains a valid theme name. Use --list-themes and --compare-themes to + /// view available themes. Note that the choice of theme only affects code syntax highlighting. + /// See --commit-color, --file-color, --hunk-color to configure the colors of other parts of + /// the diff output. pub theme: Option<String>, #[structopt(long = "highlight-removed")] @@ -50,20 +86,32 @@ pub struct Opt { pub highlight_removed: bool, #[structopt(long = "commit-style", default_value = "plain")] - /// Formatting style for commit section of git output. Options + /// Formatting style for the commit section of git output. Options /// are: plain, box. pub commit_style: SectionStyle, + #[structopt(long = "commit-color", default_value = "yellow")] + /// Color for the commit section of git output. + pub commit_color: String, + #[structopt(long = "file-style", default_value = "underline")] - /// Formatting style for file section of git output. Options + /// Formatting style for the file section of git output. Options /// are: plain, box, underline. pub file_style: SectionStyle, + #[structopt(long = "file-color", default_value = "blue")] + /// Color for the file section of git output. + pub file_color: String, + #[structopt(long = "hunk-style", default_value = "box")] - /// Formatting style for hunk section of git output. Options + /// Formatting style for the hunk-marker section of git output. Options /// are: plain, box. pub hunk_style: SectionStyle, + #[structopt(long = "hunk-color", default_value = "blue")] + /// Color for the hunk-marker section of git output. + pub hunk_color: String, + /// The width (in characters) of the background color /// highlighting. By default, the width is the current terminal /// width. Use --width=variable to apply background colors to the diff --git a/src/config.rs b/src/config.rs index 500c5e2e..2135efe8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use std::process; use std::str::FromStr; use syntect::highlighting::{Color, Style, StyleModifier, Theme, ThemeSet}; @@ -5,6 +6,7 @@ use syntect::parsing::SyntaxSet; use crate::cli; use crate::env; +use crate::paint; use crate::style; pub struct Config<'a> { @@ -14,6 +16,9 @@ pub struct Config<'a> { pub minus_emph_style_modifier: StyleModifier, pub plus_style_modifier: StyleModifier, pub plus_emph_style_modifier: StyleModifier, + pub commit_color: Color, + pub file_color: Color, + pub hunk_color: Color, pub syntax_set: &'a SyntaxSet, pub terminal_width: usize, pub width: Option<usize>, @@ -45,7 +50,7 @@ pub fn get_config<'a>( }; let minus_style_modifier = StyleModifier { - background: Some(color_from_arg( + background: Some(color_from_arg_or_mode_default( opt.minus_color.as_ref(), is_light_mode, style::LIGHT_THEME_MINUS_COLOR, @@ -60,7 +65,7 @@ pub fn get_config<'a>( }; let minus_emph_style_modifier = StyleModifier { - background: Some(color_from_arg( + background: Some(color_from_arg_or_mode_default( opt.minus_emph_color.as_ref(), is_light_mode, style::LIGHT_THEME_MINUS_EMPH_COLOR, @@ -75,7 +80,7 @@ pub fn get_config<'a>( }; let plus_style_modifier = StyleModifier { - background: Some(color_from_arg( + background: Some(color_from_arg_or_mode_default( opt.plus_color.as_ref(), is_light_mode, style::LIGHT_THEME_PLUS_COLOR, @@ -86,7 +91,7 @@ pub fn get_config<'a>( }; let plus_emph_style_modifier = StyleModifier { - background: Some(color_from_arg( + background: Some(color_from_arg_or_mode_default( opt.plus_emph_color.as_ref(), is_light_mode, style::LIGHT_THEME_PLUS_EMPH_COLOR, @@ -103,6 +108,9 @@ pub fn get_config<'a>( minus_emph_style_modifier, plus_style_modifier, plus_emph_style_modifier, + commit_color: color_from_rgb_or_ansi_code(&opt.commit_color), + file_color: color_from_rgb_or_ansi_code(&opt.file_color), + hunk_color: color_from_rgb_or_ansi_code(&opt.hunk_color), terminal_width, width, tab_width: opt.tab_width, @@ -168,18 +176,32 @@ fn valid_theme_name_or_none(theme_name: Option<&String>, theme_set: &ThemeSet) - } } -fn color_from_arg( +fn color_from_rgb_or_ansi_code(s: &str) -> Color { + let die = || { + eprintln!("Invalid color: {}", s); + process::exit(1); + }; + if s.starts_with("#") { + Color::from_str(s).unwrap_or_else(|_| die()) + } else { + paint::color_from_ansi_name(s).unwrap_or_else(die) + } +} + +fn color_from_arg_or_mode_default( arg: Option<&String>, is_light_mode: bool, light_theme_default: Color, dark_theme_default: Color, ) -> Color { - arg.and_then(|s| Color::from_str(s).ok()) - .unwrap_or_else(|| { + match arg { + Some(string) => color_from_rgb_or_ansi_code(&string), + None => { if is_light_mode { light_theme_default } else { dark_theme_default } - }) + } + } } diff --git a/src/delta.rs b/src/delta.rs index 7f8aff5a..9c6d16f9 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -1,6 +1,5 @@ use std::io::Write; -use ansi_term::Colour::{Blue, Yellow}; use console::strip_ansi_codes; use unicode_segmentation::UnicodeSegmentation; @@ -8,7 +7,7 @@ use crate::bat::assets::HighlightingAssets; use crate::cli; use crate::config::Config; use crate::draw; -use crate::paint::Painter; +use crate::paint::{self, Painter}; use crate::parse; use crate::style; @@ -203,7 +202,7 @@ fn handle_commit_meta_header_line( painter.writer, line, config.terminal_width, - Yellow.normal(), + config.commit_color, true, )?; Ok(()) @@ -232,13 +231,12 @@ fn handle_generic_file_meta_header_line( cli::SectionStyle::Underline => draw::write_underlined, cli::SectionStyle::Plain => panic!(), }; - let ansi_style = Blue.normal(); writeln!(painter.writer)?; draw_fn( painter.writer, - &ansi_style.paint(line), + &paint::paint_text_foreground(line, config.file_color), config.terminal_width, - ansi_style, + config.file_color, false, )?; Ok(()) @@ -254,7 +252,6 @@ fn handle_hunk_meta_line( cli::SectionStyle::Underline => draw::write_underlined, cli::SectionStyle::Plain => panic!(), }; - let ansi_style = Blue.normal(); let (raw_code_fragment, line_number) = parse::parse_hunk_metadata(&line); let code_fragment = prepare(raw_code_fragment, config.tab_width, false); if !code_fragment.is_empty() { @@ -280,12 +277,16 @@ fn handle_hunk_meta_line( painter.writer, &painter.output_buffer, config.terminal_width, - ansi_style, + config.hunk_color, false, )?; painter.output_buffer.clear(); } - writeln!(painter.writer, "\n{}", ansi_style.paint(line_number))?; + writeln!( + painter.writer, + "\n{}", + paint::paint_text_foreground(line_number, config.hunk_color) + )?; Ok(()) } @@ -617,8 +618,11 @@ mod tests { theme: None, highlight_removed: false, commit_style: cli::SectionStyle::Plain, + commit_color: "Yellow".to_string(), file_style: cli::SectionStyle::Underline, + file_color: "Blue".to_string(), hunk_style: cli::SectionStyle::Box, + hunk_color: "blue".to_string(), width: Some("variable".to_string()), tab_width: 4, show_background_colors: false, diff --git a/src/draw.rs b/src/draw.rs index 9487c557..751f99f9 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,17 +1,19 @@ use std::io::Write; -use ansi_term::Style; use box_drawing; use console::strip_ansi_codes; +use syntect::highlighting::Color; use unicode_segmentation::UnicodeSegmentation; +use crate::paint; + /// Write text to stream, surrounded by a box, leaving the cursor just /// beyond the bottom right corner. pub fn write_boxed( writer: &mut dyn Write, text: &str, _line_width: usize, // ignored - line_style: Style, + color: Color, heavy: bool, ) -> std::io::Result<()> { let up_left = if heavy { @@ -20,8 +22,8 @@ pub fn write_boxed( box_drawing::light::UP_LEFT }; let box_width = strip_ansi_codes(text).graphemes(true).count() + 1; - write_boxed_partial(writer, text, box_width, line_style, heavy)?; - write!(writer, "{}", line_style.paint(up_left))?; + write_boxed_partial(writer, text, box_width, color, heavy)?; + write!(writer, "{}", paint::paint_text_foreground(up_left, color))?; Ok(()) } @@ -31,11 +33,11 @@ pub fn write_boxed_with_line( writer: &mut dyn Write, text: &str, line_width: usize, - line_style: Style, + color: Color, heavy: bool, ) -> std::io::Result<()> { let box_width = strip_ansi_codes(text).graphemes(true).count() + 1; - write_boxed_with_horizontal_whisker(writer, text, box_width, line_style, heavy)?; + write_boxed_with_horizontal_whisker(writer, text, box_width, color, heavy)?; write_horizontal_line( writer, if line_width > box_width { @@ -43,7 +45,7 @@ pub fn write_boxed_with_line( } else { 0 }, - line_style, + color, heavy, )?; write!(writer, "\n")?; @@ -54,11 +56,11 @@ pub fn write_underlined( writer: &mut dyn Write, text: &str, line_width: usize, - line_style: Style, + color: Color, heavy: bool, ) -> std::io::Result<()> { - writeln!(writer, "{}", line_style.paint(text))?; - write_horizontal_line(writer, line_width - 1, line_style, heavy)?; + writeln!(writer, "{}", paint::paint_text_foreground(text, color))?; + write_horizontal_line(writer, line_width - 1, color, heavy)?; write!(writer, "\n")?; Ok(()) } @@ -66,7 +68,7 @@ pub fn write_underlined( fn write_horizontal_line( writer: &mut dyn Write, line_width: usize, - line_style: Style, + color: Color, heavy: bool, ) -> std::io::Result<()> { let horizontal = if heavy { @@ -77,7 +79,7 @@ fn write_horizontal_line( write!( writer, "{}", - line_style.paint(horizontal.repeat(line_width),) + paint::paint_text_foreground(&horizontal.repeat(line_width), color) ) } @@ -85,7 +87,7 @@ pub fn write_boxed_with_horizontal_whisker( writer: &mut dyn Write, text: &str, box_width: usize, - line_style: Style, + color: Color, heavy: bool, ) -> std::io::Result<()> { let up_horizontal = if heavy { @@ -93,8 +95,12 @@ pub fn write_boxed_with_horizontal_whisker( } else { box_drawing::light::UP_HORIZONTAL }; - write_boxed_partial(writer, text, box_width, line_style, heavy)?; - write!(writer, "{}", line_style.paint(up_horizontal))?; + write_boxed_partial(writer, text, box_width, color, heavy)?; + write!( + writer, + "{}", + paint::paint_text_foreground(up_horizontal, color) + )?; Ok(()) } @@ -102,7 +108,7 @@ fn write_boxed_partial( writer: &mut dyn Write, text: &str, box_width: usize, - line_style: Style, + color: Color, heavy: bool, ) -> std::io::Result<()> { let horizontal = if heavy { @@ -125,10 +131,10 @@ fn write_boxed_partial( write!( writer, "{}{}\n{} {}\n{}", - line_style.paint(&horizontal_edge), - line_style.paint(down_left), - line_style.paint(text), - line_style.paint(vertical), - line_style.paint(&horizontal_edge), + paint::paint_text_foreground(&horizontal_edge, color), + paint::paint_text_foreground(down_left, color), + paint::paint_text_foreground(text, color), + paint::paint_text_foreground(vertical, color), + paint::paint_text_foreground(&horizontal_edge, color), ) } diff --git a/src/paint.rs b/src/paint.rs index 5aec3a15..cf6968e1 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -1,4 +1,5 @@ use std::io::Write; +use std::str::FromStr; use syntect::easy::HighlightLines; use syntect::highlighting::{Color, Style, StyleModifier}; @@ -204,23 +205,31 @@ impl<'a> Painter<'a> { } } -/// Write section text to buffer with color escape codes. +/// Write section text to buffer with shell escape codes specifying foreground and background color. pub fn paint_text(text: &str, style: Style, output_buffer: &mut String) { if text.is_empty() { return; } if style.background != style::NO_COLOR { - output_buffer.push_str(&get_color_code(style.background, false)); + output_buffer.push_str(&get_color_escape_sequence(style.background, false)); } if style.foreground != style::NO_COLOR { - output_buffer.push_str(&get_color_code(style.foreground, true)); + output_buffer.push_str(&get_color_escape_sequence(style.foreground, true)); } output_buffer.push_str(text); } -/// ANSI color escape code. -// See https://github.com/ogham/rust-ansi-term/blob/ff7eba98d55ad609c7fcc8c7bb0859b37c7545cc/src/ansi.rs#L82-L112 -fn get_color_code(color: Color, foreground: bool) -> String { +/// Return text together with shell escape codes specifying the foreground color. +pub fn paint_text_foreground(text: &str, color: Color) -> String { + format!("{}{}", get_color_escape_sequence(color, true), text) +} + +/// Return shell escape sequence specifying either an RGB color, or a user-customizable 8-bit ANSI +/// color code. +// See +// https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit +// https://github.com/ogham/rust-ansi-term/blob/ff7eba98d55ad609c7fcc8c7bb0859b37c7545cc/src/ansi.rs#L82-L112 +fn get_color_escape_sequence(color: Color, foreground: bool) -> String { if color.a == 0 { // See https://github.com/sharkdp/bat/pull/543 format!("\x1b[{};5;{}m", if foreground { 38 } else { 48 }, color.r) @@ -235,6 +244,42 @@ fn get_color_code(color: Color, foreground: bool) -> String { } } +// 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, + } +} + +pub fn color_from_ansi_name(name: &str) -> Option<Color> { + ansi_color_name_to_number(name).and_then(|n| Some(color_from_ansi_number(n))) +} + +/// Convert 8-bit ANSI code to #RGBA string with ANSI code in red channel and 0 in alpha channel. +// See https://github.com/sharkdp/bat/pull/543 +pub fn color_from_ansi_number(n: u8) -> Color { + Color::from_str(&format!("#{:02x}000000", n)).unwrap() +} + mod superimpose_style_sections { use syntect::highlighting::{Style, StyleModifier}; |