search: prune invalid history entries on feed

pull/9702/head
Mitchell Hashimoto 2025-11-24 21:12:53 -08:00
parent 9511b237f3
commit 08f57ab6d6
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
1 changed files with 62 additions and 0 deletions

View File

@ -90,6 +90,11 @@ pub const ScreenSearch = struct {
pub fn needsFeed(self: State) bool {
return switch (self) {
.history_feed => true,
// Not obvious but complete search states will prune
// stale history results on feed.
.complete => true,
else => false,
};
}
@ -216,6 +221,9 @@ pub const ScreenSearch = struct {
/// Feed more data to the searcher so it can continue searching. This
/// accesses the screen state, so the caller must hold the necessary locks.
///
/// Feed on a complete screen search will perform some cleanup of
/// potentially stale history results (pruned) and reclaim some memory.
pub fn feed(self: *ScreenSearch) Allocator.Error!void {
const history: *PageListSearch = if (self.history) |*h| &h.searcher else {
// No history to feed, search is complete.
@ -228,6 +236,11 @@ pub const ScreenSearch = struct {
if (!try history.feed()) {
// No more data to feed, search is complete.
self.state = .complete;
// We use this opportunity to also clean up older history
// results that may be gone due to scrollback pruning, though.
self.pruneHistory();
return;
}
@ -246,6 +259,55 @@ pub const ScreenSearch = struct {
}
}
fn pruneHistory(self: *ScreenSearch) void {
const history: *PageListSearch = if (self.history) |*h| &h.searcher else return;
// Keep track of the last checked node to avoid redundant work.
var last_checked: ?*PageList.List.Node = null;
// Go through our history results in reverse order to find
// the oldest matches first (since oldest nodes are pruned first).
for (0..self.history_results.items.len) |rev_i| {
const i = self.history_results.items.len - 1 - rev_i;
const node = node: {
const hl = &self.history_results.items[i];
break :node hl.chunks.items(.node)[0];
};
// If this is the same node as what we last checked and
// found to prune, then continue until we find the first
// non-matching, non-pruned node so we can prune the older
// ones.
if (last_checked == node) continue;
last_checked = node;
// Try to find this node in the PageList using a standard
// O(N) traversal. This isn't as bad as it seems because our
// oldest matches are likely to be near the start of the
// list and as soon as we find one we're done.
var it = history.list.pages.first;
while (it) |valid_node| : (it = valid_node.next) {
if (valid_node != node) continue;
// This is a valid node. If we're not at rev_i 0 then
// it means we have some data to prune! If we are
// at rev_i 0 then we can break out because there
// is nothing to prune.
if (rev_i == 0) return;
// Prune the last rev_i items.
const alloc = self.allocator();
for (self.history_results.items[i + 1 ..]) |*prune_hl| {
prune_hl.deinit(alloc);
}
self.history_results.shrinkAndFree(alloc, i);
// Once we've pruned, future results can't be invalid.
return;
}
}
}
fn tickActive(self: *ScreenSearch) Allocator.Error!void {
// For the active area, we consume the entire search in one go
// because the active area is generally small.