From 852e04fa009eca31727762f2d72d2fec4fcea273 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Dec 2024 10:58:22 -0800 Subject: [PATCH] terminal: test for match in second slice of circ buf --- src/terminal/search.zig | 73 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/src/terminal/search.zig b/src/terminal/search.zig index 88da8304d..7b6486429 100644 --- a/src/terminal/search.zig +++ b/src/terminal/search.zig @@ -118,12 +118,16 @@ const SlidingWindow = struct { /// the invariant that the window is always big enough to contain /// the needle. pub fn next(self: *SlidingWindow, needle: []const u8) ?Selection { - const data_len = self.data.len(); - if (data_len == 0) return null; - const slices = self.data.getPtrSlice( - self.data_offset, - data_len - self.data_offset, - ); + const slices = slices: { + // If we have less data then the needle then we can't possibly match + const data_len = self.data.len(); + if (data_len < needle.len) return null; + + break :slices self.data.getPtrSlice( + self.data_offset, + data_len - self.data_offset, + ); + }; // Search the first slice for the needle. if (std.mem.indexOf(u8, slices[0], needle)) |idx| { @@ -134,7 +138,6 @@ const SlidingWindow = struct { // Search the last slice for the needle. if (std.mem.indexOf(u8, slices[1], needle)) |idx| { - if (true) @panic("TODO: test"); return self.selection(slices[0].len + idx, needle.len); } @@ -182,6 +185,10 @@ const SlidingWindow = struct { self.data.deleteOldest(prune_data_len); } + // Our data offset now moves to needle.len - 1 from the end so + // that we can handle the overlap case. + self.data_offset = self.data.len() - needle.len + 1; + self.assertIntegrity(); return null; } @@ -615,3 +622,55 @@ test "SlidingWindow two pages no match keeps both pages" { // No pruning because both pages are needed to fit needle. try testing.expectEqual(2, w.meta.len()); } + +test "SlidingWindow single append across circular buffer boundary" { + const testing = std.testing; + const alloc = testing.allocator; + + var w = try SlidingWindow.initEmpty(alloc); + defer w.deinit(alloc); + + var s = try Screen.init(alloc, 80, 24, 0); + defer s.deinit(); + try s.testWriteString("XXXXXXXXXXXXXXXXXXXboo!XXXXX"); + + // We are trying to break a circular buffer boundary so the way we + // do this is to duplicate the data then do a failing search. This + // will cause the first page to be pruned. The next time we append we'll + // put it in the middle of the circ buffer. We assert this so that if + // our implementation changes our test will fail. + try testing.expect(s.pages.pages.first == s.pages.pages.last); + const node: *PageList.List.Node = s.pages.pages.first.?; + try w.append(alloc, node); + try w.append(alloc, node); + { + // No wrap around yet + const slices = w.data.getPtrSlice(0, w.data.len()); + try testing.expect(slices[0].len > 0); + try testing.expect(slices[1].len == 0); + } + + // Search non-match, prunes page + try testing.expect(w.next("abc") == null); + try testing.expectEqual(1, w.meta.len()); + + // Add new page, now wraps + try w.append(alloc, node); + { + const slices = w.data.getPtrSlice(0, w.data.len()); + try testing.expect(slices[0].len > 0); + try testing.expect(slices[1].len > 0); + } + { + const sel = w.next("boo!").?; + try testing.expectEqual(point.Point{ .active = .{ + .x = 19, + .y = 0, + } }, s.pages.pointFromPin(.active, sel.start()).?); + try testing.expectEqual(point.Point{ .active = .{ + .x = 22, + .y = 0, + } }, s.pages.pointFromPin(.active, sel.end()).?); + } + try testing.expect(w.next("boo!") == null); +}