aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-23 20:35:13 +0200
committerblasty <blasty@local>2026-07-23 20:35:13 +0200
commitf5aaf50476e5bf72eaec0c09ddd984b28960ca8d (patch)
tree223ec22ab0692a88397fbaabd55c805636c36e4c
parentlisting: two-level indent, address on headers, drop 'near', opcode cap+cycle (diff)
downloadida-tui-f5aaf50476e5bf72eaec0c09ddd984b28960ca8d.tar.gz
ida-tui-f5aaf50476e5bf72eaec0c09ddd984b28960ca8d.tar.xz
ida-tui-f5aaf50476e5bf72eaec0c09ddd984b28960ca8d.zip
listing: code labels on their own line; search loaded portion (no wedge)
Code labels (loc_XXX/jump targets) get their OWN line at depth 0, like IDA, instead of inline with the instruction: the heads walker (annotate) emits a kind=label row ('loc_XXX:') before a named non-function-start code head and strips the name from the instruction. ListingModel doesn't index label rows (goto/xref/follow still land on the code); ListingView renders them at depth 0. Also fix a search regression the extra rows exposed: search no longer forces a blocking full-segment load_all (which stalled/thrashed the worker and wedged the session on the larger annotated listing). It searches the loaded portion; the background grower streams the rest and unloaded lines are skipped until they arrive. Verified: labels on their own line; search 2.9s (was 51.9s+wedge); search/mouse/ rename_history/continuous_view/func_banners/follow_xrefs 26/0.
-rw-r--r--idatui/app.py28
-rw-r--r--idatui/domain.py7
-rw-r--r--server/patch_server.py8
3 files changed, 24 insertions, 19 deletions
diff --git a/idatui/app.py b/idatui/app.py
index 59f0af3..396e466 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -975,9 +975,9 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru
h = self._head(idx)
if h is None:
return None
- # Function-name headers sit at depth 0 (with the address); everything
- # else is indented one level, code/data opcode+text included.
- if h.kind == "funchdr":
+ # Function headers and code labels sit at depth 0 (with the address);
+ # everything else is indented one level (opcode+text included).
+ if h.kind in ("funchdr", "label"):
return f"{h.ea:08X} {h.text}"
base = f"{h.ea:08X} " + _LST_INDENT
if h.kind == "sep":
@@ -1090,19 +1090,11 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru
return self._line_plain(i)
def _search_ensure(self, done) -> None:
- # Search needs the whole segment; finish loading it first if streaming.
- if self.model is not None and not self.model.complete:
- self._search_load_all(done)
- else:
- done()
-
- @work(thread=True, exclusive=True, group="listing-search-load")
- def _search_load_all(self, done) -> None: # type: ignore[no-untyped-def]
- model = self.model
- if model is not None:
- model.load_all()
- self.app.call_from_thread(self._grew, len(model))
- self.app.call_from_thread(done)
+ # Search the currently-loaded portion; the background grower streams the
+ # rest of the segment in on its own (a blocking full load_all here would
+ # stall/thrash on a big segment). Unloaded lines read back as None from
+ # _search_line_text and are skipped until they arrive.
+ done()
# -- rendering --------------------------------------------------------- #
def render_line(self, y: int) -> Strip:
@@ -1124,6 +1116,10 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru
# depth-0: address + 'name proc'/'endp' (no indent)
strip = Strip([Segment(f"{h.ea:08X} ", _S_ADDR),
Segment(h.text, _S_FUNCHDR)])
+ elif h.kind == "label":
+ # depth-0: address + 'loc_XXX:' on its own line
+ strip = Strip([Segment(f"{h.ea:08X} ", _S_ADDR),
+ Segment(h.text, _S_LABEL)])
else:
# depth-1: address, one indent, then opcode+text
segs: list[Segment] = [Segment(f"{h.ea:08X} ", _S_ADDR),
diff --git a/idatui/domain.py b/idatui/domain.py
index 7452ffd..e8e2996 100644
--- a/idatui/domain.py
+++ b/idatui/domain.py
@@ -609,9 +609,10 @@ class ListingModel:
with self._lock:
base = len(self._heads)
for i, h in enumerate(page):
- # Banner rows (function headers/separators) are display-only;
- # don't index them so navigation lands on real code/data.
- if h.kind not in ("sep", "funchdr"):
+ # Banner/label rows (function headers, separators, code labels)
+ # are display-only; don't index them so navigation lands on the
+ # real code/data head at that address.
+ if h.kind not in ("sep", "funchdr", "label"):
self._by_ea.setdefault(h.ea, base + i)
self._heads.append(h)
nxt = cur.get("next")
diff --git a/server/patch_server.py b/server/patch_server.py
index 73144fe..7ee490a 100644
--- a/server/patch_server.py
+++ b/server/patch_server.py
@@ -451,6 +451,14 @@ def heads(
if at_start:
row = dict(row)
row["name"] = None # the name is shown on the proc header line
+ elif annotate and row.get("kind") == "code" and row.get("name"):
+ # A code label (loc_XXX/jump target) gets its OWN line at depth 0,
+ # like IDA; strip it from the instruction row below.
+ nm = row["name"]
+ out.append({"ea": hex(e), "kind": "label", "size": 0,
+ "text": nm + ":", "name": nm})
+ row = dict(row)
+ row["name"] = None
out.append(row)
if row.get("kind") == "data":
out.extend(_idatui_struct_member_rows(e)) # expand struct fields