fix(tui): use updated v_offset after params_changed in ensure_viewport_cache (#24)

The loading branch of ensure_viewport_cache captured v_offset before the
params_changed block, which could reassign self.v_offset. This caused the
viewport to use a stale offset when loading + width/format changed together.

Remove the stale local variable and read self.v_offset directly, consistent
with the non-loading branch. Add regression test.
This commit is contained in:
dailz
2026-06-11 08:58:10 +08:00
parent 463c53148b
commit 70f930eef7

View File

@@ -246,7 +246,6 @@ impl App {
/// Returns (start_logical, offset_in_line) for rendering.
pub(crate) fn ensure_viewport_cache(&mut self, width: usize) -> (usize, usize) {
let viewport_height = self.content_height as usize;
let v_offset = self.v_offset;
if !self.is_loaded() || width == 0 || viewport_height == 0 {
return (0, 0);
@@ -268,7 +267,7 @@ impl App {
// Find start logical line from v_offset
let (start_logical, offset_in_line) = if self.is_loading() {
(v_offset.min(self.total_lines().saturating_sub(1)), self.v_sub_offset)
(self.v_offset.min(self.total_lines().saturating_sub(1)), self.v_sub_offset)
} else {
self.find_logical_line_at_visual_row(self.v_offset, width)
};
@@ -2538,6 +2537,42 @@ plain text line
assert!(result.is_ok());
}
/// Regression test for issue #24:
/// ensure_viewport_cache must use the updated self.v_offset after params_changed
/// recalculates it, not a stale local captured before the change block.
#[test]
fn test_loading_viewport_cache_uses_updated_v_offset_on_params_changed() {
let content: String = (0..200).map(|i| format!("line{i}\n")).collect();
let path = make_temp_file(&content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading(), "should be in Loading state");
app.content_height = 10;
app.ensure_viewport_cache(80);
app.v_offset = 90;
app.cursor_line = 100;
let new_width = 40;
app.ensure_viewport_cache(new_width);
let recomputed_offset = app.v_offset;
assert_ne!(
recomputed_offset, 90,
"v_offset should have been recalculated by params_changed block, still 90"
);
assert_eq!(
app.viewport_cache.logical_start, recomputed_offset.min(app.total_lines().saturating_sub(1)),
"logical_start should match the updated v_offset"
);
});
cleanup(&path);
assert!(result.is_ok());
}
fn install_vhi(app: &mut App, heights: &[usize]) {
let width = app.get_content_width();
let json_format = app.json_format;