terminal: sliding window search can move the cursor

This commit is contained in:
Mitchell Hashimoto
2024-12-03 13:06:52 -05:00
parent 6ed298c9c1
commit d307b02e40
2 changed files with 75 additions and 5 deletions

View File

@ -56,6 +56,11 @@ pub fn CircBuf(comptime T: type, comptime default: T) type {
self.idx -|= @intCast(@abs(amount)); self.idx -|= @intCast(@abs(amount));
} }
} }
/// Reset the iterator back to the first value.
pub fn reset(self: *Iterator) void {
self.idx = 0;
}
}; };
/// Initialize a new circular buffer that can store size elements. /// Initialize a new circular buffer that can store size elements.

View File

@ -66,6 +66,11 @@ const SlidingWindow = struct {
/// data to meta. /// data to meta.
meta: MetaBuf, meta: MetaBuf,
/// Offset into data for our current state. This handles the
/// situation where our search moved through meta[0] but didn't
/// do enough to prune it.
data_offset: usize = 0,
const DataBuf = CircBuf(u8, 0); const DataBuf = CircBuf(u8, 0);
const MetaBuf = CircBuf(Meta, undefined); const MetaBuf = CircBuf(Meta, undefined);
const Meta = struct { const Meta = struct {
@ -98,31 +103,60 @@ const SlidingWindow = struct {
self.meta.deinit(alloc); self.meta.deinit(alloc);
} }
/// Clear all data but retain allocated capacity.
pub fn clearAndRetainCapacity(self: *SlidingWindow) void {
var meta_it = self.meta.iterator(.forward);
while (meta_it.next()) |meta| meta.deinit();
self.meta.clear();
self.data.clear();
self.data_offset = 0;
}
/// Search the window for the next occurrence of the needle. As /// Search the window for the next occurrence of the needle. As
/// the window moves, the window will prune itself while maintaining /// the window moves, the window will prune itself while maintaining
/// the invariant that the window is always big enough to contain /// the invariant that the window is always big enough to contain
/// the needle. /// the needle.
pub fn next(self: *SlidingWindow, needle: []const u8) ?Selection { pub fn next(self: *SlidingWindow, needle: []const u8) ?Selection {
const slices = self.data.getPtrSlice(0, self.data.len()); 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,
);
// Search the first slice for the needle. // Search the first slice for the needle.
if (std.mem.indexOf(u8, slices[0], needle)) |idx| { if (std.mem.indexOf(u8, slices[0], needle)) |idx| {
return self.selection(idx, needle.len); return self.selection(idx, needle.len);
} }
@panic("TODO"); // TODO: search overlap
// 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);
}
// No match. Clear everything.
self.clearAndRetainCapacity();
return null;
} }
/// Return a selection for the given start and length into the data /// Return a selection for the given start and length into the data
/// buffer and also prune the data/meta buffers if possible up to /// buffer and also prune the data/meta buffers if possible up to
/// this start index. /// this start index.
///
/// The start index is assumed to be relative to the offset. i.e.
/// index zero is actually at `self.data[self.data_offset]`. The
/// selection will account for the offset.
fn selection( fn selection(
self: *SlidingWindow, self: *SlidingWindow,
start: usize, start_offset: usize,
len: usize, len: usize,
) Selection { ) Selection {
const start = start_offset + self.data_offset;
assert(start < self.data.len()); assert(start < self.data.len());
assert(start + len < self.data.len()); assert(start + len <= self.data.len());
var meta_it = self.meta.iterator(.forward); var meta_it = self.meta.iterator(.forward);
const tl: Pin = pin(&meta_it, start); const tl: Pin = pin(&meta_it, start);
@ -132,8 +166,37 @@ const SlidingWindow = struct {
// same segment. // same segment.
meta_it.seekBy(-1); meta_it.seekBy(-1);
const br: Pin = pin(&meta_it, start + len - 1); const br: Pin = pin(&meta_it, start + len - 1);
assert(meta_it.idx >= 1);
// TODO: prune based on meta_it.idx // meta_it.idx is now the index after the br pin. We can
// safely prune our data up to this index. (It is after
// because next() is called at least once).
const br_meta_idx: usize = meta_it.idx - 1;
meta_it.reset();
var offset: usize = 0;
while (meta_it.next()) |meta| {
const meta_idx = start - offset;
if (meta_idx >= meta.cell_map.items.len) {
// Prior to our matches, we can prune it.
offset += meta.cell_map.items.len;
meta.deinit();
}
assert(meta_it.idx == br_meta_idx + 1);
break;
}
// If we have metas to prune, then prune them. They should be
// deinitialized already from the while loop above.
if (br_meta_idx > 0) {
assert(offset > 0);
self.meta.deleteOldest(br_meta_idx);
self.data.deleteOldest(offset);
@panic("TODO: TEST");
}
// Move our data one beyond so we don't rematch.
self.data_offset = start - offset + 1;
return Selection.init(tl, br, false); return Selection.init(tl, br, false);
} }
@ -316,6 +379,8 @@ test "SlidingWindow single append" {
.y = 0, .y = 0,
} }, s.pages.pointFromPin(.active, sel.end()).?); } }, s.pages.pointFromPin(.active, sel.end()).?);
} }
try testing.expect(w.next(needle) == null);
try testing.expect(w.next(needle) == null);
} }
test "SlidingWindow two pages" { test "SlidingWindow two pages" {