aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/app.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-09 22:40:29 +0200
committerblasty <blasty@local>2026-07-09 22:40:29 +0200
commit08cd9409b1d30da6a0439144a295c47c2a021827 (patch)
tree674f5296824e529e486a3e48f0c81f3536df42f3 /idatui/app.py
parentxrefs: informative labels — function+offset, and section instead of '?' (diff)
downloadida-tui-08cd9409b1d30da6a0439144a295c47c2a021827.tar.gz
ida-tui-08cd9409b1d30da6a0439144a295c47c2a021827.tar.xz
ida-tui-08cd9409b1d30da6a0439144a295c47c2a021827.zip
xrefs: pre-select the site the dialog was invoked from
Opening the xref dialog with 'x' now highlights the entry for the current site (the instruction/pseudocode line under the cursor), so a long xref list can be stepped through systematically without losing your place. Capture the cursor's ea span when 'x' is pressed (disasm: the instruction .. the next; decomp: the line's /*0xEA*/ anchor .. the next), and pre-select the xref whose frm matches exactly, else the one within that span. Threaded through _xrefs_{disasm,decomp} -> _xrefs_present -> XrefsScreen(preselect), which sets OptionList.highlighted (also scrolls it into view). Pilot check for the decomp path. full suite 68/68.
Diffstat (limited to 'idatui/app.py')
-rw-r--r--idatui/app.py61
1 files changed, 49 insertions, 12 deletions
diff --git a/idatui/app.py b/idatui/app.py
index c1b98a5..4d2b7a7 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -1021,10 +1021,12 @@ class XrefsScreen(ModalScreen):
BINDINGS = [Binding("escape", "close", "Close")]
- def __init__(self, label: str, items: list[tuple[int, str]]) -> None:
+ def __init__(self, label: str, items: list[tuple[int, str]],
+ preselect: int = 0) -> None:
super().__init__()
self._label = label
self._items = items
+ self._preselect = preselect
def compose(self) -> ComposeResult:
with Vertical(id="xref-box"):
@@ -1032,7 +1034,12 @@ class XrefsScreen(ModalScreen):
yield OptionList(*[Option(text) for _, text in self._items], id="xref-list")
def on_mount(self) -> None:
- self.query_one(OptionList).focus()
+ ol = self.query_one(OptionList)
+ # Start on the site we invoked xrefs from, so stepping a long list keeps
+ # your place (and scroll it into view).
+ if 0 <= self._preselect < len(self._items):
+ ol.highlighted = self._preselect
+ ol.focus()
def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
self.dismiss(self._items[event.option_index][0])
@@ -1378,9 +1385,20 @@ class IdaTui(App):
if isinstance(view, DisasmView):
ea = view._cursor_ea()
if ea is not None:
- self._xrefs_disasm(ea, word)
+ # span = this instruction .. the next, to pre-select the dialog
+ # entry for the site we invoked xrefs from.
+ nxt = view.model.cached_line(view.cursor + 1) if view.model else None
+ self._xrefs_disasm(ea, word, ea, nxt.ea if nxt else None)
elif isinstance(view, DecompView) and view._texts:
- self._xrefs_decomp(view._texts[view.cursor], word)
+ here = view._line_ea(view.cursor)
+ end = None
+ if here is not None:
+ for j in range(view.cursor + 1, len(view._line_eas)):
+ e = view._line_eas[j]
+ if e is not None and e > here:
+ end = e
+ break
+ self._xrefs_decomp(view._texts[view.cursor], word, here, end)
@staticmethod
def _looks_like_symbol(word: str | None) -> bool:
@@ -1454,7 +1472,8 @@ class IdaTui(App):
return None
@work(thread=True, group="xrefs")
- def _xrefs_disasm(self, ea: int, word: str | None) -> None:
+ def _xrefs_disasm(self, ea: int, word: str | None,
+ here_ea: int | None = None, here_end: int | None = None) -> None:
assert self.program is not None
subj: int | None = None
if self._looks_like_symbol(word):
@@ -1468,10 +1487,11 @@ class IdaTui(App):
# fall-through to the next instruction, which would point xrefs at
# the wrong place. At a function entry, xrefs-to == the callers.)
subj = ea
- self._xrefs_present(subj, word)
+ self._xrefs_present(subj, word, here_ea, here_end)
@work(thread=True, group="xrefs")
- def _xrefs_decomp(self, line: str, word: str | None) -> None:
+ def _xrefs_decomp(self, line: str, word: str | None,
+ here_ea: int | None = None, here_end: int | None = None) -> None:
subj: int | None = None
if self._cur is not None and word:
dec = self.program.decompile(self._cur.ea)
@@ -1484,9 +1504,25 @@ class IdaTui(App):
if subj is None:
subj = self._ref_on_line(line) or (self._cur.ea if self._cur else None)
if subj is not None:
- self._xrefs_present(subj, word)
+ self._xrefs_present(subj, word, here_ea, here_end)
- def _xrefs_present(self, subj: int, subj_name: str | None = None) -> None: # worker
+ @staticmethod
+ def _xref_preselect(xr, here_ea, here_end) -> int: # type: ignore[no-untyped-def]
+ """Index of the xref whose `frm` is the site we invoked xrefs from: an
+ exact match, else the one falling in the current line/instruction span."""
+ if here_ea is None:
+ return 0
+ span = None
+ for i, x in enumerate(xr):
+ if x.frm == here_ea:
+ return i
+ if span is None and here_end is not None and here_ea <= x.frm < here_end:
+ span = i
+ return span if span is not None else 0
+
+ def _xrefs_present(self, subj: int, subj_name: str | None = None,
+ here_ea: int | None = None,
+ here_end: int | None = None) -> None: # worker
assert self.program is not None
xr = self.program.xrefs_to(subj)
fn = self.program.function_of(subj)
@@ -1513,16 +1549,17 @@ class IdaTui(App):
# data slot or a loose thunk) instead of a bare '?'.
loc = self.program.section_of(x.frm) or "<no seg>"
items.append((x.frm, f"{x.frm:08X} {loc:<26} [{x.type}]"))
- self.app.call_from_thread(self._present_xrefs, label, items, focus)
+ preselect = self._xref_preselect(xr, here_ea, here_end)
+ self.app.call_from_thread(self._present_xrefs, label, items, focus, preselect)
def _present_xrefs(self, label: str, items: list[tuple[int, str]],
- focus_name: str | None = None) -> None:
+ focus_name: str | None = None, preselect: int = 0) -> None:
if not items:
self._status(f"{label}: none")
return
self._xref_focus_name = focus_name
self._status(f"{label}: {len(items)}")
- self.push_screen(XrefsScreen(label, items), self._on_xref_chosen)
+ self.push_screen(XrefsScreen(label, items, preselect), self._on_xref_chosen)
def _on_xref_chosen(self, addr: int | None) -> None:
if addr is not None: