diff options
| -rw-r--r-- | idatui/app.py | 17 | ||||
| -rw-r--r-- | idatui/rpc.py | 2 | ||||
| -rw-r--r-- | tests/test_scenarios.py | 17 |
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") |
