Skip to content

Commit

Permalink
Provide CLI options for all colors
Browse files Browse the repository at this point in the history
- Fixes #99, #103
- Support both RGB and ANSI codes
- Eliminate (almost) ansi_term crate in favor of syntect for representing colors
  • Loading branch information
dandavison committed Feb 26, 2020
1 parent 10871b1 commit a7ae530
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 42 deletions.
31 changes: 29 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,22 @@ 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 accept an RGB hex string. An example of passing an RGB hex string is
--plus-color=\"#002800\".
It is also possible to specify one of the 16 ANSI terminal color codes. To do this, pass an RGBA
hex string, with the alpha channel set to 00, and use the red channel to specify the ANSI terminal
color code. The blue and green channels are ignored. For example, to specify ANSI terminal code
34 (blue), one could pass --plus-color=\"#12000000\".
"
)]
pub struct Opt {
/// Use colors appropriate for a light terminal background. For
/// more control, see --theme, --plus-color, and --minus-color.
Expand Down Expand Up @@ -54,16 +69,28 @@ pub struct Opt {
/// are: plain, box.
pub commit_style: SectionStyle,

#[structopt(long = "commit-color", default_value = "33")]
/// Color for commit section of git output. See below for how to specify colors.
pub commit_color: String,

#[structopt(long = "file-style", default_value = "underline")]
/// Formatting style for file section of git output. Options
/// are: plain, box, underline.
pub file_style: SectionStyle,

#[structopt(long = "file-color", default_value = "34")]
/// Color for file section of git output. See below for how to specify colors.
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 = "34")]
/// Color for the hunk-marker section of git output. See below for how to specify colors.
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
Expand Down
24 changes: 19 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use syntect::parsing::SyntaxSet;

use crate::cli;
use crate::env;
use crate::paint;
use crate::style;

pub struct Config<'a> {
Expand All @@ -14,6 +15,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>,
Expand Down Expand Up @@ -45,7 +49,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,
Expand All @@ -60,7 +64,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,
Expand All @@ -75,7 +79,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,
Expand All @@ -86,7 +90,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,
Expand All @@ -103,6 +107,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,
Expand Down Expand Up @@ -168,7 +175,14 @@ 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 {
match s.len() {
2 => paint::color_from_ansi_code(s),
_ => Color::from_str(s).expect(&format!("Invalid color: {}", s)),
}
}

fn color_from_arg_or_mode_default(
arg: Option<&String>,
is_light_mode: bool,
light_theme_default: Color,
Expand Down
22 changes: 13 additions & 9 deletions src/delta.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::io::Write;

use ansi_term::Colour::{Blue, Yellow};
use console::strip_ansi_codes;
use unicode_segmentation::UnicodeSegmentation;

use crate::bat::assets::HighlightingAssets;
use crate::cli;
use crate::config::Config;
use crate::draw;
use crate::paint::Painter;
use crate::paint::{paint, Painter};
use crate::parse;
use crate::style;

Expand Down Expand Up @@ -203,7 +202,7 @@ fn handle_commit_meta_header_line(
painter.writer,
line,
config.terminal_width,
Yellow.normal(),
config.commit_color,
true,
)?;
Ok(())
Expand Down Expand Up @@ -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(line, config.file_color),
config.terminal_width,
ansi_style,
config.file_color,
false,
)?;
Ok(())
Expand All @@ -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() {
Expand All @@ -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(line_number, config.hunk_color)
)?;
Ok(())
}

Expand Down Expand Up @@ -617,8 +618,11 @@ mod tests {
theme: None,
highlight_removed: false,
commit_style: cli::SectionStyle::Plain,
commit_color: "33".to_string(),
file_style: cli::SectionStyle::Underline,
file_color: "34".to_string(),
hunk_style: cli::SectionStyle::Box,
hunk_color: "34".to_string(),
width: Some("variable".to_string()),
tab_width: 4,
show_background_colors: false,
Expand Down
48 changes: 23 additions & 25 deletions src/draw.rs
Original file line number Diff line number Diff line change
@@ -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::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 {
Expand All @@ -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(up_left, color))?;
Ok(())
}

Expand All @@ -31,19 +33,19 @@ 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 {
line_width - box_width - 1
} else {
0
},
line_style,
color,
heavy,
)?;
write!(writer, "\n")?;
Expand All @@ -54,55 +56,51 @@ 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(text, color))?;
write_horizontal_line(writer, line_width - 1, color, heavy)?;
write!(writer, "\n")?;
Ok(())
}

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 {
box_drawing::heavy::HORIZONTAL
} else {
box_drawing::light::HORIZONTAL
};
write!(
writer,
"{}",
line_style.paint(horizontal.repeat(line_width),)
)
write!(writer, "{}", paint(&horizontal.repeat(line_width), color))
}

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 {
box_drawing::heavy::UP_HORIZONTAL
} 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(up_horizontal, color))?;
Ok(())
}

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 {
Expand All @@ -125,10 +123,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(&horizontal_edge, color),
paint(down_left, color),
paint(text, color),
paint(vertical, color),
paint(&horizontal_edge, color),
)
}
18 changes: 17 additions & 1 deletion src/paint.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::io::Write;
use std::str::FromStr;

use syntect::easy::HighlightLines;
use syntect::highlighting::{Color, Style, StyleModifier};
Expand Down Expand Up @@ -204,6 +205,11 @@ impl<'a> Painter<'a> {
}
}

/// Return text together with shell escape codes specifying the color.
pub fn paint(text: &str, color: Color) -> String {
format!("{}{}", get_color_code(color, true), text)
}

/// Write section text to buffer with color escape codes.
pub fn paint_text(text: &str, style: Style, output_buffer: &mut String) {
if text.is_empty() {
Expand All @@ -218,7 +224,7 @@ pub fn paint_text(text: &str, style: Style, output_buffer: &mut String) {
output_buffer.push_str(text);
}

/// ANSI color escape code.
/// Return shell escape codes specifying either an RGB color, or a user-customizable ANSI color 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 {
if color.a == 0 {
Expand All @@ -235,6 +241,16 @@ fn get_color_code(color: Color, foreground: bool) -> String {
}
}

/// 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_code(s: &str) -> Color {
Color::from_str(&format!(
"#{:02x}000000",
s.parse::<u8>().expect(&format!("Invalid color: {}", s)),
))
.unwrap()
}

mod superimpose_style_sections {
use syntect::highlighting::{Style, StyleModifier};

Expand Down

0 comments on commit a7ae530

Please sign in to comment.