aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-09 23:12:02 +0200
committerblasty <blasty@local>2026-07-09 23:12:02 +0200
commitc6ef8ceeba6b11ce0f6024ff089eff50e8de3579 (patch)
treea66b449a2cead5b953272f7b6639a10afac62791
parentnames: command-palette fuzzy finder (Ctrl+N), overlay-first (diff)
downloadida-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.py22
-rw-r--r--tests/test_tui.py34
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)