Skip to content

Commit

Permalink
Implement color-moved
Browse files Browse the repository at this point in the history
Fixes #72
  • Loading branch information
dandavison committed Jul 30, 2020
1 parent f135ad1 commit 4750858
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 94 deletions.
18 changes: 18 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@ 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:
Expand Down Expand Up @@ -427,6 +435,16 @@ 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,
Expand Down
55 changes: 55 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ 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,
Expand All @@ -55,8 +56,11 @@ 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 side_by_side: bool,
pub side_by_side_data: side_by_side::SideBySideData,
pub syntax_dummy_theme: SyntaxTheme,
Expand All @@ -73,6 +77,10 @@ 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::CommitMeta => &self.commit_style,
State::FileMeta => &self.file_style,
State::HunkHeader => &self.hunk_header_style,
Expand All @@ -87,11 +95,13 @@ 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);
Expand Down Expand Up @@ -127,6 +137,27 @@ 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,
);

Self {
available_terminal_width: opt.computed.available_terminal_width,
background_color_extends_to_terminal_width: opt
Expand Down Expand Up @@ -158,6 +189,7 @@ 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,
Expand All @@ -167,8 +199,11 @@ 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,
side_by_side: opt.side_by_side,
side_by_side_data,
syntax_dummy_theme: SyntaxTheme::default(),
Expand Down Expand Up @@ -197,6 +232,8 @@ 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;
Expand Down Expand Up @@ -236,6 +273,14 @@ 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(
Expand Down Expand Up @@ -290,6 +335,14 @@ 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(
Expand All @@ -313,11 +366,13 @@ 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,
)
Expand Down
38 changes: 23 additions & 15 deletions src/delta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ use crate::style::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, // In hunk; removed line
HunkPlus, // In hunk; added line
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)
Unknown,
}

