pub mod word_diff;
pub use word_diff::{Token, highlight_line_pair};
use crate::model::{DiffHunk, LineOrigin};
pub fn pair_deletions_with_additions(hunk: &DiffHunk) -> Vec<(Option<usize>, Option<usize>)> {
let lines = &hunk.lines;
let mut out: Vec<(Option<usize>, Option<usize>)> = Vec::with_capacity(lines.len());
let mut i = 0;
while i < lines.len() {
match lines[i].origin {
LineOrigin::Deletion => {
let del_start = i;
let mut del_end = i + 1;
while del_end < lines.len() && lines[del_end].origin == LineOrigin::Deletion {
del_end += 1;
}
let add_start = del_end;
let mut add_end = add_start;
while add_end < lines.len() && lines[add_end].origin == LineOrigin::Addition {
add_end += 1;
}
let del_count = del_end - del_start;
let add_count = add_end - add_start;
if del_count == add_count && del_count > 0 {
for off in 0..del_count {
out.push((Some(del_start + off), Some(add_start + off)));
}
} else {
for off in 0..del_count {
out.push((Some(del_start + off), None));
}
for off in 0..add_count {
out.push((None, Some(add_start + off)));
}
}
i = add_end;
}
LineOrigin::Addition => {
out.push((None, Some(i)));
i += 1;
}
LineOrigin::Context => {
out.push((Some(i), None));
i += 1;
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{DiffHunk, DiffLine, LineOrigin};
fn line(origin: LineOrigin, content: &str) -> DiffLine {
DiffLine {
origin,
content: content.to_string(),
old_lineno: None,
new_lineno: None,
highlighted_spans: None,
}
}
fn hunk(lines: Vec<DiffLine>) -> DiffHunk {
DiffHunk {
header: String::new(),
lines,
old_start: 0,
old_count: 0,
new_start: 0,
new_count: 0,
}
}
#[test]
fn pair_deletions_with_additions_pairs_adjacent_runs() {
let h = hunk(vec![
line(LineOrigin::Context, "ctx"),
line(LineOrigin::Deletion, "-a"),
line(LineOrigin::Deletion, "-b"),
line(LineOrigin::Addition, "+A"),
line(LineOrigin::Addition, "+B"),
line(LineOrigin::Context, "ctx2"),
]);
let pairs = pair_deletions_with_additions(&h);
assert_eq!(
pairs,
vec![
(Some(0), None), (Some(1), Some(3)), (Some(2), Some(4)), (Some(5), None), ]
);
}
#[test]
fn pair_deletions_with_additions_bails_on_unequal_counts() {
let h = hunk(vec![
line(LineOrigin::Deletion, "-a"),
line(LineOrigin::Deletion, "-b"),
line(LineOrigin::Addition, "+A"),
]);
let pairs = pair_deletions_with_additions(&h);
assert_eq!(
pairs,
vec![(Some(0), None), (Some(1), None), (None, Some(2))]
);
}
#[test]
fn pair_deletions_with_additions_handles_standalone_additions() {
let h = hunk(vec![
line(LineOrigin::Addition, "+a"),
line(LineOrigin::Context, "ctx"),
]);
let pairs = pair_deletions_with_additions(&h);
assert_eq!(pairs, vec![(None, Some(0)), (Some(1), None)]);
}
#[test]
fn pair_deletions_with_additions_handles_deletion_without_following_addition() {
let h = hunk(vec![
line(LineOrigin::Deletion, "-a"),
line(LineOrigin::Context, "ctx"),
]);
let pairs = pair_deletions_with_additions(&h);
assert_eq!(pairs, vec![(Some(0), None), (Some(1), None)]);
}
#[test]
fn pair_deletions_with_additions_three_way_replace_preserves_order() {
let h = hunk(vec![
line(LineOrigin::Context, "ctx-a"),
line(LineOrigin::Deletion, "-1"),
line(LineOrigin::Deletion, "-2"),
line(LineOrigin::Deletion, "-3"),
line(LineOrigin::Addition, "+1"),
line(LineOrigin::Addition, "+2"),
line(LineOrigin::Addition, "+3"),
line(LineOrigin::Context, "ctx-b"),
]);
let pairs = pair_deletions_with_additions(&h);
assert_eq!(
pairs,
vec![
(Some(0), None), (Some(1), Some(4)), (Some(2), Some(5)), (Some(3), Some(6)), (Some(7), None), ]
);
}
#[test]
fn pair_deletions_with_additions_empty_hunk() {
let h = hunk(Vec::new());
assert!(pair_deletions_with_additions(&h).is_empty());
}
}