summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/ansi.rs68
-rw-r--r--src/cli.rs18
-rw-r--r--src/config.rs70
-rw-r--r--src/delta.rs34
-rw-r--r--src/features/color_moved.rs24
-rw-r--r--src/features/mod.rs5
-rw-r--r--src/features/side_by_side.rs34
-rw-r--r--src/options/set.rs10
-rw-r--r--src/paint.rs39
-rw-r--r--src/parse_style.rs4
-rw-r--r--src/style.rs90
11 files changed, 254 insertions, 142 deletions
diff --git a/src/ansi.rs b/src/ansi.rs
index 01b2e0e6..c885c304 100644
--- a/src/ansi.rs
+++ b/src/ansi.rs
@@ -1,3 +1,71 @@
+use std::cmp::min;
+
+use console;
+use itertools::Itertools;
+
pub const ANSI_CSI_CLEAR_TO_EOL: &str = "\x1b[0K";
pub const ANSI_CSI_CLEAR_TO_BOL: &str = "\x1b[1K";
pub const ANSI_SGR_RESET: &str = "\x1b[0m";
+
+pub fn string_starts_with_ansi_escape_sequence(s: &str) -> bool {
+ console::AnsiCodeIterator::new(s)
+ .nth(0)
+ .map(|(_, is_ansi)| is_ansi)
+ .unwrap_or(false)
+}
+
+/// Return string formed from a byte slice starting at byte position `start`, where the index
+/// counts bytes in non-ANSI-escape-sequence content only. All ANSI escape sequences in the
+/// original string are preserved.
+pub fn ansi_preserving_slice(s: &str, start: usize) -> String {
+ console::AnsiCodeIterator::new(s)
+ .scan(0, |i, (substring, is_ansi)| {
+ // i is the index in non-ANSI-escape-sequence content.
+ let substring_slice = if is_ansi || *i > start {
+ substring
+ } else {
+ &substring[min(substring.len(), start - *i)..]
+ };
+ if !is_ansi {
+ *i += substring.len();
+ }
+ Some(substring_slice)
+ })
+ .join("")
+}
+
+#[cfg(test)]
+mod tests {
+
+ use crate::ansi::ansi_preserving_slice;
+ use crate::ansi::string_starts_with_ansi_escape_sequence;
+
+ #[test]
+ fn test_string_starts_with_ansi_escape_sequence() {
+ assert!(!string_starts_with_ansi_escape_sequence(""));
+ assert!(!string_starts_with_ansi_escape_sequence("-"));
+ assert!(string_starts_with_ansi_escape_sequence(
+ "\x1b[31m-XXX\x1b[m\n"
+ ));
+ assert!(string_starts_with_ansi_escape_sequence("\x1b[32m+XXX"));
+ }
+
+ #[test]
+ fn test_ansi_preserving_slice() {
+ assert_eq!(ansi_preserving_slice("", 0), "");
+ assert_eq!(ansi_preserving_slice("a", 0), "a");
+ assert_eq!(ansi_preserving_slice("a", 1), "");
+ assert_eq!(
+ ansi_preserving_slice("\x1b[1;35m-2222.2222.2222.2222\x1b[0m", 1),
+ "\x1b[1;35m2222.2222.2222.2222\x1b[0m"
+ );
+ assert_eq!(
+ ansi_preserving_slice("\x1b[1;35m-2222.2222.2222.2222\x1b[0m", 15),
+ "\x1b[1;35m.2222\x1b[0m"
+ );
+ assert_eq!(
+ ansi_preserving_slice("\x1b[1;36m-\x1b[m\x1b[1;36m2222·2222·2222·2222\x1b[m\n", 1),
+ "\x1b[1;36m\x1b[m\x1b[1;36m2222·2222·2222·2222\x1b[m\n"
+ )
+ }
+}
diff --git a/src/cli.rs b/src/cli.rs
index 384db044..0532a6f2 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -234,14 +234,6 @@ pub struct Opt {
/// --file-renamed-label.
pub navigate: bool,
- #[structopt(long = "color-moved")]
- /// Detect moved code lines and apply the styles color-moved-minus-style and
- /// color-moved-plus-style. This option requires the git config setting `diff.colorMoved =
- /// true` (or that you pass --color-moved to git on the command line). It can only work if
- /// delta receives colored input from git. So it works with `core.pager = delta` in git config,
- /// but if you pipe git's output to delta, you must pass --color=always to git.
- pub color_moved: bool,
-
#[structopt(long = "hyperlinks")]
/// Render commit hashes, file names, and line numbers as hyperlinks, according to the
/// hyperlink spec for terminal emulators:
@@ -435,16 +427,6 @@ pub struct Opt {
#[structopt(long = "line-numbers-right-style", default_value = "auto")]
pub line_numbers_right_style: String,
- #[structopt(long = "color-moved-minus-style", default_value = "auto")]
- /// Style (foreground, background, attributes) for moved lines in their old location. See
- /// STYLES section.
- pub color_moved_minus_style: String,
-
- #[structopt(long = "color-moved-plus-style", default_value = "auto")]
- /// Style (foreground, background, attributes) for moved lines in their new location. See
- /// STYLES section.
- pub color_moved_plus_style: String,
-
#[structopt(long = "file-modified-label", default_value = "")]
/// Text to display in front of a modified file path.
pub file_modified_label: String,
diff --git a/src/config.rs b/src/config.rs
index 4703e38e..9b39c350 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -15,7 +15,7 @@ use crate::delta::State;
use crate::env;
use crate::features::side_by_side;
use crate::git_config_entry::GitConfigEntry;
-use crate::style::Style;
+use crate::style::{self, Style};
pub struct Config {
pub available_terminal_width: usize,
@@ -46,7 +46,6 @@ pub struct Config {
pub minus_emph_style: Style,
pub minus_empty_line_marker_style: Style,
pub minus_file: Option<PathBuf>,
- pub minus_moved_style: Style,
pub minus_non_emph_style: Style,
pub minus_style: Style,
pub navigate: bool,
@@ -56,11 +55,10 @@ pub struct Config {
pub plus_emph_style: Style,
pub plus_empty_line_marker_style: Style,
pub plus_file: Option<PathBuf>,
- pub plus_moved_style: Style,
pub plus_non_emph_style: Style,
pub plus_style: Style,
- pub raw_expected_minus_style: Style,
- pub raw_expected_plus_style: Style,
+ pub git_minus_style: Style,
+ pub git_plus_style: Style,
pub side_by_side: bool,
pub side_by_side_data: side_by_side::SideBySideData,
pub syntax_dummy_theme: SyntaxTheme,
@@ -77,10 +75,8 @@ pub struct Config {
impl Config {
pub fn get_style(&self, state: &State) -> &Style {
match state {
- State::HunkMinus(false) => &self.minus_style,
- State::HunkMinus(true) => &self.minus_moved_style,
- State::HunkPlus(false) => &self.plus_style,
- State::HunkPlus(true) => &self.plus_moved_style,
+ State::HunkMinus(_) => &self.minus_style,
+ State::HunkPlus(_) => &self.plus_style,
State::CommitMeta => &self.commit_style,
State::FileMeta => &self.file_style,
State::HunkHeader => &self.hunk_header_style,
@@ -95,13 +91,11 @@ impl From<cli::Opt> for Config {
minus_style,
minus_emph_style,
minus_non_emph_style,
- minus_moved_style,
minus_empty_line_marker_style,
zero_style,
plus_style,
plus_emph_style,
plus_non_emph_style,
- plus_moved_style,
plus_empty_line_marker_style,
whitespace_error_style,
) = make_hunk_styles(&opt);
@@ -137,26 +131,14 @@ impl From<cli::Opt> for Config {
&opt.computed.available_terminal_width,
);
- let raw_expected_minus_style = Style::from_str(
- match opt.git_config_entries.get("color.diff.old") {
- Some(GitConfigEntry::Style(s)) => s,
- _ => "red",
- },
- None,
- None,
- opt.computed.true_color,
- false,
- );
- let raw_expected_plus_style = Style::from_str(
- match opt.git_config_entries.get("color.diff.new") {
- Some(GitConfigEntry::Style(s)) => s,
- _ => "green",
- },
- None,
- None,
- opt.computed.true_color,
- false,
- );
+ let git_minus_style = match opt.git_config_entries.get("color.diff.old") {
+ Some(GitConfigEntry::Style(s)) => Style::from_git_str(s),
+ _ => *style::GIT_DEFAULT_MINUS_STYLE,
+ };
+ let git_plus_style = match opt.git_config_entries.get("color.diff.new") {
+ Some(GitConfigEntry::Style(s)) => Style::from_git_str(s),
+ _ => *style::GIT_DEFAULT_PLUS_STYLE,
+ };
Self {
available_terminal_width: opt.computed.available_terminal_width,
@@ -189,7 +171,6 @@ impl From<cli::Opt> for Config {
minus_emph_style,
minus_empty_line_marker_style,
minus_file: opt.minus_file.map(|s| s.clone()),
- minus_moved_style,
minus_non_emph_style,
minus_style,
navigate: opt.navigate,
@@ -199,11 +180,10 @@ impl From<cli::Opt> for Config {
plus_emph_style,
plus_empty_line_marker_style,
plus_file: opt.plus_file.map(|s| s.clone()),
- plus_moved_style,
plus_non_emph_style,
plus_style,
- raw_expected_minus_style,
- raw_expected_plus_style,
+ git_minus_style,
+ git_plus_style,
side_by_side: opt.side_by_side,
side_by_side_data,
syntax_dummy_theme: SyntaxTheme::default(),
@@ -232,8 +212,6 @@ fn make_hunk_styles<'a>(
Style,
Style,
Style,
- Style,
- Style,
) {
let is_light_mode = opt.computed.is_light_mode;
let true_color = opt.computed.true_color;
@@ -273,14 +251,6 @@ fn make_hunk_styles<'a>(
false,
);
- let minus_moved_style = Style::from_str(
- &opt.color_moved_minus_style,
- Some(minus_style),
- None,
- true_color,
- false,
- );
-
// The style used to highlight a removed empty line when otherwise it would be invisible due to
// lack of background color in minus-style.
let minus_empty_line_marker_style = Style::from_str(
@@ -335,14 +305,6 @@ fn make_hunk_styles<'a>(
false,
);
- let plus_moved_style = Style::from_str(
- &opt.color_moved_plus_style,
- Some(plus_style),
- None,
- true_color,
- false,
- );
-
// The style used to highlight an added empty line when otherwise it would be invisible due to
// lack of background color in plus-style.
let plus_empty_line_marker_style = Style::from_str(
@@ -366,13 +328,11 @@ fn make_hunk_styles<'a>(
minus_style,
minus_emph_style,
minus_non_emph_style,
- minus_moved_style,
minus_empty_line_marker_style,
zero_style,
plus_style,
plus_emph_style,
plus_non_emph_style,
- plus_moved_style,
plus_empty_line_marker_style,
whitespace_error_style,
)
diff --git a/src/delta.rs b/src/delta.rs
index 5b3de991..409c1df6 100644
--- a/src/delta.rs
+++ b/src/delta.rs
@@ -12,16 +12,16 @@ use crate::features;
use crate::format;
use crate::paint::Painter;
use crate::parse;
-use crate::style::DecorationStyle;
+use crate::style::{self, DecorationStyle};
#[derive(Clone, Debug, PartialEq)]
pub enum State {
- CommitMeta, // In commit metadata section
- FileMeta, // In diff metadata section, between (possible) commit metadata and first hunk
- HunkHeader, // In hunk metadata line
- HunkZero, // In hunk; unchanged line
- HunkMinus(bool), // In hunk; removed line (is_moved)
- HunkPlus(bool), // In hunk; added line (is_moved)
+ CommitMeta, // In commit metadata section
+ FileMeta, // In diff metadata section, between (possible) commit metadata and first hunk
+ HunkHeader, // In hunk metadata line
+ HunkZero, // In hunk; unchanged line
+ HunkMinus(Option<String>), // In hunk; removed line (raw_line)
+ HunkPlus(Option<String>), // In hunk; added line (raw_line)
Unknown,
}
@@ -502,16 +502,28 @@ fn handle_hunk_line(
if let State::HunkPlus(_) = state {
painter.paint_buffered_minus_and_plus_lines();
}
- let is_moved = !config.raw_expected_minus_style.is_applied_to(raw_line);
- let state = State::HunkMinus(is_moved);
+ let state = if style::line_has_style_other_than(
+ raw_line,
+ [*style::GIT_DEFAULT_MINUS_STYLE, config.git_minus_style].iter(),
+ ) {
+ State::HunkMinus(Some(painter.prepare_raw_line(raw_line)))
+ } else {
+ State::HunkMinus(None)
+ };
painter
.minus_lines
.push((painter.prepare(&line, true), state.clone()));
state
}
Some('+') => {
- let is_moved = !config.raw_expected_plus_style.is_applied_to(raw_line);
- let state = State::HunkPlus(is_moved);
+ let state = if style::line_has_style_other_than(
+ raw_line,
+ [*style::GIT_DEFAULT_PLUS_STYLE, config.git_plus_style].iter(),
+ ) {
+ State::HunkPlus(Some(painter.prepare_raw_line(raw_line)))
+ } else {
+ State::HunkPlus(None)
+ };
painter
.plus_lines
.push((painter.prepare(&line, true), state.clone()));
diff --git a/src/features/color_moved.rs b/src/features/color_moved.rs
deleted file mode 100644
index 2879fb8d..00000000
--- a/src/features/color_moved.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use crate::features::OptionValueFunction;
-
-pub fn make_feature() -> Vec<(String, OptionValueFunction)> {
- builtin_feature!([
- (
- "color-moved",
- bool,
- None,
- _opt => true
- ),
- (
- "color-moved-minus-style",
- bool,
- Some("color.diff.oldMoved"),
- _opt => "red black"
- ),
- (
- "color-moved-plus-style",
- bool,
- Some("color.diff.newMoved"),
- _opt => "green black"
- )
- ])
-}
diff --git a/src/features/mod.rs b/src/features/mod.rs
index 89081e4f..67dbd6ba 100644
--- a/src/features/mod.rs
+++ b/src/features/mod.rs
@@ -27,10 +27,6 @@ type OptionValueFunction = Box<dyn Fn(&cli::Opt, &Option<GitConfig>) -> Provenan
pub fn make_builtin_features() -> HashMap<String, BuiltinFeature> {
vec![
(
- "color-moved".to_string(),
- color_moved::make_feature().into_iter().collect(),
- ),
- (
"color-only".to_string(),
color_only::make_feature().into_iter().collect(),
),
@@ -86,7 +82,6 @@ macro_rules! builtin_feature {
}
}
-pub mod color_moved;
pub mod color_only;
pub mod diff_highlight;
pub mod diff_so_fancy;
diff --git a/src/features/side_by_side.rs b/src/features/side_by_side.rs
index 3feb6e90..5226c97a 100644
--- a/src/features/side_by_side.rs
+++ b/src/features/side_by_side.rs
@@ -60,11 +60,13 @@ impl SideBySideData {
}
/// Emit a sequence of minus and plus lines in side-by-side mode.
-pub fn paint_minus_and_plus_lines_side_by_side(
+pub fn paint_minus_and_plus_lines_side_by_side<'a>(
minus_syntax_style_sections: Vec<Vec<(SyntectStyle, &str)>>,
minus_diff_style_sections: Vec<Vec<(Style, &str)>>,
+ minus_states: Vec<&'a State>,
plus_syntax_style_sections: Vec<Vec<(SyntectStyle, &str)>>,
plus_diff_style_sections: Vec<Vec<(Style, &str)>>,
+ plus_states: Vec<&'a State>,
line_alignment: Vec<(Option<usize>, Option<usize>)>,
output_buffer: &mut String,
config: &Config,
@@ -76,6 +78,10 @@ pub fn paint_minus_and_plus_lines_side_by_side(
minus_line_index,
&minus_syntax_style_sections,
&minus_diff_style_sections,
+ match minus_line_index {
+ Some(i) => minus_states[i],
+ None => &State::HunkMinus(None),
+ },
line_numbers_data,
if config.keep_plus_minus_markers {
"-"
@@ -89,6 +95,10 @@ pub fn paint_minus_and_plus_lines_side_by_side(
plus_line_index,
&plus_syntax_style_sections,
&plus_diff_style_sections,
+ match plus_line_index {
+ Some(i) => plus_states[i],
+ None => &State::HunkPlus(None),
+ },
line_numbers_data,
if config.keep_plus_minus_markers {
"+"
@@ -166,10 +176,11 @@ pub fn paint_zero_lines_side_by_side(
}
}
-fn paint_left_panel_minus_line(
+fn paint_left_panel_minus_line<'a>(
line_index: Option<usize>,
syntax_style_sections: &Vec<Vec<(SyntectStyle, &str)>>,
diff_style_sections: &Vec<Vec<(Style, &str)>>,
+ state: &'a State,
line_numbers_data: &mut Option<&mut line_numbers::LineNumbersData>,
prefix: &str,
background_color_extends_to_terminal_width: Option<bool>,
@@ -179,7 +190,7 @@ fn paint_left_panel_minus_line(
line_index,
&syntax_style_sections,
&diff_style_sections,
- &State::HunkMinus(false),
+ state,
line_numbers_data,
PanelSide::Left,
prefix,
@@ -190,7 +201,7 @@ fn paint_left_panel_minus_line(
panel_line_is_empty,
line_index,
diff_style_sections,
- &State::HunkMinus(false),
+ state,
background_color_extends_to_terminal_width,
config,
);
@@ -198,10 +209,11 @@ fn paint_left_panel_minus_line(
panel_line
}
-fn paint_right_panel_plus_line(
+fn paint_right_panel_plus_line<'a>(
line_index: Option<usize>,
syntax_style_sections: &Vec<Vec<(SyntectStyle, &str)>>,
diff_style_sections: &Vec<Vec<(Style, &str)>>,
+ state: &'a State,
line_numbers_data: &mut Option<&mut line_numbers::LineNumbersData>,
prefix: &str,
background_color_extends_to_terminal_width: Option<bool>,
@@ -211,7 +223,7 @@ fn paint_right_panel_plus_line(
line_index,
&syntax_style_sections,
&diff_style_sections,
- &State::HunkPlus(false),
+ state,
line_numbers_data,
PanelSide::Right,
prefix,
@@ -222,7 +234,7 @@ fn paint_right_panel_plus_line(
panel_line_is_empty,
line_index,
diff_style_sections,
- &State::HunkPlus(false),
+ state,
background_color_extends_to_terminal_width,
config,
);
@@ -278,7 +290,7 @@ fn get_right_fill_style_for_left_panel(
// what this will do is set the line number pair in that function to `(Some(minus_number), None)`,
// and then only emit the right field (which has a None number, i.e. blank). However, it will also
// increment the minus line number, so we need to knock that back down.
-fn paint_minus_or_plus_panel_line(
+fn paint_minus_or_plus_panel_line<'a>(
line_index: Option<usize>,
syntax_style_sections: &Vec<Vec<(SyntectStyle, &str)>>,
diff_style_sections: &Vec<Vec<(Style, &str)>>,
@@ -298,9 +310,9 @@ fn paint_minus_or_plus_panel_line(
state.clone(),
)
} else {
- let opposite_state = match *state {
- State::HunkMinus(x) => State::HunkPlus(x),
- State::HunkPlus(x) => State::HunkMinus(x),
+ let opposite_state = match state {
+ State::HunkMinus(x) => State::HunkPlus(x.clone()),
+ State::HunkPlus(x) => State::HunkMinus(x.clone()),
_ => unreachable!(),
};
(
diff --git a/src/options/set.rs b/src/options/set.rs
index e7932e4b..02bd1ea3 100644
--- a/src/options/set.rs
+++ b/src/options/set.rs
@@ -115,9 +115,6 @@ pub fn set_options(
set_options!(
[
- color_moved,
- color_moved_minus_style,
- color_moved_plus_style,
color_only,
commit_decoration_style,
commit_style,
@@ -525,12 +522,7 @@ fn set_widths(
fn set_git_config_entries(opt: &mut cli::Opt, git_config: &mut git_config::GitConfig) {
// Styles
- for key in &[
- "color.diff.old",
- "color.diff.new",
- "color.diff.oldMoved",
- "color.diff.newMoved",
- ] {
+ for key in &["color.diff.old", "color.diff.new"] {
if let Some(style_string) = git_config.get::<String>(key) {
opt.git_config_entries
.insert(key.to_string(), GitConfigEntry::Style(style_string));
diff --git a/src/paint.rs b/src/paint.rs
index e10b4840..3aed7c21 100644
--- a/src/paint.rs
+++ b/src/paint.rs
@@ -101,6 +101,12 @@ impl<'a> Painter<'a> {
}
}
+ /// Remove the initial +/- character of a line that will be emitted unchanged, including any
+ /// ANSI escape sequences.
+ pub fn prepare_raw_line(&self, line: &str) -> String {
+ ansi::ansi_preserving_slice(line, 1)
+ }
+
/// Expand tabs as spaces.
/// tab_width = 0 is documented to mean do not replace tabs.
pub fn expand_tabs<'b, I>(&self, line: I) -> String
@@ -117,16 +123,15 @@ impl<'a> Painter<'a> {
}
pub fn paint_buffered_minus_and_plus_lines(&mut self) {
- let __ = false;
let minus_line_syntax_style_sections = Self::get_syntax_style_sections_for_lines(
&self.minus_lines,
- &State::HunkMinus(__),
+ &State::HunkMinus(None),
&mut self.highlighter,
self.config,
);
let plus_line_syntax_style_sections = Self::get_syntax_style_sections_for_lines(
&self.plus_lines,
- &State::HunkPlus(__),
+ &State::HunkPlus(None),
&mut self.highlighter,
self.config,
);
@@ -137,8 +142,10 @@ impl<'a> Painter<'a> {
side_by_side::paint_minus_and_plus_lines_side_by_side(
minus_line_syntax_style_sections,
minus_line_diff_style_sections,
+ self.minus_lines.iter().map(|(_, state)| state).collect(),
plus_line_syntax_style_sections,
plus_line_diff_style_sections,
+ self.plus_lines.iter().map(|(_, state)| state).collect(),
line_alignment,
&mut self.output_buffer,
self.config,
@@ -296,11 +303,9 @@ impl<'a> Painter<'a> {
// style: for right fill if line contains no emph sections
// non_emph_style: for right fill if line contains emph sections
let (style, non_emph_style) = match state {
- State::HunkMinus(false) => (config.minus_style, config.minus_non_emph_style),
+ State::HunkMinus(None) => (config.minus_style, config.minus_non_emph_style),
State::HunkZero => (config.zero_style, config.zero_style),
- State::HunkPlus(false) => (config.plus_style, config.plus_non_emph_style),
- State::HunkMinus(true) => (config.minus_moved_style, config.minus_moved_style),
- State::HunkPlus(true) => (config.plus_moved_style, config.plus_moved_style),
+ State::HunkPlus(None) => (config.plus_style, config.plus_non_emph_style),
_ => (config.null_style, config.null_style),
};
let fill_style = if style_sections_contain_more_than_one_style(diff_sections) {
@@ -362,6 +367,21 @@ impl<'a> Painter<'a> {
config,
))
}
+ match state {
+ State::HunkMinus(Some(raw_line)) | State::HunkPlus(Some(raw_line)) => {
+ // This line has been identified as one which should be emitted unchanged,
+ // including any ANSI escape sequences that it has.
+ return (
+ format!(
+ "{}{}",
+ ansi_term::ANSIStrings(&ansi_strings).to_string(),
+ raw_line
+ ),
+ false,
+ );
+ }
+ _ => {}
+ }
let mut is_empty = true;
for (section_style, mut text) in superimpose_style_sections(
syntax_sections,
@@ -398,16 +418,17 @@ impl<'a> Painter<'a> {
return false;
}
match state {
- State::HunkMinus(_) => {
+ State::HunkMinus(None) => {
config.minus_style.is_syntax_highlighted
|| config.minus_emph_style.is_syntax_highlighted
}
State::HunkZero => config.zero_style.is_syntax_highlighted,
- State::HunkPlus(_) => {
+ State::HunkPlus(None) => {
config.plus_style.is_syntax_highlighted
|| config.plus_emph_style.is_syntax_highlighted
}
State::HunkHeader => true,
+ State::HunkMinus(Some(_)) | State::HunkPlus(Some(_)) => false,
_ => panic!(
"should_compute_syntax_highlighting is undefined for state {:?}",
state
diff --git a/src/parse_style.rs b/src/parse_style.rs
index 73a42c65..565defa8 100644
--- a/src/parse_style.rs
+++ b/src/parse_style.rs
@@ -32,6 +32,10 @@ impl Style {
}
}
+ pub fn from_git_str(git_style_string: &str) -> Self {
+ Self::from_str(git_style_string, None, None, true, false)
+ }
+
/// Construct Style but interpreting 'ul', 'box', etc as applying to the decoration style.
fn from_str_with_handling_of_special_decoration_attributes(
style_string: &str,
diff --git a/src/style.rs b/src/style.rs
index 51403130..0188922c 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -2,7 +2,9 @@ use std::borrow::Cow;
use std::fmt;
use ansi_term;
+use lazy_static::lazy_static;
+use crate::ansi;
use crate::color;
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -137,3 +139,91 @@ impl Style {
words.join(" ")
}
}
+
+lazy_static! {
+ pub static ref GIT_DEFAULT_MINUS_STYLE: Style = Style {
+ ansi_term_style: ansi_term::Color::Red.normal(),
+ ..Style::new()
+ };
+ pub static ref GIT_DEFAULT_PLUS_STYLE: Style = Style {
+ ansi_term_style: ansi_term::Color::Green.normal(),
+ ..Style::new()
+ };
+}
+
+pub fn line_has_style_other_than<'a>(line: &str, styles: impl Iterator<Item = &'a Style>) -> bool {
+ if !ansi::string_starts_with_ansi_escape_sequence(line) {
+ return false;
+ }
+ for style in styles {
+ if style.is_applied_to(line) {
+ return false;
+ }
+ }
+ return true;
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+
+ #[test]
+ fn test_is_applied_to() {
+ assert!(Style::from_git_str(r##"black "#ddeeff""##)
+ .is_applied_to(
+ "\x1b[30;48;2;221;238;255m+\x1b[m\x1b[30;48;2;221;238;255m .map(|(_, is_ansi)| is_ansi)\x1b[m\n"))
+ }
+
+ #[test]
+ fn test_git_default_styles() {
+ let minus_line_from_unconfigured_git = "\x1b[31m-____\x1b[m\n";
+ let plus_line_from_unconfigured_git = "\x1b[32m+\x1b[m\x1b[32m____\x1b[m\n";
+ assert!(GIT_DEFAULT_MINUS_STYLE.is_applied_to(minus_line_from_unconfigured_git));
+ assert!(!GIT_DEFAULT_MINUS_STYLE.is_applied_to(plus_line_from_unconfigured_git));
+
+ assert!(GIT_DEFAULT_PLUS_STYLE.is_applied_to(plus_line_from_unconfigured_git));
+ assert!(!GIT_DEFAULT_PLUS_STYLE.is_applied_to(minus_line_from_unconfigured_git));
+ }
+
+ #[test]
+ fn test_line_has_style_other_than() {
+ let minus_line_from_unconfigured_git = "\x1b[31m-____\x1b[m\n";
+ let plus_line_from_unconfigured_git = "\x1b[32m+\x1b[m\x1b[32m____\x1b[m\n";
+
+ // Unstyled lines should test negative, regardless of supplied styles.
+ assert!(!line_has_style_other_than("", [].iter()));
+ assert!(!line_has_style_other_than(
+ "",
+ [*GIT_DEFAULT_MINUS_STYLE].iter()
+ ));
+
+ // Lines from git should test negative when corresponding default is supplied
+ assert!(!line_has_style_other_than(
+ minus_line_from_unconfigured_git,
+ [*GIT_DEFAULT_MINUS_STYLE].iter()
+ ));
+ assert!(!line_has_style_other_than(
+ plus_line_from_unconfigured_git,
+ [*GIT_DEFAULT_PLUS_STYLE].iter()
+ ));
+
+ // Styled lines should test positive when unless their style is supplied.
+ assert!(line_has_style_other_than(
+ minus_line_from_unconfigured_git,
+ [*GIT_DEFAULT_PLUS_STYLE].iter()
+ ));
+ assert!(line_has_style_other_than(
+ minus_line_from_unconfigured_git,
+ [].iter()
+ ));
+ assert!(line_has_style_other_than(
+ plus_line_from_unconfigured_git,
+ [*GIT_DEFAULT_MINUS_STYLE].iter()
+ ));
+ assert!(line_has_style_other_than(
+ plus_line_from_unconfigured_git,
+ [].iter()
+ ));
+ }
+}