diff --git a/src/terminal/search/Thread.zig b/src/terminal/search/Thread.zig index 984730793..557001fe8 100644 --- a/src/terminal/search/Thread.zig +++ b/src/terminal/search/Thread.zig @@ -161,25 +161,19 @@ fn threadMain_(self: *Thread) !void { // for data loading, etc. switch (s.tick()) { // We're complete now when we were not before. Notify! - .complete => if (self.opts.event_cb) |cb| { - cb(.complete, self.opts.event_userdata); - }, + .complete => {}, // Forward progress was made. .progress => {}, // All searches are blocked. Let's grab the lock and feed data. .blocked => { - try s.feed(self.opts.mutex, self.opts.terminal); - - // Feeding can result in completion if there is no more - // data to feed. If we transitioned to complete, notify! - if (self.opts.event_cb) |cb| { - if (s.isComplete()) cb( - .complete, - self.opts.event_userdata, - ); - } + self.opts.mutex.lock(); + defer self.opts.mutex.unlock(); + try s.feed( + self.alloc, + self.opts.terminal, + ); }, } @@ -189,6 +183,13 @@ fn threadMain_(self: *Thread) !void { cb, self.opts.event_userdata, ); + + // If our forward progress resulted in us becoming complete, + // then notify our callback. + if (s.isComplete()) cb( + .complete, + self.opts.event_userdata, + ); } // We have an active search, so we only want to process messages @@ -221,42 +222,11 @@ fn changeNeedle(self: *Thread, needle: []const u8) !void { // No needle means stop the search. if (needle.len == 0) return; - // Our new search state - var search: Search = .empty; - errdefer search.deinit(); - // We need to grab the terminal lock to setup our search state. self.opts.mutex.lock(); defer self.opts.mutex.unlock(); const t: *Terminal = self.opts.terminal; - - // Go through all our screens, setup our search state. - // - // NOTE(mitchellh): Maybe we should only initialize the screen we're - // currently looking at (the active screen) and then let our screen - // reconciliation timer add the others later in order to minimize - // startup latency. - var it = t.screens.all.iterator(); - while (it.next()) |entry| { - var screen_search: ScreenSearch = ScreenSearch.init( - self.alloc, - entry.value.*, - needle, - ) catch |err| switch (err) { - error.OutOfMemory => { - // We can ignore this (although OOM probably means the whole - // ship is sinking). Our reconciliation timer will try again - // later. - log.warn("error initializing screen search key={} err={}", .{ entry.key, err }); - continue; - }, - }; - errdefer screen_search.deinit(); - search.screens.put(entry.key, screen_search); - } - - // Our search state is setup - self.search = search; + self.search = try .init(self.alloc, needle, t); } fn wakeupCallback( @@ -340,11 +310,27 @@ const Search = struct { /// The last total matches reported. last_total: ?usize, - pub const empty: Search = .{ - .screens = .init(.{}), - .last_active_screen = .primary, - .last_total = null, - }; + pub fn init( + alloc: Allocator, + needle: []const u8, + t: *Terminal, + ) Allocator.Error!Search { + // We only initialize the primary screen for now. Our reconciler + // via feed will handle setting up our other screens. We just need + // to setup at least one here so that we can store our needle. + var screen_search: ScreenSearch = try .init( + alloc, + t.screens.get(.primary).?, + needle, + ); + errdefer screen_search.deinit(); + + return .{ + .screens = .init(.{ .primary = screen_search }), + .last_active_screen = .primary, + .last_total = null, + }; + } pub fn deinit(self: *Search) void { var it = self.screens.iterator(); @@ -412,16 +398,63 @@ const Search = struct { /// Grab the mutex and update any state that requires it, such as /// feeding additional data to the searches or updating the active screen. - pub fn feed(self: *Search, mutex: *Mutex, t: *Terminal) !void { - mutex.lock(); - defer mutex.unlock(); - + pub fn feed( + self: *Search, + alloc: Allocator, + t: *Terminal, + ) !void { // Update our active screen if (t.screens.active_key != self.last_active_screen) { self.last_active_screen = t.screens.active_key; self.last_total = null; // force notification } + // Reconcile our screens with the terminal screens. Remove + // searchers for screens that no longer exist and add searchers + // for screens that do exist but we don't have yet. + { + // Remove screens we have that no longer exist or changed. + var it = self.screens.iterator(); + while (it.next()) |entry| { + const remove: bool = remove: { + // If the screen doesn't exist at all, remove it. + const actual = t.screens.all.get(entry.key) orelse break :remove true; + + // If the screen pointer changed, remove it, the screen + // was totally reinitialized. + break :remove actual != entry.value.screen; + }; + + if (remove) { + entry.value.deinit(); + _ = self.screens.remove(entry.key); + } + } + } + { + // Add screens that exist but we don't have yet. + var it = t.screens.all.iterator(); + while (it.next()) |entry| { + if (self.screens.contains(entry.key)) continue; + self.screens.put(entry.key, ScreenSearch.init( + alloc, + entry.value.*, + self.screens.get(.primary).?.needle(), + ) catch |err| switch (err) { + error.OutOfMemory => { + // OOM is probably going to sink the entire ship but + // we can just ignore it and wait on the next + // reconciliation to try again. + log.warn( + "error initializing screen search for key={} err={}", + .{ entry.key, err }, + ); + continue; + }, + }); + } + } + // Feed data var it = self.screens.iterator(); while (it.next()) |entry| { diff --git a/src/terminal/search/screen.zig b/src/terminal/search/screen.zig index c60161153..07d700742 100644 --- a/src/terminal/search/screen.zig +++ b/src/terminal/search/screen.zig @@ -98,11 +98,11 @@ pub const ScreenSearch = struct { pub fn init( alloc: Allocator, screen: *Screen, - needle: []const u8, + needle_unowned: []const u8, ) Allocator.Error!ScreenSearch { var result: ScreenSearch = .{ .screen = screen, - .active = try .init(alloc, needle), + .active = try .init(alloc, needle_unowned), .history = null, .state = .active, .active_results = .empty, @@ -128,6 +128,12 @@ pub const ScreenSearch = struct { return self.active.window.alloc; } + /// The needle that this search is using. + pub fn needle(self: *const ScreenSearch) []const u8 { + assert(self.active.window.direction == .forward); + return self.active.window.needle; + } + /// Returns the total number of matches found so far. pub fn matchesLen(self: *const ScreenSearch) usize { return self.active_results.items.len + self.history_results.items.len; @@ -310,12 +316,9 @@ pub const ScreenSearch = struct { // No history search yet, but we now have history. So let's // initialize. - // Our usage of needle below depends on this - assert(self.active.window.direction == .forward); - var search: PageListSearch = try .init( self.allocator(), - self.active.window.needle, + self.needle(), list, history_node, ); @@ -347,7 +350,7 @@ pub const ScreenSearch = struct { var window: SlidingWindow = try .init( alloc, .forward, - self.active.window.needle, + self.needle(), ); defer window.deinit(); while (true) {