mirror of https://github.com/Wilfred/difftastic/
223 lines
6.4 KiB
Rust
223 lines
6.4 KiB
Rust
//! Inline, or "unified" diff display.
|
|
|
|
use std::collections::HashSet;
|
|
|
|
use crate::{
|
|
lines::{format_line_num, LineGroup, LineNumber},
|
|
style::apply_colors,
|
|
syntax::{MatchKind, MatchedPos},
|
|
};
|
|
use colored::*;
|
|
|
|
// TODO: This will iterate over all changes in the file for every
|
|
// hunk, which is quadratic and silly.
|
|
fn last_lhs_context_line(
|
|
lhs_hunk_start: LineNumber,
|
|
lhs_hunk_end: LineNumber,
|
|
lhs_positions: &[MatchedPos],
|
|
rhs_positions: &[MatchedPos],
|
|
) -> LineNumber {
|
|
// If we have changes on the LHS, our before context stops on the
|
|
// line before the first change in this hunk.
|
|
for lhs_position in lhs_positions {
|
|
if lhs_position.kind.is_unchanged() {
|
|
continue;
|
|
}
|
|
|
|
if lhs_position.pos.line.0 > lhs_hunk_end.0 {
|
|
break;
|
|
}
|
|
|
|
if lhs_position.pos.line.0 > lhs_hunk_start.0 {
|
|
return (lhs_position.pos.line.0 - 1).into();
|
|
}
|
|
}
|
|
|
|
// If we don't have changes on the LHS, find the line opposite the
|
|
// last RHS unchanged node in this hunk.
|
|
for rhs_position in rhs_positions {
|
|
let opposite_pos = match &rhs_position.kind {
|
|
MatchKind::Unchanged { opposite_pos, .. } => opposite_pos.0.clone(),
|
|
MatchKind::Novel { .. } | MatchKind::ChangedCommentPart { .. } => continue,
|
|
MatchKind::UnchangedCommentPart { opposite_pos } => opposite_pos.clone(),
|
|
};
|
|
|
|
if let Some(pos) = opposite_pos.first() {
|
|
if pos.line.0 > lhs_hunk_end.0 {
|
|
break;
|
|
}
|
|
|
|
if pos.line.0 > lhs_hunk_start.0 {
|
|
return pos.line;
|
|
}
|
|
}
|
|
}
|
|
|
|
panic!("Could not find LHS context line");
|
|
}
|
|
|
|
fn first_rhs_context_line(
|
|
rhs_hunk_start: LineNumber,
|
|
rhs_hunk_end: LineNumber,
|
|
// TODO: consider "lhs_matches" to avoid confusion with positions
|
|
// when we're dealing with MatchedPos values.
|
|
lhs_positions: &[MatchedPos],
|
|
rhs_positions: &[MatchedPos],
|
|
) -> LineNumber {
|
|
// If we have changes on the RHS, our after context starts on the line
|
|
// after the last change in this hunk.
|
|
let mut last_change_line = None;
|
|
for rhs_position in rhs_positions {
|
|
if rhs_position.kind.is_unchanged() {
|
|
continue;
|
|
}
|
|
|
|
if rhs_position.pos.line.0 > rhs_hunk_end.0 {
|
|
break;
|
|
}
|
|
|
|
if rhs_position.pos.line.0 > rhs_hunk_start.0 {
|
|
last_change_line = Some(rhs_position.pos.line);
|
|
}
|
|
}
|
|
|
|
if let Some(last_change_line) = last_change_line {
|
|
return (last_change_line.0 + 1).into();
|
|
}
|
|
|
|
// If we don't have changes on the RHS, find the line opposite the
|
|
// first unchanged LHS syntax node after the changed nodes.
|
|
let mut lhs_rev_positions: Vec<_> = lhs_positions.into();
|
|
lhs_rev_positions.reverse();
|
|
for lhs_position in lhs_rev_positions {
|
|
let opposite_pos = match lhs_position.kind {
|
|
MatchKind::Unchanged { opposite_pos, .. } => opposite_pos.0,
|
|
// TODO: handle UnchangedCommentPart here too?
|
|
_ => break,
|
|
};
|
|
|
|
if let Some(pos) = opposite_pos.first() {
|
|
last_change_line = Some(pos.line);
|
|
}
|
|
}
|
|
|
|
last_change_line.expect("Should have found an opposite LHS line")
|
|
}
|
|
|
|
fn changed_lines(
|
|
hunk_start: LineNumber,
|
|
hunk_end: LineNumber,
|
|
positions: &[MatchedPos],
|
|
) -> Vec<LineNumber> {
|
|
let mut lines_seen = HashSet::new();
|
|
let mut res = vec![];
|
|
|
|
for position in positions {
|
|
if position.kind.is_unchanged() {
|
|
continue;
|
|
}
|
|
|
|
if position.pos.line.0 > hunk_end.0 {
|
|
break;
|
|
}
|
|
if position.pos.line.0 >= hunk_start.0 {
|
|
if !lines_seen.contains(&position.pos.line) {
|
|
lines_seen.insert(position.pos.line);
|
|
res.push(position.pos.line);
|
|
}
|
|
}
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
pub fn display(
|
|
lhs_src: &str,
|
|
rhs_src: &str,
|
|
lhs_positions: &[MatchedPos],
|
|
rhs_positions: &[MatchedPos],
|
|
groups: &[LineGroup],
|
|
) -> String {
|
|
let lhs_colored = apply_colors(lhs_src, true, lhs_positions);
|
|
let rhs_colored = apply_colors(rhs_src, false, rhs_positions);
|
|
|
|
let lhs_lines: Vec<_> = lhs_colored.lines().collect();
|
|
let rhs_lines: Vec<_> = rhs_colored.lines().collect();
|
|
|
|
let mut res = String::new();
|
|
|
|
for group in groups {
|
|
let lhs_context_last = last_lhs_context_line(
|
|
// TODO: Use non-empty vectors in LineGroup.
|
|
*group.lhs_lines().first().unwrap(),
|
|
*group.lhs_lines().last().unwrap(),
|
|
lhs_positions,
|
|
rhs_positions,
|
|
);
|
|
for lhs_line_num in group.lhs_lines() {
|
|
if lhs_line_num.0 <= lhs_context_last.0 {
|
|
res.push_str(&format_line_num(lhs_line_num));
|
|
res.push_str(" ");
|
|
|
|
res.push_str(lhs_lines[lhs_line_num.0]);
|
|
res.push('\n');
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
for lhs_line_num in changed_lines(
|
|
*group.lhs_lines().first().unwrap(),
|
|
*group.lhs_lines().last().unwrap(),
|
|
lhs_positions,
|
|
) {
|
|
res.push_str(
|
|
&format_line_num(lhs_line_num)
|
|
.bright_red()
|
|
.bold()
|
|
.to_string(),
|
|
);
|
|
res.push_str(" ");
|
|
|
|
res.push_str(lhs_lines[lhs_line_num.0]);
|
|
res.push('\n');
|
|
}
|
|
|
|
for rhs_line_num in changed_lines(
|
|
*group.rhs_lines().first().unwrap(),
|
|
*group.rhs_lines().last().unwrap(),
|
|
rhs_positions,
|
|
) {
|
|
res.push_str(" ");
|
|
res.push_str(
|
|
&format_line_num(rhs_line_num)
|
|
.bright_green()
|
|
.bold()
|
|
.to_string(),
|
|
);
|
|
|
|
res.push_str(rhs_lines[rhs_line_num.0]);
|
|
res.push('\n');
|
|
}
|
|
|
|
let rhs_context_first = first_rhs_context_line(
|
|
*group.rhs_lines().first().unwrap(),
|
|
*group.rhs_lines().last().unwrap(),
|
|
lhs_positions,
|
|
rhs_positions,
|
|
);
|
|
for rhs_line_num in group.rhs_lines() {
|
|
if rhs_line_num.0 >= rhs_context_first.0 {
|
|
res.push_str(" ");
|
|
res.push_str(&format_line_num(rhs_line_num));
|
|
|
|
res.push_str(rhs_lines[rhs_line_num.0]);
|
|
res.push('\n');
|
|
}
|
|
}
|
|
|
|
res.push('\n');
|
|
}
|
|
|
|
res
|
|
}
|