aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--idatui/app.py17
-rw-r--r--idatui/rpc.py2
-rw-r--r--tests/test_scenarios.py17
3 files changed, 29 insertions, 7 deletions
diff --git a/idatui/app.py b/idatui/app.py
index a4a5bdd..ff383be 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -105,7 +105,6 @@ class BinaryState:
func_index: object | None = None
nav: list = field(default_factory=list)
cur: object | None = None
- pref: str = "listing"
active: str = "listing"
split: bool = False
filter_term: str = ""
@@ -3201,7 +3200,9 @@ class IdaTui(App):
self._filter_timer = None
self._sort_col = 0 # 0=addr, 1=name, 2=size
self._sort_reverse = False
- self._pref = "listing" # unified: the linear listing is the code view
+ # ONE notion of "which pane you're in": _active, kept in step with focus
+ # (on_descendant_focus does that while split). There used to be a second,
+ # _pref, but it was only ever assigned "listing" — see _code_mode().
self._active = "listing" # currently shown view (in split: the focused pane)
self._split = False # side-by-side listing + pseudocode
self._split_eamap: list[list[int]] = [] # split: decomp line -> instr EAs
@@ -3774,7 +3775,7 @@ class IdaTui(App):
self._states[self._binary] = BinaryState(
label=self._binary, program=self.program,
func_index=self._func_index, nav=list(self._nav), cur=self._cur,
- pref=self._pref, active=self._active, split=self._split,
+ active=self._active, split=self._split,
filter_term=self._filter_term, dirty=self._dirty)
self._loading_screen = LoadingScreen(label, note="switching\u2026")
self.push_screen(self._loading_screen)
@@ -3805,7 +3806,6 @@ class IdaTui(App):
self._binary = label
self._pool.set_active(label)
self._open_path = self._project.by_label(label).staged
- self._pref = st.pref if st else "listing"
self._active = st.active if st else "listing"
self._split = st.split if st else False
self._filter_term = st.filter_term if st else ""
@@ -5210,7 +5210,12 @@ class IdaTui(App):
self.query_one("#func-table", DataTable).focus()
def _code_view(self): # type: ignore[no-untyped-def]
- return self.query_one(DecompView if self._pref == "decomp" else ListingView)
+ """The code pane the user is in — where focus belongs after a prompt closes.
+
+ This used to pick on _pref, which was always "listing", so closing the
+ goto prompt while reading pseudocode threw focus into the listing.
+ """
+ return self._active_code_view()
def _active_code_view(self): # type: ignore[no-untyped-def]
"""The currently-shown code widget (for reading the cursor address)."""
@@ -5472,7 +5477,7 @@ class IdaTui(App):
self._show_active()
def on_hex_view_to_code(self, msg: HexView.ToCode) -> None:
- self._active = self._pref
+ self._active = self._code_mode()
self._goto_ea(msg.va, push=True)
def _status_for_cur(self, mode: str) -> None:
diff --git a/idatui/rpc.py b/idatui/rpc.py
index 59eec86..dd0c9c6 100644
--- a/idatui/rpc.py
+++ b/idatui/rpc.py
@@ -168,7 +168,7 @@ def snapshot(app) -> dict[str, Any]:
pass
return {
"active": app._active,
- "pref": app._pref,
+ "pref": app._code_mode(), # kept for wire compat; a constant now
"function": ({"ea": cur.ea, "name": cur.name} if cur else None),
"cursor": _cursor_info(app, w),
"status": st,
diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py
index becd8b1..b7520d9 100644
--- a/tests/test_scenarios.py
+++ b/tests/test_scenarios.py
@@ -911,6 +911,23 @@ async def s_view_toggle(c: Ctx):
styled = any(seg.style is not None and seg.style.color is not None
for strip in dec._strips[:min(dec.total, 200)] for seg in strip)
c.check("pseudocode is syntax-highlighted", styled)
+ # Cancelling a prompt must hand focus back to the pane you were READING.
+ # _code_view() used to choose on _pref, which was only ever "listing", so it
+ # focused the hidden listing and the pseudocode stopped answering the
+ # keyboard — arrows did nothing at all until you clicked.
+ dec.focus()
+ await c.pause(0.05)
+ line0 = dec.cursor
+ await c.press("g")
+ await c.wait(lambda: app.query_one("#goto", Input).display, 10)
+ await c.press("escape")
+ await c.wait(lambda: not app.query_one("#goto", Input).display, 10)
+ await c.press("down")
+ await c.press("down")
+ await c.pause(0.2)
+ c.check("cancelling goto leaves focus in the pseudocode (arrows still work)",
+ dec.cursor > line0,
+ f"cursor {line0} -> {dec.cursor} focus={type(app.focused).__name__}")
dec.focus() # Tab only toggles the view from a code pane; elsewhere it's
await c.pause(0.05) # focus-next, which would silently leave us in decomp
await c.press("tab")