diff options
| author | blasty <blasty@local> | 2026-07-09 15:25:02 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-09 15:25:02 +0200 |
| commit | e3636217535a5ff93f9b448cb1998ed010bfbbba (patch) | |
| tree | f0654dc2d657711ac0f2cbfe36eae1cb4ed6f82b | |
| parent | search: land the cursor on the match's starting column (diff) | |
| download | ida-tui-e3636217535a5ff93f9b448cb1998ed010bfbbba.tar.gz ida-tui-e3636217535a5ff93f9b448cb1998ed010bfbbba.tar.xz ida-tui-e3636217535a5ff93f9b448cb1998ed010bfbbba.zip | |
paging: preserve the cursor's viewport-relative row (IDA-style)
PageUp/Down and Ctrl+U/D now move the scroll top and the cursor by the same
(clamped) delta, so the cursor keeps its relative screen row instead of snapping
to an edge; at the very top/bottom the cursor jumps to the extreme. Shared
_page_scroll in ColumnCursor, used by both views. pilot suite 49/49.
| -rw-r--r-- | idatui/app.py | 37 | ||||
| -rw-r--r-- | tests/test_tui.py | 21 |
2 files changed, 46 insertions, 12 deletions
diff --git a/idatui/app.py b/idatui/app.py index 497f0ac..87420c2 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -301,6 +301,31 @@ class ColumnCursor: if event.chain >= 2: # double-click == place cursor + follow self.post_message(FollowRequested(self)) + # -- paging (keeps the cursor at the same viewport-relative row) ------- # + def _page_scroll(self, lines: int) -> None: + total = self.total + if total <= 0: + return + height = self._visible_height() + top = round(self.scroll_offset.y) + max_top = max(total - height, 0) + new_top = max(0, min(max_top, top + lines)) + delta = new_top - top + if delta == 0: # already at an edge: jump the cursor to the extreme + self.cursor = (total - 1) if lines > 0 else 0 + else: # move cursor by the same delta -> relative row preserved + self.cursor = max(0, min(total - 1, self.cursor + delta)) + self._clamp_x() + self.scroll_to(y=new_top, animate=False) + self.refresh() + self._after_cursor_move() + + def action_page(self, direction: int) -> None: + self._page_scroll(direction * self._visible_height()) + + def action_half_page(self, direction: int) -> None: + self._page_scroll(direction * (self._visible_height() // 2)) + class SearchMixin: """Vim-style in-view search shared by the disasm and pseudocode views. @@ -698,12 +723,6 @@ class DisasmView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True def action_cursor_up(self) -> None: self._move(-1) - def action_half_page(self, direction: int) -> None: - self._move(direction * (self._visible_height() // 2)) - - def action_page(self, direction: int) -> None: - self._move(direction * self._visible_height()) - def action_goto_top(self) -> None: self._move(-self.total) @@ -869,12 +888,6 @@ class DecompView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True def action_cursor_up(self) -> None: self._move(-1) - def action_half_page(self, direction: int) -> None: - self._move(direction * (self._visible_height() // 2)) - - def action_page(self, direction: int) -> None: - self._move(direction * self._visible_height()) - def action_goto_top(self) -> None: self._move(-len(self._strips)) diff --git a/tests/test_tui.py b/tests/test_tui.py index 688df57..47c5a67 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -501,6 +501,27 @@ async def run(db): round(dis.scroll_offset.y) == want_sy and dis.cursor == want_cur, f"scroll={round(dis.scroll_offset.y)} (want {want_sy}) cursor={dis.cursor}") + # PageUp/Down keep the cursor at the same viewport-relative row. + await goto_name(fa.name) + await wait_until(pilot, lambda: dis.total > 100 and app._cur.ea == fa.addr, timeout=20) + if app._active != "disasm": + await pilot.press("tab") + await pilot.pause(0.2) + for _ in range(6): + await pilot.press("j") + await pilot.pause(0.1) + rel = dis.cursor - round(dis.scroll_offset.y) + await pilot.press("pagedown") + await pilot.pause(0.1) + check("PageDown preserves the viewport-relative row", + dis.cursor - round(dis.scroll_offset.y) == rel, + f"rel={dis.cursor - round(dis.scroll_offset.y)} want={rel}") + await pilot.press("pageup") + await pilot.pause(0.1) + check("PageUp preserves the viewport-relative row", + dis.cursor - round(dis.scroll_offset.y) == rel, + f"rel={dis.cursor - round(dis.scroll_offset.y)} want={rel}") + def main(argv): db = None |
