mirror of
https://github.com/dandavison/delta.git
synced 2024-10-04 20:07:18 +03:00
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:
parent
f73f0a8cbd
commit
422954164f
@ -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."),
|
||||
}
|
||||
|
34
src/delta.rs
34
src/delta.rs
@ -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")
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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!(),
|
||||
};
|
||||
};
|
||||
|
@ -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 ")
|
||||
|
@ -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() {
|
||||
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()?;
|
||||
|
101
src/paint.rs
101
src/paint.rs
@ -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,
|
||||
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
@ -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(),
|
||||
);
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user