mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-17 23:31:56 +00:00
terminal: move line searching here, unit test
This commit is contained in:
124
src/terminal/StringMap.zig
Normal file
124
src/terminal/StringMap.zig
Normal file
@@ -0,0 +1,124 @@
|
||||
/// A string along with the mapping of each individual byte in the string
|
||||
/// to the point in the screen.
|
||||
const StringMap = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const oni = @import("oniguruma");
|
||||
const point = @import("point.zig");
|
||||
const Selection = @import("Selection.zig");
|
||||
const Screen = @import("Screen.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
string: [:0]const u8,
|
||||
map: []point.ScreenPoint,
|
||||
|
||||
pub fn deinit(self: StringMap, alloc: Allocator) void {
|
||||
alloc.free(self.string);
|
||||
alloc.free(self.map);
|
||||
}
|
||||
|
||||
/// Returns an iterator that yields the next match of the given regex.
|
||||
pub fn searchIterator(
|
||||
self: StringMap,
|
||||
regex: oni.Regex,
|
||||
) SearchIterator {
|
||||
return .{ .map = self, .regex = regex };
|
||||
}
|
||||
|
||||
/// Iterates over the regular expression matches of the string.
|
||||
pub const SearchIterator = struct {
|
||||
map: StringMap,
|
||||
regex: oni.Regex,
|
||||
offset: usize = 0,
|
||||
|
||||
/// Returns the next regular expression match or null if there are
|
||||
/// no more matches.
|
||||
pub fn next(self: *SearchIterator) !?Match {
|
||||
if (self.offset >= self.map.string.len) return null;
|
||||
|
||||
var region = self.regex.search(
|
||||
self.map.string[self.offset..],
|
||||
.{},
|
||||
) catch |err| switch (err) {
|
||||
error.Mismatch => {
|
||||
self.offset = self.map.string.len;
|
||||
return null;
|
||||
},
|
||||
|
||||
else => return err,
|
||||
};
|
||||
errdefer region.deinit();
|
||||
|
||||
// Increment our offset by the number of bytes in the match.
|
||||
// We defer this so that we can return the match before
|
||||
// modifying the offset.
|
||||
const end_idx: usize = @intCast(region.ends()[0]);
|
||||
defer self.offset += end_idx;
|
||||
|
||||
return .{
|
||||
.map = self.map,
|
||||
.offset = self.offset,
|
||||
.region = region,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// A single regular expression match.
|
||||
pub const Match = struct {
|
||||
map: StringMap,
|
||||
offset: usize,
|
||||
region: oni.Region,
|
||||
|
||||
pub fn deinit(self: *Match) void {
|
||||
self.region.deinit();
|
||||
}
|
||||
|
||||
/// Returns the selection containing the full match.
|
||||
pub fn selection(self: Match) Selection {
|
||||
const start_idx: usize = @intCast(self.region.starts()[0]);
|
||||
const end_idx: usize = @intCast(self.region.ends()[0] - 1);
|
||||
const start_pt = self.map.map[self.offset + start_idx];
|
||||
const end_pt = self.map.map[self.offset + end_idx];
|
||||
return .{ .start = start_pt, .end = end_pt };
|
||||
}
|
||||
};
|
||||
|
||||
test "searchIterator" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
// Initialize our regex
|
||||
try oni.testing.ensureInit();
|
||||
var re = try oni.Regex.init(
|
||||
"[A-B]{2}",
|
||||
.{},
|
||||
oni.Encoding.utf8,
|
||||
oni.Syntax.default,
|
||||
null,
|
||||
);
|
||||
defer re.deinit();
|
||||
|
||||
// Initialize our screen
|
||||
var s = try Screen.init(alloc, 5, 5, 0);
|
||||
defer s.deinit();
|
||||
const str = "1ABCD2EFGH\n3IJKL";
|
||||
try s.testWriteString(str);
|
||||
const line = s.getLine(.{ .x = 2, .y = 1 }).?;
|
||||
const map = try line.stringMap(alloc);
|
||||
defer map.deinit(alloc);
|
||||
|
||||
// Get our iterator
|
||||
var it = map.searchIterator(re);
|
||||
{
|
||||
var match = (try it.next()).?;
|
||||
defer match.deinit();
|
||||
|
||||
const sel = match.selection();
|
||||
try testing.expectEqual(Selection{
|
||||
.start = .{ .x = 1, .y = 0 },
|
||||
.end = .{ .x = 2, .y = 0 },
|
||||
}, sel);
|
||||
}
|
||||
|
||||
try testing.expect(try it.next() == null);
|
||||
}
|
Reference in New Issue
Block a user