diff options
| author | user <user@clank> | 2026-08-07 02:56:22 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-07 02:56:22 +0200 |
| commit | e41538763c6d2465c564e49ffcfce599836d8df7 (patch) | |
| tree | 16e3cc14475a2278d82988d724d79f9c9ef698e7 /idatui | |
| parent | HexView.render_line emits style RUNS instead of one Segment per byte cell (35... (diff) | |
| download | ida-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.py | 38 |
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) |
