From 8add668ad1f10f77132de36b062d16a9e4103989 Mon Sep 17 00:00:00 2001 From: blasty Date: Sun, 26 Jul 2026 09:37:47 +0200 Subject: listing: an edit must not move the view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pressing `c` jumped the scroll position. Defining an item reloads the view, and the reload only carried the cursor's row index — the viewport was recomputed from scratch, so you landed somewhere else and lost your place mid-carve. Row indices are the wrong thing to remember across this reload anyway: carving COLLAPSES rows (four undefined byte rows become one instruction row), so the row that was at the top is a different address afterwards. The anchor has to be the top visible ADDRESS, resolved back to a row after the model is rebuilt. on_edit_item_requested captures it before the edit, _do_edit_item resolves it against the new model, and _open_at grew a scroll_y so the entry can carry it. Verified: cursor at 0x4800 with the top of the screen at 0x47da, press `c`, and both are unchanged afterwards. tests: +3 blob UI (scrolled far enough to have something to lose, top address unchanged, cursor address unchanged). 25/0 blob, 202/0 scenarios. --- tests/test_blob_ui.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'tests') diff --git a/tests/test_blob_ui.py b/tests/test_blob_ui.py index 9161f44..ca4dbb3 100644 --- a/tests/test_blob_ui.py +++ b/tests/test_blob_ui.py @@ -163,6 +163,30 @@ async def run() -> int: m.get(m.index_of_ea(target - 1)).size == 1 and m.get(m.index_of_ea(target - 1)).ea == target - 1) + # -- carving must not move the view -------------------------- # + # Defining code collapses rows (four byte rows become one + # instruction), so anything that remembers a row INDEX puts you + # somewhere else afterwards. Scroll down far enough that there is a + # viewport to lose, then check the top ADDRESS is unchanged. + far = 0x4000 + 0x600 + lst.cursor = lst.model.index_of_ea(far) + lst._scroll_cursor_into_view() + await pilot.pause(0.4) + top_before = lst.model.get(round(lst.scroll_offset.y)).ea + cur_before = lst._cursor_ea() + check("scrolled somewhere with rows above us", + round(lst.scroll_offset.y) > 0, f"top={lst.scroll_offset.y}") + await pilot.press("c") + await pilot.pause(2.5) + m2 = lst.model + top_after = m2.get(round(lst.scroll_offset.y)).ea + check("carving leaves the scroll position where it was", + top_after == top_before, + f"{top_before:#x} -> {top_after:#x}") + check("and leaves the cursor on the same address", + lst._cursor_ea() == cur_before, + f"{cur_before:#x} -> {lst._cursor_ea():#x}") + await pilot.press("ctrl+l") opened = await wait(lambda: isinstance(app.screen, ConfirmScreen), pilot, 20) check("Ctrl+L offers to reload with different options", opened, -- cgit v1.3.1-sl0p