fix: retain numbered declarations in reserved sticky rows

This commit is contained in:
Valdemar Lorenzen
2026-10-16 11:29:24 +02:00
parent 7b950210b8
commit c689d9e782
3 changed files with 101 additions and 7 deletions
+99 -7
View File
@@ -3774,6 +3774,7 @@ fn draw_diff(
let viewport = panels
.diff_wrap
.get_or_insert_with(DiffWrapViewport::default);
viewport.lefts = [before_left, after_left];
viewport.update(
diff,
file,
@@ -4561,6 +4562,7 @@ struct DiffWrapViewport {
before: crate::wrap::WrapCache,
after: crate::wrap::WrapCache,
widths: [usize; 2],
lefts: [usize; 2],
heights: [usize; 2],
visible_names: [Vec<Point>; 2],
top: DiffWrapPosition,
@@ -4763,6 +4765,55 @@ impl DiffWrapViewport {
});
}
if scope.iter().any(|side| *side) && self.rows.len() + 1 < height {
if self.rows.is_empty() {
// The sticky slot can still show the adjacent declaration as source.
let previous = self.step(diff, file, position, false);
let declaration = self.row(diff, file, previous);
let visible = |side: usize| {
let focus = if side == 0 {
Focus::Before
} else {
Focus::After
};
let Some(context) = contexts[side] else {
return false;
};
if diff.display_source_row(focus, previous.display)
!= Some(context.name.row)
{
return false;
}
let source = if side == 0 { &file.before } else { &file.after };
let prefix = &source[context.name.row][..context.name.column];
let column = display_column(prefix, usize::MAX);
if declaration.unwrapped {
let pane = if diff.view_mode == ViewMode::Unified {
usize::from(diff.focus != Focus::Before)
} else {
side
};
self.lefts[pane] <= column
&& column
< self.lefts[pane].saturating_add(self.widths[pane])
} else {
declaration.segments[side].is_some_and(|segment| {
segment.start <= column && column < segment.end
})
}
};
let restore = if diff.view_mode == ViewMode::Unified {
let side = usize::from(
diff.display_focus(previous.display) != Some(Focus::Before),
);
scope[side] && visible(side)
} else {
(0..2).all(|side| !scope[side] || visible(side))
};
if previous != position && restore {
self.rows.push(declaration);
continue;
}
}
self.rows.push(WrappedDiffRow {
position,
segments: [None; 2],
@@ -8237,7 +8288,6 @@ mod tests {
Some(2),
"Keep the nearest enclosing function's sticky context, not earlier declarations"
);
assert_eq!(viewport.rows[0].scope, [true; 2]);
}
#[test]
@@ -8402,6 +8452,48 @@ mod tests {
}
}
#[test]
fn diff_scope_sticky_row_keeps_adjacent_declaration_numbered() {
let after = "\n#[allow(clippy::too_many_arguments)]\nfn draw_diff_scope_labels(\n frame: &mut Frame,\n diff: &DiffState,\n) {\n work();\n}\n";
for (mode, axis, wrap) in [
(ViewMode::Split, SplitAxis::Vertical, false),
(ViewMode::Split, SplitAxis::Horizontal, false),
(ViewMode::Split, SplitAxis::Vertical, true),
(ViewMode::Unified, SplitAxis::Vertical, false),
(ViewMode::Unified, SplitAxis::Vertical, true),
] {
let mut file = syntax_diff_file("tui.rs", "", after);
file.hunks = crate::diff::line_hunks(&file.before, false, &file.after, false);
let mut diff = diff_comparison(vec![file]);
diff.files_visible = false;
diff.focus = Focus::After;
diff.split_axis = axis;
diff.word_wrap = wrap;
diff.set_view_mode(mode);
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(160, 24)).unwrap();
let mut panels = WheelPanels::default();
for source in [0, 1, 2, 3, 4, 3, 2] {
diff.top = diff.source_display_row(Focus::After, source).unwrap();
render_diff(&mut terminal, &diff, &mut panels);
let area = diff_inner(panels.diff.as_ref().unwrap().regions[2].0);
let text = pane_screen(&terminal, area);
if source <= 3 {
let declaration = text
.lines()
.find(|line| line.contains("fn draw_diff_scope_labels("));
assert!(
declaration.is_some_and(|line| line.contains("3+")),
"The declaration must keep its source number, not become an unnumbered sticky label at top source {source}:\n{text}"
);
} else {
assert!(text.lines().any(|line| {
line.contains("draw_diff_scope_labels") && !line.contains("fn ")
}));
}
}
}
}
#[test]
fn diff_scope_labels_follow_declaration_visibility() {
let before = "@decorate()\nclass PolicyActiveStatusEnum(BaseEnum):\n ACTIVE = 1\n EXPIRED = 2\n FUTURE = 3\n UNKNOWN = 4\n";
@@ -8420,7 +8512,7 @@ mod tests {
diff.set_view_mode(mode);
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(160, 32)).unwrap();
let mut panels = WheelPanels::default();
for (source, visible) in [(0, false), (2, true), (1, false), (2, true)] {
for (source, visible) in [(0, false), (2, false), (3, true), (1, false), (2, false)] {
diff.top = diff.source_display_row(Focus::After, source).unwrap();
render_diff(&mut terminal, &diff, &mut panels);
let text = screen_text(&terminal);
@@ -8436,7 +8528,7 @@ mod tests {
0
}
);
assert!(text.contains("ACTIVE = 1"));
assert!(text.contains("EXPIRED = 2"));
}
}
let before = "def old_name():\n shared()\n tail()\n";
@@ -8466,7 +8558,7 @@ mod tests {
let mut diff = diff_comparison(vec![file]);
diff.files_visible = false;
diff.set_view_mode(mode);
diff.top = diff.source_display_row(Focus::After, 1).unwrap();
diff.top = diff.source_display_row(Focus::After, 4).unwrap();
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(160, 40)).unwrap();
let mut panels = WheelPanels::default();
render_diff(&mut terminal, &diff, &mut panels);
@@ -8520,7 +8612,7 @@ mod tests {
diff.files_visible = false;
diff.split_axis = axis;
diff.word_wrap = wrapping;
diff.top = diff.source_display_row(Focus::Before, 2).unwrap();
diff.top = diff.source_display_row(Focus::Before, 3).unwrap();
let mut terminal =
Terminal::new(ratatui::backend::TestBackend::new(160, 48)).unwrap();
let mut panels = WheelPanels::default();
@@ -8550,7 +8642,7 @@ mod tests {
}
let syntax = panels.diff_syntax.as_ref().unwrap();
let parses = (syntax.before.parse_counts(), syntax.after.parse_counts());
diff.top = diff.source_display_row(Focus::Before, 2).unwrap();
diff.top = diff.source_display_row(Focus::Before, 3).unwrap();
render_diff(&mut terminal, &diff, &mut panels);
let rows = &panels.diff_wrap.as_ref().unwrap().rows;
assert_eq!(rows[0].scope, [true; 2]);
@@ -8569,7 +8661,7 @@ mod tests {
}
let mut narrow = diff_comparison(vec![syntax_diff_file("scope.rs", before, &after)]);
narrow.files_visible = false;
narrow.top = narrow.source_display_row(Focus::Before, 2).unwrap();
narrow.top = narrow.source_display_row(Focus::Before, 3).unwrap();
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(40, 30)).unwrap();
let mut panels = WheelPanels::default();
render_diff(&mut terminal, &narrow, &mut panels);