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 da8ce86
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 42 deletions.
29 changes: 27 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,20 @@ 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. An example is:
--file-color=yellow.
"
)]
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 +67,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 da8ce86

Please sign in to comment.