diff options
| author | blasty <blasty@local> | 2026-07-23 21:39:08 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-23 21:39:33 +0200 |
| commit | a865213d04f621f3f87ad3d1873faea47709ed79 (patch) | |
| tree | 4b1f8c1bb2e3dc9c78157d5d51122ec10f843101 | |
| parent | views: highlight all occurrences of the token under the cursor (diff) | |
| download | ida-tui-a865213d04f621f3f87ad3d1873faea47709ed79.tar.gz ida-tui-a865213d04f621f3f87ad3d1873faea47709ed79.tar.xz ida-tui-a865213d04f621f3f87ad3d1873faea47709ed79.zip | |
listing: restore full-segment search load (find every match)
Revert the "search only the loaded portion" shortcut from f5aaf50 and go back to
a single, guarded background load_all before matching, so a search finds every
occurrence in the segment (not just whatever streamed in so far). The guard
(_search_loading + queued callbacks) shares ONE load across per-keystroke
updates instead of spawning/cancelling a worker each keypress.
The wedge that shortcut was meant to avoid turned out to be unrelated to the
search load: it is a pre-existing navigation bug (opening a function right after
scrolling to the bottom of another leaves the listing cursor out of range and
'/' fails to focus the search input, so typed chars leak into the view as
listing verbs, one of which is 'u'=undefine). Reproduces at 0c097f5, before any
of this work; tracked separately.
Verified in isolation: search 3.1s, matches found; search/mouse/func_banners/
listing_view/continuous_view/decomp_nav/follow_xrefs 45/0.
| -rw-r--r-- | idatui/app.py | 34 |
1 files changed, 29 insertions, 5 deletions
diff --git a/idatui/app.py b/idatui/app.py index 7ca7da6..3fa6515 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -987,6 +987,8 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru self._ranges: dict[int, list[tuple[int, int]]] = {} self._op_mode = 1 # opcode column: 0=off, 1=limited, 2=full ('o' cycles) self._op_w = 0 # char width of the hex-bytes field (excl. gap) + self._search_loading = False + self._search_pending: list = [] # done-callbacks awaiting the load # -- text helpers ------------------------------------------------------ # def _head(self, idx: int) -> Head | None: @@ -1130,11 +1132,33 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru return self._line_plain(i) def _search_ensure(self, done) -> None: - # 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() + # Search needs the whole segment loaded to find every match. Kick off a + # SINGLE background load (guarded so per-keystroke updates don't spawn + # one worker each — an exclusive worker would cancel+restart on every + # keypress and never finish) and queue the callbacks until it lands. + model = self.model + if model is None or model.complete: + done() + return + self._search_pending.append(done) + if not self._search_loading: + self._search_loading = True + self._search_load_all() + + @work(thread=True, group="listing-search-load") + def _search_load_all(self) -> None: + model = self.model + if model is not None: + model.load_all() + self.app.call_from_thread(self._search_loaded) + + def _search_loaded(self) -> None: + self._search_loading = False + if self.model is not None: + self._grew(len(self.model)) + pending, self._search_pending = self._search_pending, [] + for cb in pending: + cb() # -- rendering --------------------------------------------------------- # def render_line(self, y: int) -> Strip: |
