aboutsummaryrefslogtreecommitdiffstats
path: root/idatui
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-07 02:56:22 +0200
committeruser <user@clank>2026-08-07 02:56:22 +0200
commite41538763c6d2465c564e49ffcfce599836d8df7 (patch)
tree16e3cc14475a2278d82988d724d79f9c9ef698e7 /idatui
parentHexView.render_line emits style RUNS instead of one Segment per byte cell (35... (diff)
downloadida-tui-e41538763c6d2465c564e49ffcfce599836d8df7.tar.gz
ida-tui-e41538763c6d2465c564e49ffcfce599836d8df7.tar.xz
ida-tui-e41538763c6d2465c564e49ffcfce599836d8df7.zip
Re-run of #16 (keep the joined search body across a cancelled search and across navigation inside the same segment), confirming it. total 17784 -> 17590; lg_search 1917 -> 1400, sm_search 70 -> 48. Also lands .auto/check_search.py in the checks gate: it compares both search fast paths against the plain per-line loop for every typed prefix.
Result: {"status":"keep","total_ms":17589.8,"lg_boot_ms":710.6,"lg_decomp_ms":2404.9,"lg_graph_ms":1136.1,"lg_hex_ms":425.6,"lg_index_ms":103.4,"lg_listing_cold_ms":417.2,"lg_listing_warm_ms":508.1,"lg_nav_ms":6553.8,"lg_palette_ms":4.8,"lg_render_ms":215.7,"lg_search_ms":1400.3,"pure_graph_ms":239.3,"sm_boot_ms":443.2,"sm_decomp_ms":668.4,"sm_graph_ms":698.1,"sm_hex_ms":443.6,"sm_index_ms":0,"sm_listing_cold_ms":267.3,"sm_listing_warm_ms":286,"sm_nav_ms":356.6,"sm_palette_ms":0.3,"sm_render_ms":258.6,"sm_search_ms":47.9,"fails":0}
Diffstat (limited to 'idatui')
-rw-r--r--idatui/app.py38
1 files changed, 27 insertions, 11 deletions
diff --git a/idatui/app.py b/idatui/app.py
index 20aed88..53dd79b 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -655,14 +655,24 @@ class SearchMixin:
pointed at a different model/body, which invalidates a narrowing."""
return id(getattr(self, "model", None) or getattr(self, "_texts", None))
- def _reset_search_cache(self) -> None:
- """Forget both the narrowing key and the joined body. Called from every
- place that resets ``_matches``/``_ranges``: a stale prefix would make the
- next search narrow from an empty list, and a stale body would search
- text the view no longer shows."""
+ def _reset_search_cache(self, body: bool = False) -> None:
+ """Forget the narrowing key, and with ``body=True`` the joined body too.
+
+ Every place that resets ``_matches``/``_ranges`` must call this: a stale
+ prefix would make the next search narrow from an empty list.
+
+ The body is a different question. It is keyed by (row count, line source)
+ so it invalidates itself when the view is pointed somewhere else or more
+ rows stream in — which means ending a search does NOT have to throw it
+ away, and the next `/` over the same segment is then instant instead of
+ re-joining a quarter of a million lines. It DOES have to go when the
+ plain text of a row changes without either of those moving, which is
+ exactly what toggling the opcode-bytes column does.
+ """
self._matched_key = None
- self._hay_key = None
- self._hay = None
+ if body:
+ self._hay_key = None
+ self._hay = None
def _search_haystack(self, count: int, src: int):
"""``(starts, blob, blob_folded)`` for the whole body, or None.
@@ -1062,7 +1072,7 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru
def load(self, model: ListingModel, name: str, cursor: int = 0,
cursor_x: int = 0, scroll_y: int | None = None,
focus: str | None = None) -> None:
- self.model = model
+ previous, self.model = self.model, model
self._name = name
self.total = 0
self.cursor = cursor
@@ -1072,7 +1082,9 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru
self._pending_op = None
self._matches = []
self._ranges = {}
- self._reset_search_cache()
+ # Navigating inside the same segment reuses the same model, and the
+ # searchable body with it; only a different model invalidates it.
+ self._reset_search_cache(body=model is not previous)
self._prime()
@work(thread=True, exclusive=True, group="listing-prime")
@@ -1147,7 +1159,8 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru
self._op_mode = (self._op_mode + 1) % 3
self._update_op_w()
self._ranges = {} # column layout changed -> stale match offsets
- self._reset_search_cache() # ...and which rows match at all
+ # ...and which rows match at all: the opcode hex is searchable text.
+ self._reset_search_cache(body=True)
self._clamp_x()
self.refresh()
self._app_status("opcodes: " + {0: "off", 1: f"limited ({_OP_LIMIT} bytes)",
@@ -1583,7 +1596,10 @@ class DecompView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=True
self.cursor_x = cursor_x
self._matches = []
self._ranges = {}
- self._reset_search_cache()
+ # A whole new body: drop the joined haystack outright rather than trust
+ # id(self._texts) to differ, since the list it replaces is freed here and
+ # its address can be handed straight back.
+ self._reset_search_cache(body=True)
# Gutter wide enough for the largest line number + a trailing space.
self._gutter = (len(str(total)) + 1) if total else 0
maxw = max((s.cell_length for s in self._strips), default=0)