aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-09 21:18:24 +0200
committerblasty <blasty@local>2026-07-09 21:18:24 +0200
commitd7003527f15398e8bed4227c07302489c650fa2b (patch)
treeedce0fcb1695442c4a73a9909511b3bde80ccc62
parentdecompiler: hide the per-line /*0xEA*/ address markers (clutter) (diff)
downloadida-tui-d7003527f15398e8bed4227c07302489c650fa2b.tar.gz
ida-tui-d7003527f15398e8bed4227c07302489c650fa2b.tar.xz
ida-tui-d7003527f15398e8bed4227c07302489c650fa2b.zip
decompiler: xref jump within the current function moves the cursor
The earlier xref->pseudocode-line fix only worked when the jump target was in a DIFFERENT function (which reloads the pane). When the target was inside the function already displayed, _show_active skipped the reload -> and never applied the new dec_cursor, so the cursor stayed put (the reported 'jumping to xref in decompile view doesn't work'; most xrefs land in the shown function). _open_entry now detects the already-loaded case and repositions the decompiler via a new DecompView.goto (moves cursor/scroll on the loaded text, no re-highlight). Toggle (Tab) still goes through _show_active untouched. Add a same-function xref-jump pilot check. full suite 64/64.
-rw-r--r--idatui/app.py27
-rw-r--r--tests/test_tui.py19
2 files changed, 46 insertions, 0 deletions
diff --git a/idatui/app.py b/idatui/app.py
index 0e68f47..6356a1b 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -878,6 +878,25 @@ class DecompView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True
self._hscroll()
self.refresh()
+ def goto(self, cursor: int, cursor_x: int = 0, scroll_y: int = -1,
+ scroll_x: int = 0) -> None:
+ """Move the cursor/scroll on the already-loaded text (no re-highlight).
+ Used to jump to a target inside the function already displayed, e.g. an
+ xref/goto that resolves to this same function."""
+ total = len(self._strips)
+ if total == 0:
+ return
+ self.cursor = max(0, min(total - 1, cursor))
+ self.cursor_x = cursor_x
+ self._clamp_x()
+ if scroll_y >= 0:
+ self._apply_scroll(min(scroll_y, max(total - 1, 0)), scroll_x)
+ else:
+ self._scroll_cursor_into_view()
+ self._hscroll()
+ self.refresh()
+ self._after_cursor_move()
+
def get_loading_widget(self): # type: ignore[override]
# Shown (grayed, centered) while a (re)decompile is in flight.
return Static("― decompiling… ―", classes="decomp-loading")
@@ -1872,7 +1891,15 @@ class IdaTui(App):
sy = entry.scroll_y if entry.scroll_y >= 0 else None
self.query_one(DisasmView).load(
model, entry.name, cursor=entry.cursor, cursor_x=entry.cursor_x, scroll_y=sy)
+ dec = self.query_one(DecompView)
+ # If the decompiler already shows this function, _show_active won't reload
+ # it (and thus won't reposition), so move its cursor to the target line
+ # here — e.g. an xref/goto whose target is inside the current function.
+ reposition_dec = dec.loaded_ea == entry.ea
self._show_active()
+ if reposition_dec and self._active == "decomp":
+ dsy = entry.dec_scroll_y if entry.dec_scroll_y >= 0 else -1
+ dec.goto(entry.dec_cursor, entry.dec_cursor_x, dsy, entry.dec_scroll_x)
def _show_active(self) -> None:
dis = self.query_one(DisasmView)
diff --git a/tests/test_tui.py b/tests/test_tui.py
index 2f80270..1641368 100644
--- a/tests/test_tui.py
+++ b/tests/test_tui.py
@@ -383,6 +383,25 @@ async def run(db):
await pilot.pause(0.1)
check("xref-select lands on the reference LINE in pseudocode",
dec.cursor == dexp, f"dec.cursor={dec.cursor} want={dexp}")
+
+ # And a jump whose target is INSIDE the already-displayed
+ # function must still move the cursor (the decompiler pane
+ # isn't reloaded in that case, so it needs explicit repos).
+ anch = [(i, dec._line_ea(i)) for i in range(len(dec._texts))
+ if dec._line_ea(i) is not None]
+ if len(anch) >= 4:
+ start_ln = anch[1][0]
+ far_ea = anch[len(anch) // 2][1]
+ exp2 = app._decomp_line_for(app._cur.ea, far_ea)
+ dec.focus()
+ dec.cursor = start_ln
+ dec.refresh()
+ await pilot.pause(0.05)
+ app._on_xref_chosen(far_ea) # target within this function
+ await wait_until(pilot, lambda: dec.cursor == exp2, timeout=20)
+ check("xref-select moves the cursor within the same function",
+ dec.cursor == exp2 and exp2 != start_ln,
+ f"dec.cursor={dec.cursor} want={exp2} start={start_ln}")
app._active = "disasm" # restore for the following blocks
app._show_active()
await pilot.pause(0.05)