diff options
| author | blasty <blasty@local> | 2026-07-09 23:12:02 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-09 23:12:02 +0200 |
| commit | c6ef8ceeba6b11ce0f6024ff089eff50e8de3579 (patch) | |
| tree | a66b449a2cead5b953272f7b6639a10afac62791 | |
| parent | names: command-palette fuzzy finder (Ctrl+N), overlay-first (diff) | |
| download | ida-tui-c6ef8ceeba6b11ce0f6024ff089eff50e8de3579.tar.gz ida-tui-c6ef8ceeba6b11ce0f6024ff089eff50e8de3579.tar.xz ida-tui-c6ef8ceeba6b11ce0f6024ff089eff50e8de3579.zip | |
default to the pseudocode view instead of disassembly
Opening a function now shows the decompiler by default (Tab still toggles to
disassembly; the choice persists across opens). Startup focuses the decompiler
view, and Ctrl+B / back focus the active view rather than hard-coding disasm.
Tests: assert pseudocode-by-default on the first open, then normalize per-section
(the disasm-scrolling/search/follow blocks switch to disassembly explicitly).
full suite 75/75.
| -rw-r--r-- | idatui/app.py | 22 | ||||
| -rw-r--r-- | tests/test_tui.py | 34 |
2 files changed, 38 insertions, 18 deletions
diff --git a/idatui/app.py b/idatui/app.py index cd089f9..41e7e08 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -1232,7 +1232,7 @@ class IdaTui(App): self._filter_timer = None self._sort_col = 0 # 0=addr, 1=name, 2=size self._sort_reverse = False - self._active = "disasm" # or "decomp" + self._active = "decomp" # default code view (or "disasm") self._cur: NavEntry | None = None self._search_ctx: tuple[object | None, int] = (None, 1) self._rename_ctx: tuple[object | None, str] = (None, "") @@ -1247,10 +1247,10 @@ class IdaTui(App): fp = FunctionsPanel(id="left") fp.display = False # overlay-first: reveal the docked pane with Ctrl+B yield fp - yield DisasmView() - dv = DecompView() - dv.display = False - yield dv + dis = DisasmView() + dis.display = False + yield dis + yield DecompView() # pseudocode is the default view si = Input(id="search") si.display = False si.can_focus = False @@ -1270,9 +1270,9 @@ class IdaTui(App): # Keep the hidden command input out of the focus chain until summoned. inp = self.query_one("#func-filter", Input) inp.can_focus = False - # The names pane is an overlay now (Ctrl+N); focus a code view so app - # bindings work before anything is opened. - self.query_one(DisasmView).focus() + # The names pane is an overlay now (Ctrl+N); focus the default code view + # (pseudocode) so app bindings work before anything is opened. + self.query_one(DecompView).focus() self._connect() # -- status helper ----------------------------------------------------- # @@ -1438,7 +1438,8 @@ class IdaTui(App): left = self.query_one("#left", FunctionsPanel) left.display = not left.display if not left.display: - self.query_one(DisasmView).focus() + self.query_one(DecompView if self._active == "decomp" + else DisasmView).focus() else: self.query_one("#func-table", DataTable).focus() @@ -1492,7 +1493,8 @@ class IdaTui(App): elif self.query_one("#left", FunctionsPanel).display: table.focus() else: - self.query_one(DisasmView).focus() + self.query_one(DecompView if self._active == "decomp" + else DisasmView).focus() # -- input submit (filter / goto) ------------------------------------- # def on_search_requested(self, msg: SearchRequested) -> None: diff --git a/tests/test_tui.py b/tests/test_tui.py index afa995e..044aabd 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -137,6 +137,18 @@ async def run(db): check("disasm view opened with a total", opened, f"total={view.total}") print(f" opened func with {view.total} instructions") + # Pseudocode is the DEFAULT view: the first function opens into it. + dec0 = app.query_one(DecompView) + pc = await wait_until( + pilot, lambda: app._active == "decomp" and dec0.display + and dec0.loaded_ea is not None, 25) + check("opening a function shows pseudocode by default", pc, + f"active={app._active} disp={dec0.display}") + # Switch to disassembly for the disasm scrolling checks below. + await pilot.press("tab") + await wait_until(pilot, lambda: app._active == "disasm", 10) + view.focus() + # Wait for the first lines to be cached, then verify a rendered line. cached = await wait_until( pilot, lambda: view.model is not None and view.model.cached_line(0) is not None, @@ -194,17 +206,20 @@ async def run(db): app.clear_filter() await wait_until(pilot, lambda: table.row_count == nfuncs, timeout=15) - # Decompiler toggle: open a function, Tab -> pseudocode, Shift+Tab -> back. + # Tab toggles between pseudocode and disassembly (view state persists + # across opens, so normalize to pseudocode first). table.focus() table.move_cursor(row=biggest_i) await pilot.press("enter") dis = app.query_one(DisasmView) dec = app.query_one(DecompView) await wait_until(pilot, lambda: dis.total > 0, timeout=20) - await pilot.press("tab") - pc = await wait_until(pilot, lambda: dec.display and dec.loaded_ea is not None, 25) - check("tab shows pseudocode", pc and app._active == "decomp", - f"active={app._active} disp={dec.display}") + if app._active != "decomp": + await pilot.press("tab") + pc = await wait_until( + pilot, lambda: dec.display and dec.loaded_ea is not None + and app._active == "decomp", 25) + check("tab shows pseudocode", pc, f"active={app._active} disp={dec.display}") check("pseudocode has many lines", dec.total > 20, f"lines={dec.total}") # Highlighting: at least one styled (colored) segment across the body. styled = any( @@ -213,14 +228,17 @@ async def run(db): for seg in strip ) check("pseudocode is syntax-highlighted", styled) - await pilot.press("shift+tab") + await pilot.press("tab") await pilot.pause(0.1) - check("shift+tab returns to disassembly", + check("tab switches to disassembly", app._active == "disasm" and dis.display and not dec.display, f"active={app._active}") + await pilot.press("tab") # back to pseudocode for the overlay/gutter checks + await wait_until(pilot, lambda: app._active == "decomp", 10) # A (re)decompile shows a grayed 'decompiling…' overlay while it runs. - await pilot.press("tab") + if app._active != "decomp": + await pilot.press("tab") await wait_until(pilot, lambda: app._active == "decomp", timeout=10) await wait_until(pilot, lambda: dec.loaded_ea is not None, timeout=20) dec.loaded_ea = None # force a reload (as rename/refresh does) |
