diff --git a/src/terminal/search/active.zig b/src/terminal/search/active.zig index aee21b3e3..b99b23579 100644 --- a/src/terminal/search/active.zig +++ b/src/terminal/search/active.zig @@ -56,6 +56,10 @@ pub const ActiveSearch = struct { // Clear our previous sliding window self.window.clearAndRetainCapacity(); + // An empty needle represents an inactive search and has no overlap + // or history to load. + if (self.window.needle.len == 0) return null; + // First up, add enough pages to cover the active area. var rem: usize = list.rows; var node_ = list.pages.last; diff --git a/src/terminal/search/sliding_window.zig b/src/terminal/search/sliding_window.zig index ea039bf3a..5c29ce03b 100644 --- a/src/terminal/search/sliding_window.zig +++ b/src/terminal/search/sliding_window.zig @@ -176,6 +176,11 @@ pub const SlidingWindow = struct { /// or `append()`. If the caller wants to retain the flattened highlight /// then they should clone it. pub fn next(self: *SlidingWindow) ?FlattenedHighlight { + // An empty needle represents an inactive search. Searching for it + // would otherwise produce a zero-length match, which highlight() + // cannot represent because its end offset is inclusive. + if (self.needle.len == 0) return null; + const slices = slices: { // If we have less data then the needle then we can't possibly match const data_len = self.data.len(); @@ -704,6 +709,25 @@ test "SlidingWindow empty on init" { try testing.expectEqual(0, w.meta.len()); } +test "SlidingWindow empty needle has no matches" { + const testing = std.testing; + const alloc = testing.allocator; + + var w: SlidingWindow = try .init(alloc, .forward, ""); + defer w.deinit(); + + var s = try Screen.init(alloc, .{ + .cols = 80, + .rows = 24, + .max_scrollback = 0, + }); + defer s.deinit(); + try s.testWriteString("hello"); + + _ = try w.append(s.pages.pages.first.?); + try testing.expectEqual(null, w.next()); +} + test "SlidingWindow single append" { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/terminal/search/viewport.zig b/src/terminal/search/viewport.zig index a1458bbed..d8dbb9429 100644 --- a/src/terminal/search/viewport.zig +++ b/src/terminal/search/viewport.zig @@ -134,6 +134,7 @@ pub const ViewportSearch = struct { // Add enough overlap to cover needle.len - 1 bytes (if it // exists) so we can cover the overlap. We only do this for the // soft-wrapped prior pages. + const overlap_len = self.window.needle.len -| 1; var node_ = fingerprint.nodes[0].prev; var added: usize = 0; while (node_) |node| : (node_ = node.prev) { @@ -142,7 +143,7 @@ pub const ViewportSearch = struct { // wrap so this should be fine. const appended = try self.window.appendIfWrapped(node) orelse break; added += appended.content_len; - if (added >= self.window.needle.len - 1) break; + if (added >= overlap_len) break; } // We can use our fingerprint nodes to initialize our sliding @@ -162,7 +163,7 @@ pub const ViewportSearch = struct { while (node_) |node| : (node_ = node.next) { const appended = try self.window.append(node); added += appended.content_len; - if (added >= self.window.needle.len - 1) break; + if (added >= overlap_len) break; // If this row doesn't wrap, then we can quit if (!appended.last_row_wrapped) break;