diff options
| author | blasty <blasty@local> | 2026-07-23 15:26:42 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-23 15:26:42 +0200 |
| commit | 73305a513b929838f04f53d88dea2b1483cc485f (patch) | |
| tree | 089588a172346c854789436f6444dc3a2ee0f155 | |
| parent | unify: back the function disasm view on the heads listing walker (M4 item 4) (diff) | |
| download | ida-tui-73305a513b929838f04f53d88dea2b1483cc485f.tar.gz ida-tui-73305a513b929838f04f53d88dea2b1483cc485f.tar.xz ida-tui-73305a513b929838f04f53d88dea2b1483cc485f.zip | |
app: 'L' opens the continuous segment listing (functions+data interleaved)
Function views are bounded to one function; 'L' from any code view opens the
whole-segment ListingView at the cursor instead -- one long flat listing where
functions/data/undefined interleave and you scroll straight from one function
into the next. Reuses the region listing path (_goto_continuous).
New continuous_view scenario: open a function, press L, assert continuous
listing opens, cursor at the origin function, listing spans past the function
bounds, code interleaves with data. 22/0 with disasm_nav/view_toggle/
region_define (L is additive).
| -rw-r--r-- | idatui/app.py | 38 | ||||
| -rw-r--r-- | tests/test_scenarios.py | 27 |
2 files changed, 65 insertions, 0 deletions
diff --git a/idatui/app.py b/idatui/app.py index 234b720..415d00a 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -607,6 +607,7 @@ class DisasmView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True Binding("p", "define_func", "Func", show=False), Binding("u", "undefine", "Undef", show=False), Binding("tab,shift+tab", "app.toggle_view", "Pseudocode", priority=True), + Binding("L", "app.continuous_here", "Listing"), *SearchMixin.SEARCH_BINDINGS, *NavMixin.NAV_BINDINGS, *ColumnCursor.COL_BINDINGS, @@ -1165,6 +1166,7 @@ class DecompView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True BINDINGS = [ Binding("tab,shift+tab", "app.toggle_view", "Disasm", priority=True), + Binding("L", "app.continuous_here", "Listing"), Binding("j,down", "cursor_down", "Down", show=False), Binding("k,up", "cursor_up", "Up", show=False), Binding("ctrl+d", "half_page(1)", "½↓", show=False), @@ -3393,6 +3395,42 @@ class IdaTui(App): def _code_view(self): # type: ignore[no-untyped-def] return self.query_one(DecompView if self._pref == "decomp" else DisasmView) + def _active_code_view(self): # type: ignore[no-untyped-def] + """The currently-shown code widget (for reading the cursor address).""" + if self._active == "listing": + return self.query_one(ListingView) + if self._active == "disasm": + return self.query_one(DisasmView) + if self._active == "decomp": + return self.query_one(DecompView) + return None + + def action_continuous_here(self) -> None: + """'L': open the continuous segment listing at the cursor — one long + flat view where functions, data and undefined bytes are interleaved, + instead of the function-bounded disassembly.""" + if self.program is None: + return + view = self._active_code_view() + ea = self._line_ea_for(view) if view is not None else None + if ea is None and self._cur is not None: + ea = self._cur.ea + if ea is None: + self._status("no address here to open the continuous listing") + return + if self._cur is not None and self._cur.is_region: + self._status("already in the continuous listing") + return + self._goto_continuous(ea) + + @work(thread=True, group="nav") + def _goto_continuous(self, ea: int, push: bool = True) -> None: + assert self.program is not None + name = self.program.region_label(ea) + lm = self.program.listing(ea) + idx = max(lm.ensure_ea(ea), 0) if lm is not None else 0 + self.app.call_from_thread(self._open_at, ea, name, idx, push, -1, 0, True) + @work(thread=True, exclusive=True, group="goto") def _goto(self, target: str) -> None: assert self.program is not None diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py index d3576c0..b921c54 100644 --- a/tests/test_scenarios.py +++ b/tests/test_scenarios.py @@ -1604,6 +1604,33 @@ async def s_listing_struct_expand(c: Ctx): c.prog.bump_items() +@scenario("continuous_view") +async def s_continuous_view(c: Ctx): + """'L' opens one long continuous segment listing (functions + data + interleaved) at the cursor, instead of the function-bounded disasm.""" + app = c.app + fn = await c.open_biggest("disasm") + func_total = c.dis.total + fn_ea = fn.addr + c.dis.focus() + await c.press("L") + await c.wait(lambda: app._active == "listing" and app._cur is not None + and app._cur.is_region and c.lst.total > 0, 25) + c.check("L opens the continuous listing", + app._active == "listing" and app._cur.is_region, + f"active={app._active} cur={app._cur}") + c.check("cursor lands at the origin function in the continuous view", + c.lst._cursor_ea() == fn_ea, f"cur_ea={c.lst._cursor_ea()}") + # the continuous listing spans the whole segment, not just the function + c.lst.model.load_all() + c.check("continuous listing extends past the function's bounds", + len(c.lst.model) > func_total, + f"listing={len(c.lst.model)} func={func_total}") + kinds = {c.lst.model.get(i).kind for i in range(len(c.lst.model))} + c.check("continuous listing interleaves code with data/undefined", + "code" in kinds and ("data" in kinds or "unknown" in kinds), str(kinds)) + + # --------------------------------------------------------------------------- # # Runner # --------------------------------------------------------------------------- # |
