aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--idatui/app.py26
-rw-r--r--tests/test_scenarios.py6
2 files changed, 28 insertions, 4 deletions
diff --git a/idatui/app.py b/idatui/app.py
index 3fa6515..abe50fb 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -3691,18 +3691,37 @@ class IdaTui(App):
self._active = "listing"
self._show_active()
+ # Prompt overlays that own the keyboard while visible; a background
+ # navigation must not yank focus out from under them (else typed keys leak
+ # into a code view as destructive verbs — e.g. 'u' = undefine).
+ _PROMPT_IDS = ("search", "rename", "comment", "retype", "goto", "func-filter")
+
+ def _prompt_active(self) -> bool:
+ for iid in self._PROMPT_IDS:
+ try:
+ if self.query_one(f"#{iid}", Input).display:
+ return True
+ except Exception: # noqa: BLE001 — widget not mounted yet
+ pass
+ return False
+
def _show_active(self) -> None:
dec = self.query_one(DecompView)
lst = self.query_one(ListingView)
hx = self.query_one(HexView)
+ # Don't steal focus from an open prompt (search/rename/…) — a late async
+ # navigation completing here would otherwise pull it into the code view.
+ grab = not self._prompt_active()
dec.display = lst.display = hx.display = False
if self._active in ("listing", "disasm"):
lst.display = True
- lst.focus()
+ if grab:
+ lst.focus()
self._status_for_cur("listing")
elif self._active == "hex":
hx.display = True
- hx.focus()
+ if grab:
+ hx.focus()
ea = getattr(self, "_hex_pending_ea", None)
self._hex_pending_ea = None
if hx.model is None:
@@ -3714,7 +3733,8 @@ class IdaTui(App):
self._hex_status(hx.cursor_va())
else:
dec.display = True
- dec.focus()
+ if grab:
+ dec.focus()
if self._cur is not None and dec.loaded_ea != self._cur.ea:
self._status(f"{self._cur.name} — decompiling…")
dec.loading = True # gray out + 'decompiling…' overlay
diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py
index f72e340..7372c02 100644
--- a/tests/test_scenarios.py
+++ b/tests/test_scenarios.py
@@ -164,8 +164,12 @@ class Ctx:
raise RuntimeError(f"no function for {target!r}")
self.app._open_function(fn.addr, fn.name)
await self.wait(lambda: self.app._cur and self.app._cur.ea == fn.addr, t)
+ # Wait for the cursor to actually LAND on the target, not merely for
+ # _cur.ea to match: re-opening a function we already navigated to (its
+ # _cur.ea is stale-true) schedules an async re-navigation, and proceeding
+ # before it lands would leave the cursor parked wherever we last were.
await self.wait(lambda: self.lst.total > 0
- and self.lst._cursor_ea() is not None, t)
+ and self.lst._cursor_ea() == fn.addr, t)
if view == "decomp":
self.lst.focus()
await self.press("tab") # F5/Tab -> decompile the function at cursor