aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-23 21:39:08 +0200
committerblasty <blasty@local>2026-07-23 21:39:33 +0200
commita865213d04f621f3f87ad3d1873faea47709ed79 (patch)
tree4b1f8c1bb2e3dc9c78157d5d51122ec10f843101
parentviews: highlight all occurrences of the token under the cursor (diff)
downloadida-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.py34
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: