mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 17:26:09 +03:00
terminal/new: append/lookup graphemes and tests
This commit is contained in:
@ -20,7 +20,8 @@ const alignForward = std.mem.alignForward;
|
|||||||
/// is that most skin-tone emoji are <= 4 codepoints, letter combiners
|
/// is that most skin-tone emoji are <= 4 codepoints, letter combiners
|
||||||
/// are usually <= 4 codepoints, and 4 codepoints is a nice power of two
|
/// are usually <= 4 codepoints, and 4 codepoints is a nice power of two
|
||||||
/// for alignment.
|
/// for alignment.
|
||||||
const grapheme_chunk = 4 * @sizeOf(u21);
|
const grapheme_chunk_len = 4;
|
||||||
|
const grapheme_chunk = grapheme_chunk_len * @sizeOf(u21);
|
||||||
const GraphemeAlloc = BitmapAllocator(grapheme_chunk);
|
const GraphemeAlloc = BitmapAllocator(grapheme_chunk);
|
||||||
const grapheme_count_default = GraphemeAlloc.bitmap_bit_size;
|
const grapheme_count_default = GraphemeAlloc.bitmap_bit_size;
|
||||||
const grapheme_bytes_default = grapheme_count_default * grapheme_chunk;
|
const grapheme_bytes_default = grapheme_count_default * grapheme_chunk;
|
||||||
@ -201,6 +202,72 @@ pub const Page = struct {
|
|||||||
return .{ .row = row, .cell = cell };
|
return .{ .row = row, .cell = cell };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Append a codepoint to the given cell as a grapheme.
|
||||||
|
pub fn appendGrapheme(self: *Page, row: *Row, cell: *Cell, cp: u21) !void {
|
||||||
|
const cell_offset = getOffset(Cell, self.memory, cell);
|
||||||
|
var map = self.grapheme_map.map(self.memory);
|
||||||
|
|
||||||
|
// If this cell has no graphemes, we can go faster by knowing we
|
||||||
|
// need to allocate a new grapheme slice and update the map.
|
||||||
|
if (!cell.grapheme) {
|
||||||
|
const cps = try self.grapheme_alloc.alloc(u21, self.memory, 1);
|
||||||
|
errdefer self.grapheme_alloc.free(self.memory, cps);
|
||||||
|
cps[0] = cp;
|
||||||
|
|
||||||
|
try map.putNoClobber(cell_offset, .{
|
||||||
|
.offset = getOffset(u21, self.memory, @ptrCast(cps.ptr)),
|
||||||
|
.len = 1,
|
||||||
|
});
|
||||||
|
errdefer map.remove(cell_offset);
|
||||||
|
|
||||||
|
cell.grapheme = true;
|
||||||
|
row.grapheme = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The cell already has graphemes. We need to append to the existing
|
||||||
|
// grapheme slice and update the map.
|
||||||
|
assert(row.grapheme);
|
||||||
|
|
||||||
|
const slice = map.getPtr(cell_offset).?;
|
||||||
|
|
||||||
|
// If our slice len doesn't divide evenly by the grapheme chunk
|
||||||
|
// length then we can utilize the additional chunk space.
|
||||||
|
if (slice.len % grapheme_chunk_len != 0) {
|
||||||
|
const cps = slice.offset.ptr(self.memory);
|
||||||
|
cps[slice.len] = cp;
|
||||||
|
slice.len += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are out of chunk space. There is no fast path here. We need
|
||||||
|
// to allocate a larger chunk. This is a very slow path. We expect
|
||||||
|
// most graphemes to fit within our chunk size.
|
||||||
|
const cps = try self.grapheme_alloc.alloc(u21, self.memory, slice.len + 1);
|
||||||
|
errdefer self.grapheme_alloc.free(self.memory, cps);
|
||||||
|
const old_cps = slice.offset.ptr(self.memory)[0..slice.len];
|
||||||
|
@memcpy(cps[0..old_cps.len], old_cps);
|
||||||
|
cps[slice.len] = cp;
|
||||||
|
slice.* = .{
|
||||||
|
.offset = getOffset(u21, self.memory, @ptrCast(cps.ptr)),
|
||||||
|
.len = slice.len + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Free our old chunk
|
||||||
|
self.grapheme_alloc.free(self.memory, old_cps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the codepoints for the given cell. These are the codepoints
|
||||||
|
/// in addition to the first codepoint. The first codepoint is NOT
|
||||||
|
/// included since it is on the cell itself.
|
||||||
|
pub fn lookupGrapheme(self: *const Page, cell: *Cell) ?[]u21 {
|
||||||
|
const cell_offset = getOffset(Cell, self.memory, cell);
|
||||||
|
const map = self.grapheme_map.map(self.memory);
|
||||||
|
const slice = map.get(cell_offset) orelse return null;
|
||||||
|
return slice.offset.ptr(self.memory)[0..slice.len];
|
||||||
|
}
|
||||||
|
|
||||||
pub const Layout = struct {
|
pub const Layout = struct {
|
||||||
total_size: usize,
|
total_size: usize,
|
||||||
rows_start: usize,
|
rows_start: usize,
|
||||||
@ -490,3 +557,50 @@ test "Page read and write cells" {
|
|||||||
try testing.expectEqual(@as(u21, @intCast(y)), rac.cell.codepoint);
|
try testing.expectEqual(@as(u21, @intCast(y)), rac.cell.codepoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Page appendGrapheme small" {
|
||||||
|
var page = try Page.init(.{
|
||||||
|
.cols = 10,
|
||||||
|
.rows = 10,
|
||||||
|
.styles = 8,
|
||||||
|
});
|
||||||
|
defer page.deinit();
|
||||||
|
|
||||||
|
const rac = page.getRowAndCell(0, 0);
|
||||||
|
rac.cell.codepoint = 0x09;
|
||||||
|
|
||||||
|
// One
|
||||||
|
try page.appendGrapheme(rac.row, rac.cell, 0x0A);
|
||||||
|
try testing.expect(rac.row.grapheme);
|
||||||
|
try testing.expect(rac.cell.grapheme);
|
||||||
|
try testing.expectEqualSlices(u21, &.{0x0A}, page.lookupGrapheme(rac.cell).?);
|
||||||
|
|
||||||
|
// Two
|
||||||
|
try page.appendGrapheme(rac.row, rac.cell, 0x0B);
|
||||||
|
try testing.expect(rac.row.grapheme);
|
||||||
|
try testing.expect(rac.cell.grapheme);
|
||||||
|
try testing.expectEqualSlices(u21, &.{ 0x0A, 0x0B }, page.lookupGrapheme(rac.cell).?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Page appendGrapheme larger than chunk" {
|
||||||
|
var page = try Page.init(.{
|
||||||
|
.cols = 10,
|
||||||
|
.rows = 10,
|
||||||
|
.styles = 8,
|
||||||
|
});
|
||||||
|
defer page.deinit();
|
||||||
|
|
||||||
|
const rac = page.getRowAndCell(0, 0);
|
||||||
|
rac.cell.codepoint = 0x09;
|
||||||
|
|
||||||
|
const count = grapheme_chunk_len * 10;
|
||||||
|
for (0..count) |i| {
|
||||||
|
try page.appendGrapheme(rac.row, rac.cell, @intCast(0x0A + i));
|
||||||
|
}
|
||||||
|
|
||||||
|
const cps = page.lookupGrapheme(rac.cell).?;
|
||||||
|
try testing.expectEqual(@as(usize, count), cps.len);
|
||||||
|
for (0..count) |i| {
|
||||||
|
try testing.expectEqual(@as(u21, @intCast(0x0A + i)), cps[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user