fix: skip omitted diff lines instead of expanding excerpts during navigation

This commit is contained in:
2026-10-11 03:02:47 +02:00
parent 54bb196192
commit 86d22d4fa2
5 changed files with 302 additions and 79 deletions
+31 -35
View File
@@ -629,28 +629,24 @@ impl DiffState {
})
}
pub fn reveal_after(&mut self, caret_row: usize) {
let Some(index) = self
.rows
pub(crate) fn after_rows(&self) -> impl DoubleEndedIterator<Item = usize> + '_ {
self.excerpt_ranges
.iter()
.position(|row| row.after == Some(caret_row))
else {
return;
};
if self.after_display_row(caret_row).is_some() {
return;
.flat_map(|&(start, end)| self.rows[start..end].iter().filter_map(|row| row.after))
}
pub(crate) fn clamp_after_row(&self, row: usize) -> Option<usize> {
let mut previous = None;
for visible in self.after_rows() {
if visible >= row {
return Some(match previous {
Some(previous) if row - previous < visible - row => previous,
_ => visible,
});
}
previous = Some(visible);
}
let anchor = self.display_rows.get(self.top).copied();
self.excerpt_ranges.push((
index.saturating_sub(self.context_lines),
index
.saturating_add(self.context_lines)
.saturating_add(1)
.min(self.rows.len()),
));
self.excerpt_ranges.sort_unstable();
self.rebuild_display_rows();
self.restore_top(anchor);
previous
}
fn restore_top(&mut self, anchor: Option<DisplayRow>) {
@@ -670,12 +666,11 @@ impl DiffState {
}
}
pub fn refresh_after(&mut self, lines: &[String], revision: u64, caret_row: usize) {
pub fn refresh_after(&mut self, lines: &[String], revision: u64) {
if !self.after_editable() {
return;
}
if self.live_revision == Some(revision) {
self.reveal_after(caret_row);
return;
}
let file = &self.comparison.as_ref().unwrap().files[self.file];
@@ -761,7 +756,6 @@ impl DiffState {
{
self.restore_top(Some(DisplayRow::Line(index)));
}
self.reveal_after(caret_row);
if was_changed != changed {
self.refresh_files();
}
@@ -2039,7 +2033,7 @@ mod tests {
}
#[test]
fn live_reflow_keeps_separate_hunks_expansion_and_caret_after_revert() {
fn live_reflow_keeps_separate_hunks_and_expanded_context_after_revert() {
let base = (0..100)
.map(|row| format!("line {row}"))
.collect::<Vec<_>>();
@@ -2049,7 +2043,7 @@ mod tests {
let mut state = live_state(base.clone(), changed.clone(), true);
state.set_context_options(2, 5);
changed.push(String::new());
state.refresh_after(&changed, 1, 30);
state.refresh_after(&changed, 1);
assert_eq!(state.comparison.as_ref().unwrap().files[0].hunks.len(), 2);
assert!(
state
@@ -2061,7 +2055,7 @@ mod tests {
state.handle(Input::Char('['));
assert!(state.after_display_row(23).is_some());
changed.insert(10, "inserted outside excerpts".into());
state.refresh_after(&changed, 2, 31);
state.refresh_after(&changed, 2);
assert!(state.after_display_row(24).is_some());
assert_eq!(state.comparison.as_ref().unwrap().files[0].hunks.len(), 3);
assert!(
@@ -2071,14 +2065,15 @@ mod tests {
);
let mut reverted = base;
reverted.push(String::new());
state.refresh_after(&reverted, 3, 90);
state.refresh_after(&reverted, 3);
assert!(state.comparison.as_ref().unwrap().files[0].hunks.is_empty());
assert!(state.rows.iter().all(|row| row.kind == RowKind::Equal));
assert!(state.after_display_row(90).is_some());
assert!(state.after_display_row(23).is_some());
assert!(state.after_display_row(90).is_none());
assert_eq!(state.files.as_ref().unwrap().entries().len(), 0);
assert_eq!(state.file, 0);
reverted[50] = "changed again".into();
state.refresh_after(&reverted, 4, 50);
state.refresh_after(&reverted, 4);
assert_eq!(state.files.as_ref().unwrap().entries().len(), 1);
assert!(state.after_display_row(50).is_some());
}
@@ -2086,12 +2081,13 @@ mod tests {
#[test]
fn live_empty_and_eof_rows_match_buffer_without_false_eof_highlights() {
let mut state = live_state(vec!["same".into()], vec!["changed".into()], true);
state.refresh_after(&["same".into(), "".into()], 1, 1);
state.refresh_after(&["same".into(), "".into()], 1);
assert!(state.comparison.as_ref().unwrap().files[0].hunks.is_empty());
assert_eq!(state.rows.len(), 2);
assert!(state.rows.iter().all(|row| row.kind == RowKind::Equal));
assert!(state.after_display_row(1).is_some());
state.refresh_after(&["same".into()], 2, 0);
assert!(state.after_display_row(0).is_some());
assert!(state.after_display_row(1).is_none());
state.refresh_after(&["same".into()], 2);
assert_eq!(state.rows[0].kind, RowKind::Changed);
assert!(
state.comparison.as_ref().unwrap().files[0]
@@ -2100,17 +2096,17 @@ mod tests {
.unwrap()
.contains("After missing")
);
state.refresh_after(&["".into()], 3, 0);
state.refresh_after(&["".into()], 3);
assert!(state.after_display_row(0).is_some());
assert_eq!(state.comparison.as_ref().unwrap().files[0].after, [""]);
let mut empty = live_state(vec![], vec!["text".into()], false);
empty.refresh_after(&["".into()], 1, 0);
empty.refresh_after(&["".into()], 1);
assert!(empty.comparison.as_ref().unwrap().files[0].hunks.is_empty());
assert_eq!(empty.rows.len(), 1);
assert_eq!(empty.rows[0].kind, RowKind::Equal);
assert_eq!(empty.after_display_row(0), Some(0));
empty.after.as_mut().unwrap().target = RevisionTarget::Commit("historic".into());
empty.refresh_after(&["must not apply".into()], 2, 0);
empty.refresh_after(&["must not apply".into()], 2);
assert_eq!(empty.comparison.as_ref().unwrap().files[0].after, [""]);
empty.handle(Input::Escape);
assert_eq!(empty.phase, Phase::View);