mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
terminal: sliding window search can move the cursor
This commit is contained in:
@ -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.
|
||||||
|
@ -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" {
|
||||||
|
Reference in New Issue
Block a user