diff options
| author | blasty <blasty@local> | 2026-07-09 14:23:40 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-09 14:23:40 +0200 |
| commit | aa6066117095c375e2037f59f11ec22856d26e09 (patch) | |
| tree | 1e9503dc364bfb2718fa00ce32f708dae127db76 | |
| parent | mouse: click to place cursor, double-click to follow (diff) | |
| download | ida-tui-aa6066117095c375e2037f59f11ec22856d26e09.tar.gz ida-tui-aa6066117095c375e2037f59f11ec22856d26e09.tar.xz ida-tui-aa6066117095c375e2037f59f11ec22856d26e09.zip | |
preserve cursor line+column in the jump history
NavEntry now stores cursor_x too; the top-of-history position is kept current
(line + column) on every disasm cursor move, and DisasmView.load restores the
column (clamped once the line is cached). So Esc after a jump lands exactly on
the symbol you left from, not the line start.
pilot suite 37/37 (adds 'back restores the exact line + column').
| -rw-r--r-- | idatui/app.py | 15 | ||||
| -rw-r--r-- | tests/test_tui.py | 7 |
2 files changed, 18 insertions, 4 deletions
diff --git a/idatui/app.py b/idatui/app.py index 49156e6..22f8157 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -58,7 +58,8 @@ _S_CELL = Style(reverse=True) # the block cursor cell class NavEntry: ea: int name: str - cursor: int = 0 + cursor: int = 0 # line index (disasm instruction index) + cursor_x: int = 0 # column class SearchRequested(Message): @@ -501,12 +502,13 @@ class DisasmView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True return s + line.text # -- public API -------------------------------------------------------- # - def load(self, model: DisasmModel, name: str, cursor: int = 0) -> None: + def load(self, model: DisasmModel, name: str, cursor: int = 0, + cursor_x: int = 0) -> None: self.model = model self._name = name self.total = 0 self.cursor = cursor - self.cursor_x = 0 + self.cursor_x = cursor_x self.virtual_size = Size(0, 0) self._matches = [] self._ranges = {} @@ -567,6 +569,7 @@ class DisasmView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True def _on_primed(self, total: int) -> None: self.total = total self.virtual_size = Size(0, total) + self._clamp_x() # cursor line is now cached; keep the column in range self._scroll_cursor_into_view() self.refresh() @@ -1363,7 +1366,8 @@ class IdaTui(App): return self._cur = entry model = self.program.disasm(entry.ea, entry.name) - self.query_one(DisasmView).load(model, entry.name, cursor=entry.cursor) + self.query_one(DisasmView).load( + model, entry.name, cursor=entry.cursor, cursor_x=entry.cursor_x) self._show_active() def _show_active(self) -> None: @@ -1406,7 +1410,10 @@ class IdaTui(App): def on_disasm_view_cursor_moved(self, msg: DisasmView.CursorMoved) -> None: if self._nav: + # Keep the top-of-history position current (line + column) so that + # returning here later lands exactly where we left. self._nav[-1].cursor = msg.index + self._nav[-1].cursor_x = self.query_one(DisasmView).cursor_x ea = msg.ea if ea is not None: name = self._nav[-1].name if self._nav else "" diff --git a/tests/test_tui.py b/tests/test_tui.py index ef0ffb2..5567eb5 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -329,6 +329,13 @@ async def run(db): await wait_until(pilot, lambda: len(app._nav) > depth, timeout=25) check("double-click follows the symbol", app._cur.ea == want, f"cur={app._cur.ea:#x} want={want:#x}") + # Back restores the exact cursor position (line AND column). + await pilot.press("escape") + await wait_until(pilot, lambda: app._cur.ea != want, timeout=20) + await wait_until(pilot, lambda: dis.total > 0 and dis.cursor == mline, timeout=20) + check("back restores the exact line + column", + dis.cursor == mline and dis.word_under_cursor() == msym, + f"cursor={dis.cursor} (want {mline}) word={dis.word_under_cursor()!r}") def main(argv): |
