Handle combined diff format

With this commit combined diff
format (https://git-scm.com/docs/git-diff#_combined_diff_format) is
handled appropriately. However, there is no special handling of merge
conflict markers.

Fixes #189, #736
This commit is contained in:
Dan Davison 2021-11-29 10:37:35 -05:00
parent f73f0a8cbd
commit 422954164f
15 changed files with 196 additions and 125 deletions

View File

@ -145,11 +145,11 @@ pub struct Config {
impl Config {
pub fn get_style(&self, state: &State) -> &Style {
match state {
State::HunkMinus(_) => &self.minus_style,
State::HunkPlus(_) => &self.plus_style,
State::HunkMinus(_, _) => &self.minus_style,
State::HunkPlus(_, _) => &self.plus_style,
State::CommitMeta => &self.commit_style,
State::DiffHeader => &self.file_style,
State::HunkHeader(_, _) => &self.hunk_header_style,
State::DiffHeader(_) => &self.file_style,
State::HunkHeader(_, _, _) => &self.hunk_header_style,
State::SubmoduleLog => &self.file_style,
_ => delta_unreachable("Unreachable code reached in get_style."),
}

View File

@ -14,17 +14,17 @@ use crate::style::DecorationStyle;
#[derive(Clone, Debug, PartialEq)]
pub enum State {
CommitMeta, // In commit metadata section
DiffHeader, // In diff header section, between (possible) commit metadata and first hunk
HunkHeader(String, String), // In hunk metadata line (line, raw_line)
HunkZero, // In hunk; unchanged line
HunkMinus(Option<String>), // In hunk; removed line (raw_line)
HunkPlus(Option<String>), // In hunk; added line (raw_line)
SubmoduleLog, // In a submodule section, with gitconfig diff.submodule = log
CommitMeta, // In commit metadata section
DiffHeader(DiffType), // In diff metadata section, between (possible) commit metadata and first hunk
HunkHeader(DiffType, String, String), // In hunk metadata line (line, raw_line)
HunkZero(Option<String>), // In hunk; unchanged line (prefix)
HunkMinus(Option<String>, Option<String>), // In hunk; removed line (prefix, raw_line)
HunkPlus(Option<String>, Option<String>), // In hunk; added line (prefix, raw_line)
SubmoduleLog, // In a submodule section, with gitconfig diff.submodule = log
SubmoduleShort(String), // In a submodule section, with gitconfig diff.submodule = short
Blame(String, Option<String>), // In a line of `git blame` output (commit, repeat_blame_line).
GitShowFile, // In a line of `git show $revision:./path/to/file.ext` output
Grep, // In a line of `git grep` output
GitShowFile, // In a line of `git show $revision:./path/to/file.ext` output
Grep, // In a line of `git grep` output
Unknown,
// The following elements are created when a line is wrapped to display it:
HunkZeroWrapped, // Wrapped unchanged line
@ -32,6 +32,12 @@ pub enum State {
HunkPlusWrapped, // Wrapped added line
}
#[derive(Clone, Debug, PartialEq)]
pub enum DiffType {
Unified,
Combined(usize), // number of parent commits: https://git-scm.com/docs/git-diff#_combined_diff_format
}
#[derive(Debug, PartialEq)]
pub enum Source {
GitDiff, // Coming from a `git diff` command
@ -171,7 +177,9 @@ impl<'a> StateMachine<'a> {
/// Skip file metadata lines unless a raw diff style has been requested.
pub fn should_skip_line(&self) -> bool {
self.state == State::DiffHeader && self.should_handle() && !self.config.color_only
matches!(self.state, State::DiffHeader(_))
&& self.should_handle()
&& !self.config.color_only
}
/// Emit unchanged any line that delta does not handle.
@ -211,7 +219,11 @@ pub fn format_raw_line<'a>(line: &'a str, config: &Config) -> Cow<'a, str> {
/// * git diff
/// * diff -u
fn detect_source(line: &str) -> Source {
if line.starts_with("commit ") || line.starts_with("diff --git ") {
if line.starts_with("commit ")
|| line.starts_with("diff --git ")
|| line.starts_with("diff --cc ")
|| line.starts_with("diff --combined ")
{
Source::GitDiff
} else if line.starts_with("diff -u")
|| line.starts_with("diff -ru")

View File

@ -76,18 +76,18 @@ pub fn linenumbers_and_styles<'a>(
config.line_numbers_style_minusplus[Plus],
);
let ((minus_number, plus_number), (minus_style, plus_style)) = match state {
State::HunkMinus(_) => {
State::HunkMinus(_, _) => {
line_numbers_data.line_number[Left] += increment as usize;
((Some(nr_left), None), (minus_style, plus_style))
}
State::HunkMinusWrapped => ((None, None), (minus_style, plus_style)),
State::HunkZero => {
State::HunkZero(_) => {
line_numbers_data.line_number[Left] += increment as usize;
line_numbers_data.line_number[Right] += increment as usize;
((Some(nr_left), Some(nr_right)), (zero_style, zero_style))
}
State::HunkZeroWrapped => ((None, None), (zero_style, zero_style)),
State::HunkPlus(_) => {
State::HunkPlus(_, _) => {
line_numbers_data.line_number[Right] += increment as usize;
((None, Some(nr_right)), (minus_style, plus_style))
}

View File

@ -180,7 +180,7 @@ pub fn paint_minus_and_plus_lines_side_by_side(
&lines_have_homolog[Left],
match minus_line_index {
Some(i) => &line_states[Left][i],
None => &State::HunkMinus(None),
None => &State::HunkMinus(None, None),
},
&mut Some(line_numbers_data),
bg_should_fill[Left],
@ -201,7 +201,7 @@ pub fn paint_minus_and_plus_lines_side_by_side(
&lines_have_homolog[Right],
match plus_line_index {
Some(i) => &line_states[Right][i],
None => &State::HunkPlus(None),
None => &State::HunkPlus(None, None),
},
&mut Some(line_numbers_data),
bg_should_fill[Right],
@ -222,7 +222,7 @@ pub fn paint_zero_lines_side_by_side<'a>(
painted_prefix: Option<ansi_term::ANSIString>,
background_color_extends_to_terminal_width: BgShouldFill,
) {
let states = vec![State::HunkZero];
let states = vec![State::HunkZero(None)];
let (states, syntax_style_sections, diff_style_sections) = wrap_zero_block(
config,
@ -418,8 +418,8 @@ fn paint_minus_or_plus_panel_line<'a>(
)
} else {
let opposite_state = match state {
State::HunkMinus(x) => State::HunkPlus(x.clone()),
State::HunkPlus(x) => State::HunkMinus(x.clone()),
State::HunkMinus(_, s) => State::HunkPlus(None, s.clone()),
State::HunkPlus(_, s) => State::HunkMinus(None, s.clone()),
_ => unreachable!(),
};
(
@ -470,17 +470,17 @@ fn pad_panel_line_to_width<'a>(
// to form the other half of the line, then don't emit the empty line marker.
if panel_line_is_empty && line_index.is_some() {
match state {
State::HunkMinus(_) => Painter::mark_empty_line(
State::HunkMinus(_, _) => Painter::mark_empty_line(
&config.minus_empty_line_marker_style,
panel_line,
Some(" "),
),
State::HunkPlus(_) => Painter::mark_empty_line(
State::HunkPlus(_, _) => Painter::mark_empty_line(
&config.plus_empty_line_marker_style,
panel_line,
Some(" "),
),
State::HunkZero => {}
State::HunkZero(_) => {}
_ => unreachable!(),
};
};

View File

@ -5,7 +5,7 @@ use unicode_segmentation::UnicodeSegmentation;
use super::draw;
use crate::config::Config;
use crate::delta::{Source, State, StateMachine};
use crate::delta::{DiffType, Source, State, StateMachine};
use crate::features;
use crate::paint::Painter;
@ -24,7 +24,7 @@ pub enum FileEvent {
impl<'a> StateMachine<'a> {
#[inline]
fn test_diff_header_minus_line(&self) -> bool {
(self.state == State::DiffHeader || self.source == Source::DiffUnified)
(matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified)
&& (self.line.starts_with("--- ")
|| self.line.starts_with("rename from ")
|| self.line.starts_with("copy from ")
@ -57,7 +57,7 @@ impl<'a> StateMachine<'a> {
self.minus_file_event = file_event;
if self.source == Source::DiffUnified {
self.state = State::DiffHeader;
self.state = State::DiffHeader(DiffType::Unified);
self.painter
.set_syntax(get_file_extension_from_marker_line(&self.line));
} else {
@ -85,7 +85,7 @@ impl<'a> StateMachine<'a> {
#[inline]
fn test_diff_header_plus_line(&self) -> bool {
(self.state == State::DiffHeader || self.source == Source::DiffUnified)
(matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified)
&& (self.line.starts_with("+++ ")
|| self.line.starts_with("rename to ")
|| self.line.starts_with("copy to ")

View File

@ -1,4 +1,4 @@
use crate::delta::{State, StateMachine};
use crate::delta::{DiffType, State, StateMachine};
impl<'a> StateMachine<'a> {
#[inline]
@ -12,7 +12,12 @@ impl<'a> StateMachine<'a> {
return Ok(false);
}
self.painter.paint_buffered_minus_and_plus_lines();
self.state = State::DiffHeader;
self.state =
if self.line.starts_with("diff --cc ") || self.line.starts_with("diff --combined ") {
State::DiffHeader(DiffType::Combined(2)) // We will confirm the number of parents when we see the hunk header
} else {
State::DiffHeader(DiffType::Unified)
};
self.handled_diff_header_header_line_file_pair = None;
self.diff_line = self.line.clone();
if !self.should_skip_line() {

View File

@ -1,4 +1,4 @@
use crate::delta::{Source, State, StateMachine};
use crate::delta::{DiffType, Source, State, StateMachine};
impl<'a> StateMachine<'a> {
#[inline]
@ -11,6 +11,9 @@ impl<'a> StateMachine<'a> {
if !self.test_diff_header_misc_cases() {
return Ok(false);
}
self.handle_additional_cases(State::DiffHeader)
self.handle_additional_cases(match self.state {
State::DiffHeader(_) => self.state.clone(),
_ => State::DiffHeader(DiffType::Unified),
})
}
}

View File

@ -1,7 +1,10 @@
use std::cmp::min;
use lazy_static::lazy_static;
use crate::cli;
use crate::delta::{State, StateMachine};
use crate::config::delta_unreachable;
use crate::delta::{DiffType, State, StateMachine};
use crate::style;
use crate::utils::process::{self, CallingProcess};
use unicode_segmentation::UnicodeSegmentation;
@ -26,7 +29,10 @@ impl<'a> StateMachine<'a> {
fn test_hunk_line(&self) -> bool {
matches!(
self.state,
State::HunkHeader(_, _) | State::HunkZero | State::HunkMinus(_) | State::HunkPlus(_)
State::HunkHeader(_, _, _)
| State::HunkZero(_)
| State::HunkMinus(_, _)
| State::HunkPlus(_, _)
) && !&*IS_WORD_DIFF
}
@ -37,6 +43,7 @@ impl<'a> StateMachine<'a> {
// highlighting according to inferred edit operations. In the case of
// an unchanged line, we paint it immediately.
pub fn handle_hunk_line(&mut self) -> std::io::Result<bool> {
use State::*;
// A true hunk line should start with one of: '+', '-', ' '. However, handle_hunk_line
// handles all lines until the state transitions away from the hunk states.
if !self.test_hunk_line() {
@ -50,16 +57,17 @@ impl<'a> StateMachine<'a> {
{
self.painter.paint_buffered_minus_and_plus_lines();
}
if let State::HunkHeader(line, raw_line) = &self.state.clone() {
if let State::HunkHeader(_, line, raw_line) = &self.state.clone() {
self.emit_hunk_header_line(line, raw_line)?;
}
self.state = match self.line.chars().next() {
Some('-') => {
if let State::HunkPlus(_) = self.state {
self.state = match new_line_state(&self.line, &self.state) {
Some(HunkMinus(prefix, _)) => {
if let HunkPlus(_, _) = self.state {
// We have just entered a new subhunk; process the previous one
// and flush the line buffers.
self.painter.paint_buffered_minus_and_plus_lines();
}
let line = self.painter.prepare(&self.line, prefix.as_deref());
let state = match self.config.inspect_raw_lines {
cli::InspectRawLines::True
if style::line_has_style_other_than(
@ -67,16 +75,18 @@ impl<'a> StateMachine<'a> {
[*style::GIT_DEFAULT_MINUS_STYLE, self.config.git_minus_style].iter(),
) =>
{
State::HunkMinus(Some(self.painter.prepare_raw_line(&self.raw_line)))
let raw_line = self
.painter
.prepare_raw_line(&self.raw_line, prefix.as_deref());
HunkMinus(prefix, Some(raw_line))
}
_ => State::HunkMinus(None),
_ => HunkMinus(prefix, None),
};
self.painter
.minus_lines
.push((self.painter.prepare(&self.line), state.clone()));
self.painter.minus_lines.push((line, state.clone()));
state
}
Some('+') => {
Some(HunkPlus(prefix, _)) => {
let line = self.painter.prepare(&self.line, prefix.as_deref());
let state = match self.config.inspect_raw_lines {
cli::InspectRawLines::True
if style::line_has_style_other_than(
@ -84,37 +94,70 @@ impl<'a> StateMachine<'a> {
[*style::GIT_DEFAULT_PLUS_STYLE, self.config.git_plus_style].iter(),
) =>
{
State::HunkPlus(Some(self.painter.prepare_raw_line(&self.raw_line)))
let raw_line = self
.painter
.prepare_raw_line(&self.raw_line, prefix.as_deref());
HunkPlus(prefix, Some(raw_line))
}
_ => State::HunkPlus(None),
_ => HunkPlus(prefix, None),
};
self.painter
.plus_lines
.push((self.painter.prepare(&self.line), state.clone()));
self.painter.plus_lines.push((line, state.clone()));
state
}
Some(' ') => {
Some(HunkZero(prefix)) => {
// We are in a zero (unchanged) line, therefore we have just exited a subhunk (a
// sequence of consecutive minus (removed) and/or plus (added) lines). Process that
// subhunk and flush the line buffers.
self.painter.paint_buffered_minus_and_plus_lines();
self.painter.paint_zero_line(&self.line);
State::HunkZero
self.painter.paint_zero_line(&self.line, prefix.clone());
HunkZero(prefix)
}
_ => {
// The first character here could be e.g. '\' from '\ No newline at end of file'. This
// is not a hunk line, but the parser does not have a more accurate state corresponding
// to this.
// We are in a zero (unchanged) line, therefore we have just exited a subhunk (a
// sequence of consecutive minus (removed) and/or plus (added) lines). Process that
// subhunk and flush the line buffers.
self.painter.paint_buffered_minus_and_plus_lines();
self.painter
.output_buffer
.push_str(&self.painter.expand_tabs(self.raw_line.graphemes(true)));
self.painter.output_buffer.push('\n');
State::HunkZero
State::HunkZero(None)
}
};
self.painter.emit()?;
Ok(true)
}
}
fn new_line_state(new_line: &str, prev_state: &State) -> Option<State> {
use State::*;
let diff_type = match prev_state {
HunkMinus(None, _) | HunkZero(None) | HunkPlus(None, _) => DiffType::Unified,
HunkHeader(diff_type, _, _) => diff_type.clone(),
HunkMinus(Some(prefix), _) | HunkZero(Some(prefix)) | HunkPlus(Some(prefix), _) => {
DiffType::Combined(prefix.len())
}
_ => delta_unreachable(&format!("diff_type: unexpected state: {:?}", prev_state)),
};
let (prefix_char, prefix) = match diff_type {
DiffType::Unified => (new_line.chars().next(), None),
DiffType::Combined(n_parents) => {
let prefix = &new_line[..min(n_parents, new_line.len())];
let prefix_char = match prefix.chars().find(|c| c == &'-' || c == &'+') {
Some(c) => Some(c),
None => match prefix.chars().find(|c| c != &' ') {
None => Some(' '),
Some(_) => None,
},
};
(prefix_char, Some(prefix.to_string()))
}
};
match prefix_char {
Some('-') => Some(HunkMinus(prefix, None)),
Some(' ') => Some(HunkZero(prefix)),
Some('+') => Some(HunkPlus(prefix, None)),
_ => None,
}
}

View File

@ -26,7 +26,7 @@ use regex::Regex;
use super::draw;
use crate::config::Config;
use crate::delta::{self, State, StateMachine};
use crate::delta::{self, DiffType, State, StateMachine};
use crate::paint::{self, BgShouldFill, Painter, StyleSectionSpecifier};
use crate::style::DecorationStyle;
@ -40,7 +40,15 @@ impl<'a> StateMachine<'a> {
if !self.test_hunk_header_line() {
return Ok(false);
}
self.state = State::HunkHeader(self.line.clone(), self.raw_line.clone());
let diff_type = match &self.state {
State::DiffHeader(DiffType::Combined(_)) => {
// https://git-scm.com/docs/git-diff#_combined_diff_format
let n_parents = self.line.chars().take_while(|c| c == &'@').count() - 1;
DiffType::Combined(n_parents)
}
_ => DiffType::Unified,
};
self.state = State::HunkHeader(diff_type, self.line.clone(), self.raw_line.clone());
Ok(true)
}
@ -251,7 +259,7 @@ fn write_to_output_buffer(
painter.syntax_highlight_and_paint_line(
&line,
StyleSectionSpecifier::Style(config.hunk_header_style),
delta::State::HunkHeader("".to_owned(), "".to_owned()),
delta::State::HunkHeader(DiffType::Unified, "".to_owned(), "".to_owned()),
BgShouldFill::No,
);
painter.output_buffer.pop(); // trim newline

View File

@ -18,7 +18,7 @@ impl<'a> StateMachine<'a> {
#[inline]
fn test_submodule_short_line(&self) -> bool {
matches!(self.state, State::HunkHeader(_, _))
matches!(self.state, State::HunkHeader(_, _, _))
&& self.line.starts_with("-Subproject commit ")
|| matches!(self.state, State::SubmoduleShort(_))
&& self.line.starts_with("+Subproject commit ")
@ -29,7 +29,7 @@ impl<'a> StateMachine<'a> {
return Ok(false);
}
if let Some(commit) = get_submodule_short_commit(&self.line) {
if let State::HunkHeader(_, _) = self.state {
if let State::HunkHeader(_, _, _) = self.state {
self.state = State::SubmoduleShort(commit.to_owned());
} else if let State::SubmoduleShort(minus_commit) = &self.state {
self.painter.emit()?;

View File

@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::io::Write;
use ansi_term::ANSIString;
use itertools::Itertools;
use syntect::easy::HighlightLines;
use syntect::highlighting::Style as SyntectStyle;
@ -122,26 +123,33 @@ impl<'p> Painter<'p> {
// Terminating with newline character is necessary for many of the sublime syntax definitions to
// highlight correctly.
// See https://docs.rs/syntect/3.2.0/syntect/parsing/struct.SyntaxSetBuilder.html#method.add_from_folder
pub fn prepare(&self, line: &str) -> String {
pub fn prepare(&self, line: &str, prefix: Option<&str>) -> String {
if !line.is_empty() {
let mut line = line.graphemes(true);
// The first column contains a -/+/space character, added by git. We remove it now so that
// it is not present during syntax highlighting or wrapping. If --keep-plus-minus-markers is
// in effect this character is re-inserted in Painter::paint_line.
line.next();
// The initial columns contain -/+/space characters, added by git. Remove them now so
// they are not present during syntax highlighting or wrapping. If
// --keep-plus-minus-markers is in effect this prefix is re-inserted in
// Painter::paint_line.
let prefix_length = prefix.map(|s| s.len()).unwrap_or(1);
for _ in 0..prefix_length {
line.next();
}
format!("{}\n", self.expand_tabs(line))
} else {
"\n".to_string()
}
}
// Remove initial -/+ character, expand tabs as spaces, retaining ANSI sequences. Terminate with
// Remove initial -/+ characters, expand tabs as spaces, retaining ANSI sequences. Terminate with
// newline character.
pub fn prepare_raw_line(&self, raw_line: &str) -> String {
pub fn prepare_raw_line(&self, raw_line: &str, prefix: Option<&str>) -> String {
format!(
"{}\n",
ansi::ansi_preserving_slice(&self.expand_tabs(raw_line.graphemes(true)), 1),
ansi::ansi_preserving_slice(
&self.expand_tabs(raw_line.graphemes(true)),
prefix.map(|s| s.len()).unwrap_or(1)
),
)
}
@ -207,11 +215,6 @@ impl<'p> Painter<'p> {
&mut self.output_buffer,
self.config,
&mut self.line_numbers_data.as_mut(),
if self.config.keep_plus_minus_markers {
Some(self.config.minus_style.paint("-"))
} else {
None
},
Some(self.config.minus_empty_line_marker_style),
BgShouldFill::default(),
);
@ -225,11 +228,6 @@ impl<'p> Painter<'p> {
&mut self.output_buffer,
self.config,
&mut self.line_numbers_data.as_mut(),
if self.config.keep_plus_minus_markers {
Some(self.config.plus_style.paint("+"))
} else {
None
},
Some(self.config.plus_empty_line_marker_style),
BgShouldFill::default(),
);
@ -239,16 +237,10 @@ impl<'p> Painter<'p> {
self.plus_lines.clear();
}
pub fn paint_zero_line(&mut self, line: &str) {
let state = State::HunkZero;
let painted_prefix = if self.config.keep_plus_minus_markers && !line.is_empty() {
// A zero line here still contains the " " prefix, so use it.
Some(self.config.zero_style.paint(&line[..1]))
} else {
None
};
let lines = vec![(self.prepare(line), state)];
pub fn paint_zero_line(&mut self, line: &str, prefix: Option<String>) {
let line = self.prepare(line, prefix.as_deref());
let state = State::HunkZero(prefix);
let lines = vec![(line, state.clone())];
let syntax_style_sections = Painter::get_syntax_style_sections_for_lines(
&lines,
self.highlighter.as_mut(),
@ -265,7 +257,7 @@ impl<'p> Painter<'p> {
&mut self.output_buffer,
self.config,
&mut self.line_numbers_data.as_mut(),
painted_prefix,
painted_prefix(state, self.config),
BgShouldFill::With(BgFillMethod::Spaces),
);
} else {
@ -277,7 +269,6 @@ impl<'p> Painter<'p> {
&mut self.output_buffer,
self.config,
&mut self.line_numbers_data.as_mut(),
painted_prefix,
None,
BgShouldFill::With(BgFillMethod::Spaces),
);
@ -295,7 +286,6 @@ impl<'p> Painter<'p> {
output_buffer: &mut String,
config: &config::Config,
line_numbers_data: &mut Option<&mut line_numbers::LineNumbersData>,
painted_prefix: Option<ansi_term::ANSIString>,
empty_line_style: Option<Style>, // a style with background color to highlight an empty line
background_color_extends_to_terminal_width: BgShouldFill,
) {
@ -318,7 +308,7 @@ impl<'p> Painter<'p> {
state,
line_numbers_data,
None,
painted_prefix.clone(),
painted_prefix(state.clone(), config),
config,
);
let (bg_fill_mode, fill_style) =
@ -384,7 +374,6 @@ impl<'p> Painter<'p> {
self.config,
&mut None,
None,
None,
background_color_extends_to_terminal_width,
);
}
@ -399,22 +388,22 @@ impl<'p> Painter<'p> {
config: &config::Config,
) -> (Option<BgFillMethod>, Style) {
let fill_style = match state {
State::HunkMinus(None) | State::HunkMinusWrapped => {
State::HunkMinus(_, None) | State::HunkMinusWrapped => {
if let Some(true) = line_has_homolog {
config.minus_non_emph_style
} else {
config.minus_style
}
}
State::HunkZero | State::HunkZeroWrapped => config.zero_style,
State::HunkPlus(None) | State::HunkPlusWrapped => {
State::HunkZero(_) | State::HunkZeroWrapped => config.zero_style,
State::HunkPlus(_, None) | State::HunkPlusWrapped => {
if let Some(true) = line_has_homolog {
config.plus_non_emph_style
} else {
config.plus_style
}
}
State::HunkMinus(Some(_)) | State::HunkPlus(Some(_)) => {
State::HunkMinus(_, Some(_)) | State::HunkPlus(_, Some(_)) => {
// Consider the following raw line, from git colorMoved:
// ␛[1;36m+␛[m␛[1;36mclass·X:·pass␛[m␊ The last style section returned by
// parse_style_sections will be a default style associated with the terminal newline
@ -480,7 +469,7 @@ impl<'p> Painter<'p> {
state: &State,
line_numbers_data: &mut Option<&mut line_numbers::LineNumbersData>,
side_by_side_panel: Option<PanelSide>,
painted_prefix: Option<ansi_term::ANSIString>,
mut painted_prefix: Option<ansi_term::ANSIString>,
config: &config::Config,
) -> (String, bool) {
let mut ansi_strings = Vec::new();
@ -517,8 +506,8 @@ impl<'p> Painter<'p> {
for (section_style, text) in &superimposed {
// If requested re-insert the +/- prefix with proper styling.
if !handled_prefix {
if let Some(ref painted_prefix) = painted_prefix {
ansi_strings.push(painted_prefix.clone());
if let Some(painted_prefix) = painted_prefix.take() {
ansi_strings.push(painted_prefix)
}
}
@ -545,19 +534,19 @@ impl<'p> Painter<'p> {
return false;
}
match state {
State::HunkMinus(None) => {
State::HunkMinus(_, None) => {
config.minus_style.is_syntax_highlighted
|| config.minus_emph_style.is_syntax_highlighted
|| config.minus_non_emph_style.is_syntax_highlighted
}
State::HunkZero => config.zero_style.is_syntax_highlighted,
State::HunkPlus(None) => {
State::HunkZero(_) => config.zero_style.is_syntax_highlighted,
State::HunkPlus(_, None) => {
config.plus_style.is_syntax_highlighted
|| config.plus_emph_style.is_syntax_highlighted
|| config.plus_non_emph_style.is_syntax_highlighted
}
State::HunkHeader(_, _) => true,
State::HunkMinus(Some(_raw_line)) | State::HunkPlus(Some(_raw_line)) => {
State::HunkHeader(_, _, _) => true,
State::HunkMinus(_, Some(_raw_line)) | State::HunkPlus(_, Some(_raw_line)) => {
// It is possible that the captured raw line contains an ANSI
// style that has been mapped (via map-styles) to a delta Style
// with syntax-highlighting.
@ -568,7 +557,7 @@ impl<'p> Painter<'p> {
State::Grep => true,
State::Unknown
| State::CommitMeta
| State::DiffHeader
| State::DiffHeader(_)
| State::HunkMinusWrapped
| State::HunkZeroWrapped
| State::HunkPlusWrapped
@ -701,7 +690,8 @@ impl<'p> Painter<'p> {
.zip_eq(lines_style_sections)
.zip_eq(lines_have_homolog)
{
if let State::HunkMinus(Some(raw_line)) | State::HunkPlus(Some(raw_line)) = state {
if let State::HunkMinus(_, Some(raw_line)) | State::HunkPlus(_, Some(raw_line)) = state
{
// raw_line is captured in handle_hunk_line under certain conditions. If we have
// done so, then overwrite the style sections with styles parsed directly from the
// raw line. Currently the only reason this is done is to handle a diff.colorMoved
@ -733,6 +723,23 @@ impl<'p> Painter<'p> {
}
}
fn painted_prefix(state: State, config: &config::Config) -> Option<ANSIString> {
match (state, config.keep_plus_minus_markers) {
// If there is Some(prefix) then this is a combined diff. In this case we do not honor
// keep_plus_minus_markers -- i.e. always emit the prefix -- because there is currently no
// way to distinguish, say, a '+ ' line from a ' +' line, by styles alone.
(State::HunkMinus(Some(prefix), _), _) => Some(config.minus_style.paint(prefix)),
(State::HunkZero(Some(prefix)), _) => Some(config.zero_style.paint(prefix)),
(State::HunkPlus(Some(prefix), _), _) => Some(config.plus_style.paint(prefix)),
// But if there is no prefix we honor keep_plus_minus_markers.
(_, false) => None,
(State::HunkMinus(None, _), true) => Some(config.minus_style.paint("-".to_string())),
(State::HunkZero(None), true) => Some(config.zero_style.paint(" ".to_string())),
(State::HunkPlus(None, _), true) => Some(config.plus_style.paint("+".to_string())),
_ => None,
}
}
// Parse ANSI styles encountered in `raw_line` and apply `styles_map`.
pub fn parse_style_sections<'a>(
raw_line: &'a str,

View File

@ -43,7 +43,7 @@ pub fn show_colors() -> std::io::Result<()> {
painter.syntax_highlight_and_paint_line(
line,
paint::StyleSectionSpecifier::Style(style),
delta::State::HunkZero,
delta::State::HunkZero(None),
BgShouldFill::default(),
)
}
@ -62,7 +62,7 @@ pub fn show_colors() -> std::io::Result<()> {
painter.syntax_highlight_and_paint_line(
line,
paint::StyleSectionSpecifier::Style(style),
delta::State::HunkZero,
delta::State::HunkZero(None),
BgShouldFill::default(),
)
}

View File

@ -136,12 +136,6 @@ pub mod ansi_test_utils {
config,
);
let diff_style_sections = vec![vec![(syntax_highlighted_style, lines[0].0.as_str())]];
let prefix = match (&state, config.keep_plus_minus_markers) {
(State::HunkMinus(_), true) => "-",
(State::HunkZero, true) => " ",
(State::HunkPlus(_), true) => "+",
_ => "",
};
paint::Painter::paint_lines(
&lines,
&syntax_style_sections,
@ -150,7 +144,6 @@ pub mod ansi_test_utils {
&mut output_buffer,
config,
&mut None,
Some(config.null_style.paint(prefix)),
None,
paint::BgShouldFill::default(),
);

View File

@ -2,7 +2,7 @@
mod tests {
use crate::ansi::{self, strip_ansi_codes};
use crate::cli::InspectRawLines;
use crate::delta::State;
use crate::delta::{DiffType, State};
use crate::style;
use crate::tests::ansi_test_utils::ansi_test_utils;
use crate::tests::integration_test_utils;
@ -1384,7 +1384,7 @@ src/align.rs:71: impl<'a> Alignment<'a> { │
4,
"impl<'a> Alignment<'a> { ",
"rs",
State::HunkHeader("".to_owned(), "".to_owned()),
State::HunkHeader(DiffType::Unified, "".to_owned(), "".to_owned()),
&config,
);
ansi_test_utils::assert_line_has_no_color(&output, 12, "─────────────────────────────┘");
@ -1539,7 +1539,7 @@ src/align.rs:71: impl<'a> Alignment<'a> { │
1,
" for (i, x_i) in self.x.iter().enumerate() {",
"rs",
State::HunkZero,
State::HunkZero(None),
&config,
);
}

View File

@ -449,13 +449,13 @@ pub fn wrap_minusplus_block<'c: 'a, 'a>(
};
if minus_extended > 0 {
new_states[Left].push(State::HunkMinus(None));
new_states[Left].push(State::HunkMinus(None, None));
for _ in 1..minus_extended {
new_states[Left].push(State::HunkMinusWrapped);
}
}
if plus_extended > 0 {
new_states[Right].push(State::HunkPlus(None));
new_states[Right].push(State::HunkPlus(None, None));
for _ in 1..plus_extended {
new_states[Right].push(State::HunkPlusWrapped);
}