Skip to content

Commit

Permalink
New option to map raw styles encountered in input
Browse files Browse the repository at this point in the history
Fixes #72
  • Loading branch information
dandavison committed Nov 23, 2021
1 parent e8a860e commit a89001f
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 2 deletions.
6 changes: 6 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,12 @@ pub struct Opt {
/// (underline), 'ol' (overline), or the combination 'ul ol'.
pub hunk_header_decoration_style: String,

#[structopt(long = "map-styles")]
/// A string specifying a mapping styles encountered in raw input to desired
/// output styles. An example is --map-styles='black cyan => white magenta,
/// red cyan => white blue'
pub map_styles: Option<String>,

/// Format string for git blame commit metadata. Available placeholders are
/// "{timestamp}", "{author}", and "{commit}".
#[structopt(
Expand Down
26 changes: 26 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::git_config::{GitConfig, GitConfigEntry};
use crate::minusplus::MinusPlus;
use crate::paint::BgFillMethod;
use crate::parse_styles;
use crate::style;
use crate::style::Style;
use crate::tests::TESTING;
use crate::utils::bat::output::PagingMode;
Expand Down Expand Up @@ -105,6 +106,7 @@ pub struct Config {
pub line_numbers_style_minusplus: MinusPlus<Style>,
pub line_numbers_zero_style: Style,
pub line_numbers: bool,
pub styles_map: Option<HashMap<style::AnsiTermStyleEqualityKey, Style>>,
pub max_line_distance_for_naively_paired_lines: f64,
pub max_line_distance: f64,
pub max_line_length: usize,
Expand Down Expand Up @@ -157,6 +159,7 @@ impl Config {
impl From<cli::Opt> for Config {
fn from(opt: cli::Opt) -> Self {
let styles = parse_styles::parse_styles(&opt);
let styles_map = make_styles_map(&opt);

let max_line_distance_for_naively_paired_lines =
env::get_env_var("DELTA_EXPERIMENTAL_MAX_LINE_DISTANCE_FOR_NAIVELY_PAIRED_LINES")
Expand Down Expand Up @@ -297,6 +300,7 @@ impl From<cli::Opt> for Config {
),
line_numbers_zero_style: styles["line-numbers-zero-style"],
line_buffer_size: opt.line_buffer_size,
styles_map,
max_line_distance: opt.max_line_distance,
max_line_distance_for_naively_paired_lines,
max_line_length: match (opt.side_by_side, wrap_max_lines_plus1) {
Expand Down Expand Up @@ -396,6 +400,28 @@ fn make_blame_palette(blame_palette: Option<String>, is_light_mode: bool) -> Vec
}
}

fn make_styles_map(opt: &cli::Opt) -> Option<HashMap<style::AnsiTermStyleEqualityKey, Style>> {
if let Some(styles_map_str) = &opt.map_styles {
let mut styles_map = HashMap::new();
for pair_str in styles_map_str.split(",") {
let mut style_strs = pair_str.split("=>").map(|s| s.trim());
if let (Some(from_str), Some(to_str)) = (style_strs.next(), style_strs.next()) {
let key = style::ansi_term_style_equality_key(
Style::from_str(from_str, None, None, true, opt.git_config.as_ref())
.ansi_term_style,
);
styles_map.insert(
key,
Style::from_str(to_str, None, None, true, opt.git_config.as_ref()),
);
}
}
Some(styles_map)
} else {
None
}
}

/// Did the user supply `option` on the command line?
pub fn user_supplied_option(option: &str, arg_matches: &clap::ArgMatches) -> bool {
arg_matches.occurrences_of(option) > 0
Expand Down
1 change: 1 addition & 0 deletions src/options/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ pub fn set_options(
inspect_raw_lines,
keep_plus_minus_markers,
line_buffer_size,
map_styles,
max_line_distance,
max_line_length,
// Hack: minus-style must come before minus-*emph-style because the latter default
Expand Down
22 changes: 20 additions & 2 deletions src/paint.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::io::Write;

use itertools::Itertools;
Expand All @@ -6,7 +7,6 @@ use syntect::highlighting::Style as SyntectStyle;
use syntect::parsing::{SyntaxReference, SyntaxSet};
use unicode_segmentation::UnicodeSegmentation;

use crate::ansi;
use crate::config::{self, delta_unreachable, Config};
use crate::delta::State;
use crate::edits;
Expand All @@ -18,6 +18,7 @@ use crate::minusplus::*;
use crate::paint::superimpose_style_sections::superimpose_style_sections;
use crate::style::Style;
use crate::wrapping::wrap_minusplus_block;
use crate::{ansi, style};

pub struct Painter<'p> {
pub minus_lines: Vec<(String, State)>,
Expand Down Expand Up @@ -138,7 +139,7 @@ impl<'p> Painter<'p> {
/// ANSI escape sequences.
pub fn prepare_raw_line(&self, line: &str) -> String {
ansi::ansi_preserving_slice(
&self.expand_tabs(line.graphemes(true)),
&self.expand_tabs(self.map_styles(line).graphemes(true)),
if self.config.keep_plus_minus_markers {
0
} else {
Expand All @@ -147,6 +148,23 @@ impl<'p> Painter<'p> {
)
}

fn map_styles<'a>(&self, line: &'a str) -> Cow<'a, str> {
if let Some(styles_map) = &self.config.styles_map {
let transformed_line = ansi::parse_style_sections(line)
.iter()
.map(|(original_style, s)| {
match styles_map.get(&style::ansi_term_style_equality_key(*original_style)) {
Some(mapped_style) => mapped_style.paint(*s),
None => original_style.paint(*s),
}
})
.join("");
Cow::from(transformed_line)
} else {
Cow::from(line)
}
}

/// Expand tabs as spaces.
/// tab_width = 0 is documented to mean do not replace tabs.
pub fn expand_tabs<'a, I>(&self, line: I) -> String
Expand Down
75 changes: 75 additions & 0 deletions src/style.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::borrow::Cow;
use std::fmt;
use std::hash::{Hash, Hasher};

use lazy_static::lazy_static;

Expand Down Expand Up @@ -210,6 +211,60 @@ pub fn ansi_term_style_equality(a: ansi_term::Style, b: ansi_term::Style) -> boo
}
}

// TODO: The equality methods were implemented first, and the equality_key
// methods later. The former should be re-implemented in terms of the latter.
// But why did the former not address equality of ansi_term::Color::RGB values?
pub struct AnsiTermStyleEqualityKey {
attrs_key: (bool, bool, bool, bool, bool, bool, bool, bool),
foreground_key: Option<(u8, u8, u8, u8)>,
background_key: Option<(u8, u8, u8, u8)>,
}

impl PartialEq for AnsiTermStyleEqualityKey {
fn eq(&self, other: &Self) -> bool {
let option_eq = |opt_a, opt_b| match (opt_a, opt_b) {
(Some(a), Some(b)) => a == b,
(None, None) => true,
_ => false,
};

if self.attrs_key != other.attrs_key {
false
} else {
option_eq(self.foreground_key, other.foreground_key)
&& option_eq(self.background_key, other.background_key)
}
}
}

impl Eq for AnsiTermStyleEqualityKey {}

impl Hash for AnsiTermStyleEqualityKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.attrs_key.hash(state);
self.foreground_key.hash(state);
self.background_key.hash(state);
}
}

pub fn ansi_term_style_equality_key(style: ansi_term::Style) -> AnsiTermStyleEqualityKey {
let attrs_key = (
style.is_bold,
style.is_dimmed,
style.is_italic,
style.is_underline,
style.is_blink,
style.is_reverse,
style.is_hidden,
style.is_strikethrough,
);
AnsiTermStyleEqualityKey {
attrs_key,
foreground_key: style.foreground.map(ansi_term_color_equality_key),
background_key: style.background.map(ansi_term_color_equality_key),
}
}

fn ansi_term_color_equality(a: Option<ansi_term::Color>, b: Option<ansi_term::Color>) -> bool {
match (a, b) {
(None, None) => true,
Expand Down Expand Up @@ -239,6 +294,26 @@ fn ansi_term_16_color_equality(a: ansi_term::Color, b: ansi_term::Color) -> bool
)
}

fn ansi_term_color_equality_key(color: ansi_term::Color) -> (u8, u8, u8, u8) {
// Same (r, g, b, a) encoding as in utils::bat::terminal::to_ansi_color.
// When a = 0xFF, then a 256-color number is stored in the red channel, and
// the green and blue channels are meaningless. But a=0 signifies an RGB
// color.
let default = 0xFF;
match color {
ansi_term::Color::Fixed(0) | ansi_term::Color::Black => (0, default, default, default),
ansi_term::Color::Fixed(1) | ansi_term::Color::Red => (1, default, default, default),
ansi_term::Color::Fixed(2) | ansi_term::Color::Green => (2, default, default, default),
ansi_term::Color::Fixed(3) | ansi_term::Color::Yellow => (3, default, default, default),
ansi_term::Color::Fixed(4) | ansi_term::Color::Blue => (4, default, default, default),
ansi_term::Color::Fixed(5) | ansi_term::Color::Purple => (5, default, default, default),
ansi_term::Color::Fixed(6) | ansi_term::Color::Cyan => (6, default, default, default),
ansi_term::Color::Fixed(7) | ansi_term::Color::White => (7, default, default, default),
ansi_term::Color::Fixed(n) => (n, default, default, default),
ansi_term::Color::RGB(r, g, b) => (r, g, b, 0),
}
}

lazy_static! {
pub static ref GIT_DEFAULT_MINUS_STYLE: Style = Style {
ansi_term_style: ansi_term::Color::Red.normal(),
Expand Down

0 comments on commit a89001f

Please sign in to comment.