Expand All @@ -35,7 +35,7 @@ pub enum Source {
impl State {
fn is_in_hunk(&self) -> bool {
match *self {
State::HunkHeader | State::HunkZero | State::HunkMinus | State::HunkPlus => true,
State::HunkHeader | State::HunkZero | State::HunkMinus(_) | State::HunkPlus(_) => true,
_ => false,
}
}
Expand Down Expand Up @@ -415,7 +415,7 @@ fn handle_hunk_header_line(
};
writeln!(painter.writer)?;
if !line.is_empty() {
let lines = vec![line];
let lines = vec![(line, State::HunkHeader)];
let syntax_style_sections = Painter::get_syntax_style_sections_for_lines(
&lines,
&State::HunkHeader,
Expand All @@ -424,8 +424,8 @@ fn handle_hunk_header_line(
);
Painter::paint_lines(
syntax_style_sections,
vec![vec![(config.hunk_header_style, &lines[0])]],
&State::HunkHeader,
vec![vec![(config.hunk_header_style, &lines[0].0)]], // TODO: compute style from state
[State::HunkHeader].iter(),
&mut painter.output_buffer,
config,
&mut None,
Expand Down Expand Up @@ -499,15 +499,23 @@ fn handle_hunk_line(
}
match line.chars().next() {
Some('-') => {
if state == State::HunkPlus {
if let State::HunkPlus(_) = state {
painter.paint_buffered_minus_and_plus_lines();
}
painter.minus_lines.push(painter.prepare(&line, true));
State::HunkMinus
let is_moved = !config.raw_expected_minus_style.is_applied_to(raw_line);
let state = State::HunkMinus(is_moved);
painter
.minus_lines
.push((painter.prepare(&line, true), state.clone()));
state
}
Some('+') => {
painter.plus_lines.push(painter.prepare(&line, true));
State::HunkPlus
let is_moved = !config.raw_expected_plus_style.is_applied_to(raw_line);
let state = State::HunkPlus(is_moved);
painter
.plus_lines
.push((painter.prepare(&line, true), state.clone()));
state
}
Some(' ') => {
painter.paint_buffered_minus_and_plus_lines();
Expand Down
42 changes: 20 additions & 22 deletions src/edits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ use crate::align;
/// lines. A "line" is a string. An annotated line is a Vec of (op, &str) pairs, where the &str
/// slices are slices of the line, and their concatenation equals the line. Return the input minus
/// and plus lines, in annotated form. Also return a specification of the inferred alignment of
/// minus and plus lines.
/// minus and plus lines. `noop_deletions[i]` is the appropriate deletion operation tag to be used
/// for `minus_lines[i]`; `noop_deletions` is guaranteed to be the same length as `minus_lines`.
/// The equivalent statements hold for `plus_insertions` and `plus_lines`.
pub fn infer_edits<'a, EditOperation>(
minus_lines: &'a [String],
plus_lines: &'a [String],
noop_deletion: EditOperation,
minus_lines: Vec<&'a str>,
plus_lines: Vec<&'a str>,
noop_deletions: Vec<EditOperation>,
deletion: EditOperation,
noop_insertion: EditOperation,
noop_insertions: Vec<EditOperation>,
insertion: EditOperation,
tokenization_regex: &Regex,
max_line_distance: f64,
Expand Down Expand Up @@ -44,9 +46,9 @@ where
);
let (annotated_minus_line, annotated_plus_line, distance) = annotate(
alignment,
noop_deletion,
noop_deletions[minus_index],
deletion,
noop_insertion,
noop_insertions[plus_index],
insertion,
minus_line,
plus_line,
Expand All @@ -59,7 +61,7 @@ where

// Emit as unpaired the plus lines already considered and rejected
for plus_line in &plus_lines[plus_index..(plus_index + considered)] {
annotated_plus_lines.push(vec![(noop_insertion, plus_line)]);
annotated_plus_lines.push(vec![(noop_insertions[plus_index], plus_line)]);
line_alignment.push((None, Some(plus_index)));
plus_index += 1;
}
Expand All @@ -75,12 +77,12 @@ where
}
}
// No homolog was found for minus i; emit as unpaired.
annotated_minus_lines.push(vec![(noop_deletion, minus_line)]);
annotated_minus_lines.push(vec![(noop_deletions[minus_index], minus_line)]);
line_alignment.push((Some(minus_index), None));
}
// Emit any remaining plus lines
for plus_line in &plus_lines[plus_index..] {
annotated_plus_lines.push(vec![(noop_insertion, plus_line)]);
annotated_plus_lines.push(vec![(noop_insertions[plus_index], plus_line)]);
line_alignment.push((None, Some(plus_index)));
plus_index += 1;
}
Expand Down Expand Up @@ -724,20 +726,16 @@ mod tests {
expected_edits: Edits,
max_line_distance: f64,
) {
let minus_lines = minus_lines
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>();
let plus_lines = plus_lines
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>();
let (minus_lines, noop_deletions): (Vec<&str>, Vec<EditOperation>) =
minus_lines.into_iter().map(|s| (s, MinusNoop)).unzip();
let (plus_lines, noop_insertions): (Vec<&str>, Vec<EditOperation>) =
plus_lines.into_iter().map(|s| (s, PlusNoop)).unzip();
let actual_edits = infer_edits(
&minus_lines,
&plus_lines,
MinusNoop,
minus_lines,
plus_lines,
noop_deletions,
Deletion,
PlusNoop,
noop_insertions,
Insertion,
&*DEFAULT_TOKENIZATION_REGEXP,
max_line_distance,
Expand Down
24 changes: 24 additions & 0 deletions src/features/color_moved.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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"
)
])
}
4 changes: 2 additions & 2 deletions src/features/line_numbers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub fn format_and_paint_line_numbers<'a>(
config.line_numbers_plus_style,
);
let ((minus_number, plus_number), (minus_style, plus_style)) = match state {
State::HunkMinus => {
State::HunkMinus(_) => {
let m = *m_ref;
*m_ref += 1;
((Some(m), None), (minus_style, plus_style))
Expand All @@ -87,7 +87,7 @@ pub fn format_and_paint_line_numbers<'a>(
*p_ref += 1;
((Some(m), Some(p)), (zero_style, zero_style))
}
State::HunkPlus => {
State::HunkPlus(_) => {
let p = *p_ref;
*p_ref += 1;
((None, Some(p)), (minus_style, plus_style))
Expand Down
Loading

0 comments on commit 4750858

Please sign in to comment.