aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-09 16:53:13 +0200
committerblasty <blasty@local>2026-07-09 16:53:13 +0200
commitc2f56ee87f9de280fb8d5e94f1730a0574e0c0e7 (patch)
treed77896f42694526892b6fb12c86e3ac1e2724a5a
parentrobust scroll restore for both views (fix stale frame in real terminal) (diff)
downloadida-tui-c2f56ee87f9de280fb8d5e94f1730a0574e0c0e7.tar.gz
ida-tui-c2f56ee87f9de280fb8d5e94f1730a0574e0c0e7.tar.xz
ida-tui-c2f56ee87f9de280fb8d5e94f1730a0574e0c0e7.zip
decompiler: grayed 'decompiling…' overlay during (re)decompile
When a decompile runs (tab to pseudocode, or an implicit refresh after rename), DecompView.loading covers the pane with a centered, grayed '― decompiling… ―' (custom get_loading_widget + .decomp-loading CSS), cleared when the body arrives. The loading cover blurs the widget, which made Tab fall back to focus-nav instead of toggling; made the app Tab/Shift+Tab binding priority so it toggles regardless of focus, and restore focus to the pane when the decompile finishes. pilot suite 55/55 (adds overlay-shown / overlay-cleared).
-rw-r--r--idatui/app.py14
-rw-r--r--tests/test_tui.py19
2 files changed, 32 insertions, 1 deletions
diff --git a/idatui/app.py b/idatui/app.py
index a6affa2..88e1dea 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -835,6 +835,10 @@ class DecompView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True
self._hscroll()
self.refresh()
+ def get_loading_widget(self): # type: ignore[override]
+ # Shown (grayed, centered) while a (re)decompile is in flight.
+ return Static("― decompiling… ―", classes="decomp-loading")
+
def _line_ea(self, idx: int) -> int | None:
if not (0 <= idx < len(self._texts)):
return None
@@ -972,6 +976,10 @@ class IdaTui(App):
background: $warning-darken-2; color: $text;
}
#status { height: 1; background: $panel; color: $text; padding: 0 1; }
+ .decomp-loading {
+ width: 100%; height: 100%; content-align: center middle;
+ background: $panel-darken-1; color: $text-muted; text-style: italic bold;
+ }
XrefsScreen { align: center middle; }
#xref-box { width: 84; max-height: 70%; height: auto; border: thick $accent; background: $panel; }
#xref-title { dock: top; height: 1; background: $accent; color: $text; padding: 0 1; }
@@ -983,7 +991,7 @@ class IdaTui(App):
Binding("slash", "filter", "Filter"),
Binding("g", "goto", "Goto"),
Binding("ctrl+b", "toggle_functions", "Names"),
- Binding("tab,shift+tab", "toggle_view", "Disasm/Pseudocode"),
+ Binding("tab,shift+tab", "toggle_view", "Disasm/Pseudocode", priority=True),
Binding("ctrl+s", "save", "Save"),
Binding("escape", "back", "Back"),
]
@@ -1670,6 +1678,7 @@ class IdaTui(App):
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
self._load_decomp(self._cur.ea, self._cur.name)
else:
self._status_for_cur("pseudocode")
@@ -1686,6 +1695,9 @@ class IdaTui(App):
def _apply_decomp(self, ea: int, name: str, dec) -> None: # type: ignore[no-untyped-def]
view = self.query_one(DecompView)
+ view.loading = False
+ if self._active == "decomp":
+ view.focus() # loading cover had blurred it; restore focus
# Restore the saved pseudocode position when returning to this function.
cur = self._cur
same = cur is not None and cur.ea == ea
diff --git a/tests/test_tui.py b/tests/test_tui.py
index 884b1c3..494ea4a 100644
--- a/tests/test_tui.py
+++ b/tests/test_tui.py
@@ -157,6 +157,25 @@ async def run(db):
app._active == "disasm" and dis.display and not dec.display,
f"active={app._active}")
+ # A (re)decompile shows a grayed 'decompiling…' overlay while it runs.
+ 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)
+ app._show_active()
+ cover = dec._cover_widget
+ check("decompile shows a 'decompiling…' overlay",
+ dec.loading and cover is not None
+ and "decomp-loading" in cover.classes
+ and "decompiling" in str(cover.render()),
+ f"loading={dec.loading} cover={cover!r}")
+ await wait_until(pilot, lambda: not dec.loading and dec._cover_widget is None,
+ timeout=25)
+ check("overlay clears when the decompile finishes",
+ not dec.loading and dec._cover_widget is None)
+ await pilot.press("tab") # back to disasm for the rest of the tests
+ await wait_until(pilot, lambda: app._active == "disasm", timeout=10)
+
# Vim-style search in the disassembly view.
line0 = dis.model.cached_line(0)
raw = (line0.text.split() or ["push"])[0] if line0 else "push"