diff --git a/src/cli.rs b/src/cli.rs index 0532a6f21..384db0449 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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: @@ -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, diff --git a/src/config.rs b/src/config.rs index 13a0c9c53..4703e38e7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -46,6 +46,7 @@ pub struct Config { pub minus_emph_style: Style, pub minus_empty_line_marker_style: Style, pub minus_file: Option, + pub minus_moved_style: Style, pub minus_non_emph_style: Style, pub minus_style: Style, pub navigate: bool, @@ -55,8 +56,11 @@ pub struct Config { pub plus_emph_style: Style, pub plus_empty_line_marker_style: Style, pub plus_file: Option, + 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, @@ -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, @@ -87,11 +95,13 @@ impl From 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); @@ -127,6 +137,27 @@ impl From 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 @@ -158,6 +189,7 @@ impl From 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, @@ -167,8 +199,11 @@ impl From 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(), @@ -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; @@ -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( @@ -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( @@ -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, ) diff --git a/src/delta.rs b/src/delta.rs index fa36f0161..5b3de991d 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -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, } @@ -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, } } @@ -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, @@ -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, @@ -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(); diff --git a/src/edits.rs b/src/edits.rs index 510b6f765..870ce2d31 100644 --- a/src/edits.rs +++ b/src/edits.rs @@ -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, deletion: EditOperation, - noop_insertion: EditOperation, + noop_insertions: Vec, insertion: EditOperation, tokenization_regex: &Regex, max_line_distance: f64, @@ -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, @@ -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; } @@ -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; } @@ -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::>(); - let plus_lines = plus_lines - .into_iter() - .map(|s| s.to_string()) - .collect::>(); + let (minus_lines, noop_deletions): (Vec<&str>, Vec) = + minus_lines.into_iter().map(|s| (s, MinusNoop)).unzip(); + let (plus_lines, noop_insertions): (Vec<&str>, Vec) = + 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, diff --git a/src/features/color_moved.rs b/src/features/color_moved.rs new file mode 100644 index 000000000..2879fb8dc --- /dev/null +++ b/src/features/color_moved.rs @@ -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" + ) + ]) +} diff --git a/src/features/line_numbers.rs b/src/features/line_numbers.rs index 1543f46dc..092629c12 100644 --- a/src/features/line_numbers.rs +++ b/src/features/line_numbers.rs @@ -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)) @@ -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)) diff --git a/src/features/mod.rs b/src/features/mod.rs index 67dbd6ba5..89081e4fc 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -26,6 +26,10 @@ type OptionValueFunction = Box) -> Provenan // for the option. pub fn make_builtin_features() -> HashMap { vec![ + ( + "color-moved".to_string(), + color_moved::make_feature().into_iter().collect(), + ), ( "color-only".to_string(), color_only::make_feature().into_iter().collect(), @@ -82,6 +86,7 @@ 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 eb224f161..3feb6e903 100644 --- a/src/features/side_by_side.rs +++ b/src/features/side_by_side.rs @@ -179,7 +179,7 @@ fn paint_left_panel_minus_line( line_index, &syntax_style_sections, &diff_style_sections, - &State::HunkMinus, + &State::HunkMinus(false), line_numbers_data, PanelSide::Left, prefix, @@ -190,7 +190,7 @@ fn paint_left_panel_minus_line( panel_line_is_empty, line_index, diff_style_sections, - &State::HunkMinus, + &State::HunkMinus(false), background_color_extends_to_terminal_width, config, ); @@ -211,7 +211,7 @@ fn paint_right_panel_plus_line( line_index, &syntax_style_sections, &diff_style_sections, - &State::HunkPlus, + &State::HunkPlus(false), line_numbers_data, PanelSide::Right, prefix, @@ -222,7 +222,7 @@ fn paint_right_panel_plus_line( panel_line_is_empty, line_index, diff_style_sections, - &State::HunkPlus, + &State::HunkPlus(false), background_color_extends_to_terminal_width, config, ); @@ -295,12 +295,12 @@ fn paint_minus_or_plus_panel_line( ( &syntax_style_sections[index], &diff_style_sections[index], - state, + state.clone(), ) } else { let opposite_state = match *state { - State::HunkMinus => &State::HunkPlus, - State::HunkPlus => &State::HunkMinus, + State::HunkMinus(x) => State::HunkPlus(x), + State::HunkPlus(x) => State::HunkMinus(x), _ => unreachable!(), }; ( @@ -321,14 +321,14 @@ fn paint_minus_or_plus_panel_line( ); // Knock back down spuriously incremented line numbers. See comment above. - match (state, state_for_line_numbers_field) { + match (state, &state_for_line_numbers_field) { (s, t) if s == t => {} - (State::HunkPlus, State::HunkMinus) => { + (State::HunkPlus(_), State::HunkMinus(_)) => { line_numbers_data .as_mut() .map(|d| d.hunk_minus_line_number -= 1); } - (State::HunkMinus, State::HunkPlus) => { + (State::HunkMinus(_), State::HunkPlus(_)) => { line_numbers_data .as_mut() .map(|d| d.hunk_plus_line_number -= 1); @@ -357,7 +357,7 @@ fn right_pad_left_panel_line( // 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(" "), @@ -428,7 +428,7 @@ fn right_fill_right_panel_line( // Emit empty line marker when the panel line is empty but not empty-by-construction. See // parallel comment in `paint_left_panel_minus_line`. match state { - State::HunkPlus => Painter::mark_empty_line( + State::HunkPlus(_) => Painter::mark_empty_line( &config.plus_empty_line_marker_style, panel_line, Some(" "), diff --git a/src/git_config_entry.rs b/src/git_config_entry.rs index f6a594002..2764e2bd5 100644 --- a/src/git_config_entry.rs +++ b/src/git_config_entry.rs @@ -6,11 +6,10 @@ use lazy_static::lazy_static; use regex::Regex; use crate::errors::*; -use crate::style::Style; #[derive(Clone, Debug)] pub enum GitConfigEntry { - Style(Style), + Style(String), GitRemote(GitRemoteRepo), Path(PathBuf), } diff --git a/src/options/set.rs b/src/options/set.rs index 40582f606..9fdb6c278 100644 --- a/src/options/set.rs +++ b/src/options/set.rs @@ -15,7 +15,6 @@ use crate::git_config; use crate::git_config_entry::{self, GitConfigEntry}; use crate::options::option_value::{OptionValue, ProvenancedOptionValue}; use crate::options::theme; -use crate::style::Style; macro_rules! set_options { ([$( $field_ident:ident ),* ], @@ -116,6 +115,9 @@ pub fn set_options( set_options!( [ + color_moved, + color_moved_minus_style, + color_moved_plus_style, color_only, commit_decoration_style, commit_style, @@ -523,18 +525,15 @@ 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"] { + for key in &[ + "color.diff.old", + "color.diff.new", + "color.diff.oldMoved", + "color.diff.newMoved", + ] { if let Some(style_string) = git_config.get::(key) { - opt.git_config_entries.insert( - key.to_string(), - GitConfigEntry::Style(Style::from_str( - &style_string, - None, - None, - opt.computed.true_color, - false, - )), - ); + opt.git_config_entries + .insert(key.to_string(), GitConfigEntry::Style(style_string)); } } diff --git a/src/paint.rs b/src/paint.rs index da158326e..e10b4840f 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -19,8 +19,8 @@ use crate::paint::superimpose_style_sections::superimpose_style_sections; use crate::style::Style; pub struct Painter<'a> { - pub minus_lines: Vec, - pub plus_lines: Vec, + pub minus_lines: Vec<(String, State)>, + pub plus_lines: Vec<(String, State)>, pub writer: &'a mut dyn Write, pub syntax: &'a SyntaxReference, pub highlighter: HighlightLines<'a>, @@ -117,15 +117,16 @@ 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(__), &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(__), &mut self.highlighter, self.config, ); @@ -149,7 +150,7 @@ impl<'a> Painter<'a> { Painter::paint_lines( minus_line_syntax_style_sections, minus_line_diff_style_sections, - &State::HunkMinus, + self.minus_lines.iter().map(|(_, state)| state), &mut self.output_buffer, self.config, &mut Some(&mut self.line_numbers_data), @@ -166,7 +167,7 @@ impl<'a> Painter<'a> { Painter::paint_lines( plus_line_syntax_style_sections, plus_line_diff_style_sections, - &State::HunkPlus, + self.plus_lines.iter().map(|(_, state)| state), &mut self.output_buffer, self.config, &mut Some(&mut self.line_numbers_data), @@ -185,19 +186,20 @@ impl<'a> Painter<'a> { } pub fn paint_zero_line(&mut self, line: &str) { + let state = State::HunkZero; let prefix = if self.config.keep_plus_minus_markers && !line.is_empty() { &line[..1] } else { "" }; - let lines = vec![self.prepare(line, true)]; + let lines = vec![(self.prepare(line, true), state.clone())]; let syntax_style_sections = Painter::get_syntax_style_sections_for_lines( &lines, - &State::HunkZero, + &state, &mut self.highlighter, &self.config, ); - let diff_style_sections = vec![(self.config.zero_style, lines[0].as_str())]; + let diff_style_sections = vec![(self.config.zero_style, lines[0].0.as_str())]; // TODO: compute style from state if self.config.side_by_side { side_by_side::paint_zero_lines_side_by_side( @@ -214,7 +216,7 @@ impl<'a> Painter<'a> { Painter::paint_lines( syntax_style_sections, vec![diff_style_sections], - &State::HunkZero, + [state].iter(), &mut self.output_buffer, self.config, &mut Some(&mut self.line_numbers_data), @@ -230,7 +232,7 @@ impl<'a> Painter<'a> { pub fn paint_lines( syntax_style_sections: Vec>, diff_style_sections: Vec>, - state: &State, + states: impl Iterator, output_buffer: &mut String, config: &config::Config, line_numbers_data: &mut Option<&mut line_numbers::LineNumbersData>, @@ -246,10 +248,11 @@ impl<'a> Painter<'a> { // 2. We must ensure that we fill rightwards with the appropriate // non-emph background color. In that case we don't use the last // style of the line, because this might be emph. - for (syntax_sections, diff_sections) in syntax_style_sections - .iter() - .zip_eq(diff_style_sections.iter()) - { + for (state, (syntax_sections, diff_sections)) in states.zip_eq( + syntax_style_sections + .iter() + .zip_eq(diff_style_sections.iter()), + ) { let (mut line, line_is_empty) = Painter::paint_line( syntax_sections, diff_sections, @@ -293,9 +296,11 @@ 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 => (config.minus_style, config.minus_non_emph_style), + State::HunkMinus(false) => (config.minus_style, config.minus_non_emph_style), State::HunkZero => (config.zero_style, config.zero_style), - State::HunkPlus => (config.plus_style, config.plus_non_emph_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), _ => (config.null_style, config.null_style), }; let fill_style = if style_sections_contain_more_than_one_style(diff_sections) { @@ -393,12 +398,12 @@ impl<'a> Painter<'a> { return false; } match state { - State::HunkMinus => { + State::HunkMinus(_) => { 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(_) => { config.plus_style.is_syntax_highlighted || config.plus_emph_style.is_syntax_highlighted } @@ -411,14 +416,14 @@ impl<'a> Painter<'a> { } pub fn get_syntax_style_sections_for_lines<'s>( - lines: &'s Vec, + lines: &'s Vec<(String, State)>, state: &State, highlighter: &mut HighlightLines, config: &config::Config, ) -> Vec> { let fake = !Painter::should_compute_syntax_highlighting(state, config); let mut line_sections = Vec::new(); - for line in lines.iter() { + for (line, _) in lines.iter() { if fake { line_sections.push(vec![(config.null_syntect_style, line.as_str())]) } else { @@ -430,21 +435,29 @@ impl<'a> Painter<'a> { /// Set background styles to represent diff for minus and plus lines in buffer. fn get_diff_style_sections<'b>( - minus_lines: &'b Vec, - plus_lines: &'b Vec, + minus_lines: &'b Vec<(String, State)>, + plus_lines: &'b Vec<(String, State)>, config: &config::Config, ) -> ( Vec>, Vec>, Vec<(Option, Option)>, ) { + let (minus_lines, minus_styles): (Vec<&str>, Vec