Git · DiffViewer: stretches made elsewhere, a hunk button, pickable changed lines, words marked on built lines; its words read i18n

This commit is contained in:
ZacharyZhang-NY committed 2026-10-07 00:14:51 -04:00
1 parent 10788579c9
commit 19b0b2997a
5 files changed
+357 -67

No files matched your search

+84 -14
View File
@@ -124,33 +124,79 @@ pub fn stat(old: &str, new: &str) -> (usize, usize) {
})
}
/// A hunk's lines side by side: unchanged lines on both sides, each run of removals beside the additions after it.
pub fn pairs(lines: &[DiffLine]) -> Vec<(Option<&DiffLine>, Option<&DiffLine>)> {
/// A hunk's rows side by side, as places in `lines`.
pub(crate) fn pair_places(lines: &[DiffLine]) -> Vec<(Option<usize>, Option<usize>)> {
let mut out = Vec::new();
let mut ix = 0;
while ix < lines.len() {
if lines[ix].kind == LineKind::Same {
out.push((Some(&lines[ix]), Some(&lines[ix])));
out.push((Some(ix), Some(ix)));
ix += 1;
continue;
}
let removed: Vec<&DiffLine> = lines[ix..]
.iter()
.take_while(|line| line.kind == LineKind::Removed)
.collect();
ix += removed.len();
let added: Vec<&DiffLine> = lines[ix..]
.iter()
.take_while(|line| line.kind == LineKind::Added)
.collect();
ix += added.len();
let run = |from: usize, kind| {
from..from
+ lines[from..]
.iter()
.take_while(|line| line.kind == kind)
.count()
};
let removed = run(ix, LineKind::Removed);
let added = run(removed.end, LineKind::Added);
ix = added.end;
for row in 0..removed.len().max(added.len()) {
out.push((removed.get(row).copied(), added.get(row).copied()));
let at = |range: &Range<usize>| (row < range.len()).then(|| range.start + row);
out.push((at(&removed), at(&added)));
}
}
out
}
/// A hunk's lines side by side: unchanged lines on both sides, each run of removals beside the additions after it.
pub fn pairs(lines: &[DiffLine]) -> Vec<(Option<&DiffLine>, Option<&DiffLine>)> {
pair_places(lines)
.into_iter()
.map(|(left, right)| (left.map(|ix| &lines[ix]), right.map(|ix| &lines[ix])))
.collect()
}
/// Marks the words each removal and the addition beside it changed.
pub fn mark_words(lines: &mut [DiffLine]) {
for (left, right) in pair_places(lines) {
let (Some(left), Some(right)) = (left, right) else {
continue;
};
if left == right {
continue;
}
let (old, new) = (lines[left].text.clone(), lines[right].text.clone());
let diff = TextDiff::from_unicode_words(old.as_ref(), new.as_ref());
let (mut at, mut words, mut kept) = ((0, 0), (Vec::new(), Vec::new()), false);
for change in diff.iter_all_changes() {
let (value, len) = (change.value(), change.value().len());
let real = !value.trim().is_empty();
match change.tag() {
ChangeTag::Equal => {
kept |= real;
at = (at.0 + len, at.1 + len);
}
ChangeTag::Delete => {
words.0.extend(real.then(|| at.0..at.0 + len));
at.0 += len;
}
ChangeTag::Insert => {
words.1.extend(real.then(|| at.1..at.1 + len));
at.1 += len;
}
}
}
// Lines with nothing in common mark no words.
if kept {
(lines[left].words, lines[right].words) = words;
}
}
}
/// The numbers a side-by-side row shows: the old file's on the left, the new file's on the right.
pub(crate) fn side_numbers(
left: Option<&DiffLine>,
@@ -220,6 +266,30 @@ mod tests {
assert_eq!(sides, [(true, true), (true, true), (false, true)]);
}
#[test]
fn words_are_marked_on_lines_built_elsewhere() {
let line = |kind, text: &str| DiffLine {
kind,
old: None,
new: None,
text: text.to_string().into(),
words: Vec::new(),
};
let mut lines = vec![
line(LineKind::Removed, "let x = 1;"),
line(LineKind::Removed, "abc"),
line(LineKind::Added, "let x = 2;"),
line(LineKind::Added, "xyz"),
];
mark_words(&mut lines);
assert_eq!(&lines[0].text[lines[0].words[0].clone()], "1");
assert_eq!(&lines[2].text[lines[2].words[0].clone()], "2");
assert!(
lines[1].words.is_empty() && lines[3].words.is_empty(),
"nothing shared"
);
}
#[test]
fn each_side_numbers_its_own_file() {
let stretches = diff("a\nb\n", "x\na\nb\n", 3);