aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/unicode/lut.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/unicode/lut.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/unicode/lut.zig')
-rw-r--r--vendor/libghostty-vt/src/unicode/lut.zig188
1 files changed, 188 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/unicode/lut.zig b/vendor/libghostty-vt/src/unicode/lut.zig
new file mode 100644
index 00000000..da90f1ee
--- /dev/null
+++ b/vendor/libghostty-vt/src/unicode/lut.zig
@@ -0,0 +1,188 @@
+const std = @import("std");
+const assert = std.debug.assert;
+const Allocator = std.mem.Allocator;
+
+// This whole file is based on the algorithm described here:
+// https://here-be-braces.com/fast-lookup-of-unicode-properties/
+
+/// Creates a type that is able to generate a 3-level lookup table
+/// from a Unicode codepoint to a mapping of type Elem. The lookup table
+/// generally is expected to be codegen'd and then reloaded, although it
+/// can in theory be generated at runtime.
+///
+/// Context must have two functions:
+/// - `get(Context, u21) Elem`: returns the mapping for a given codepoint
+/// - `eql(Context, Elem, Elem) bool`: returns true if two mappings are equal
+///
+pub fn Generator(
+ comptime Elem: type,
+ comptime Context: type,
+) type {
+ return struct {
+ const Self = @This();
+
+ const block_size = 256;
+ const Block = [block_size]u16;
+
+ /// Mapping of a block to its index in the stage2 array.
+ const BlockMap = std.HashMap(
+ Block,
+ u16,
+ struct {
+ pub fn hash(ctx: @This(), k: Block) u64 {
+ _ = ctx;
+ var hasher = std.hash.Wyhash.init(0);
+ std.hash.autoHashStrat(&hasher, k, .DeepRecursive);
+ return hasher.final();
+ }
+
+ pub fn eql(ctx: @This(), a: Block, b: Block) bool {
+ _ = ctx;
+ return std.mem.eql(u16, &a, &b);
+ }
+ },
+ std.hash_map.default_max_load_percentage,
+ );
+
+ ctx: Context = undefined,
+
+ /// Generate the lookup tables. The arrays in the return value
+ /// are owned by the caller and must be freed.
+ pub fn generate(self: *const Self, alloc: Allocator) !Tables(Elem) {
+ // Maps block => stage2 index
+ var blocks_map = BlockMap.init(alloc);
+ defer blocks_map.deinit();
+
+ // Our stages
+ var stage1: std.ArrayList(u16) = .empty;
+ var stage2: std.ArrayList(u16) = .empty;
+ var stage3: std.ArrayList(Elem) = .empty;
+ defer {
+ stage1.deinit(alloc);
+ stage2.deinit(alloc);
+ stage3.deinit(alloc);
+ }
+
+ var block: Block = undefined;
+ var block_len: u16 = 0;
+ for (0..std.math.maxInt(u21) + 1) |cp| {
+ // Get our block value and find the matching result value
+ // in our list of possible values in stage3. This way, each
+ // possible mapping only gets one entry in stage3.
+ const elem = try self.ctx.get(@as(u21, @intCast(cp)));
+ const block_idx = block_idx: {
+ for (stage3.items, 0..) |item, i| {
+ if (self.ctx.eql(item, elem)) break :block_idx i;
+ }
+
+ const idx = stage3.items.len;
+ try stage3.append(alloc, elem);
+ break :block_idx idx;
+ };
+
+ // The block stores the mapping to the stage3 index
+ block[block_len] = std.math.cast(u16, block_idx) orelse return error.BlockTooLarge;
+ block_len += 1;
+
+ // If we still have space and we're not done with codepoints,
+ // we keep building up the block. Conversely: we finalize this
+ // block if we've filled it or are out of codepoints.
+ if (block_len < block_size and cp != std.math.maxInt(u21)) continue;
+ if (block_len < block_size) @memset(block[block_len..block_size], 0);
+
+ // Look for the stage2 index for this block. If it doesn't exist
+ // we add it to stage2 and update the mapping.
+ const gop = try blocks_map.getOrPut(block);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = std.math.cast(
+ u16,
+ stage2.items.len,
+ ) orelse return error.Stage2TooLarge;
+ for (block[0..block_len]) |entry| try stage2.append(alloc, entry);
+ }
+
+ // Map stage1 => stage2 and reset our block
+ try stage1.append(alloc, gop.value_ptr.*);
+ block_len = 0;
+ }
+
+ // All of our lengths must fit in a u16 for this to work
+ assert(stage1.items.len <= std.math.maxInt(u16));
+ assert(stage2.items.len <= std.math.maxInt(u16));
+ assert(stage3.items.len <= std.math.maxInt(u16));
+
+ const stage1_owned = try stage1.toOwnedSlice(alloc);
+ errdefer alloc.free(stage1_owned);
+ const stage2_owned = try stage2.toOwnedSlice(alloc);
+ errdefer alloc.free(stage2_owned);
+ const stage3_owned = try stage3.toOwnedSlice(alloc);
+ errdefer alloc.free(stage3_owned);
+
+ return .{
+ .stage1 = stage1_owned,
+ .stage2 = stage2_owned,
+ .stage3 = stage3_owned,
+ };
+ }
+ };
+}
+
+/// Creates a type that given a 3-level lookup table, can be used to
+/// look up a mapping for a given codepoint, encode it out to Zig, etc.
+pub fn Tables(comptime Elem: type) type {
+ return struct {
+ const Self = @This();
+
+ stage1: []const u16,
+ stage2: []const u16,
+ stage3: []const Elem,
+
+ /// Given a codepoint, returns the mapping for that codepoint.
+ pub inline fn get(self: *const Self, cp: u21) Elem {
+ const high = cp >> 8;
+ const low = cp & 0xFF;
+ return self.stage3[self.stage2[self.stage1[high] + low]];
+ }
+
+ /// Writes the lookup table as Zig to the given writer. The
+ /// written file exports three constants: stage1, stage2, and
+ /// stage3. These can be used to rebuild the lookup table in Zig.
+ pub fn writeZig(self: *const Self, writer: *std.Io.Writer) !void {
+ try writer.print(
+ \\//! This file is auto-generated. Do not edit.
+ \\
+ \\pub fn Tables(comptime Elem: type) type {{
+ \\ return struct {{
+ \\pub const stage1: [{}]u16 = .{{
+ , .{self.stage1.len});
+ for (self.stage1) |entry| try writer.print("{},", .{entry});
+
+ try writer.print(
+ \\
+ \\}};
+ \\
+ \\pub const stage2: [{}]u16 = .{{
+ , .{self.stage2.len});
+ for (self.stage2) |entry| try writer.print("{},", .{entry});
+ try writer.writeAll("};");
+
+ try writer.print(
+ \\
+ \\pub const stage3: [{}]Elem = .{{
+ , .{self.stage3.len});
+ for (self.stage3) |entry| {
+ if (@typeInfo(@TypeOf(entry)) == .@"struct" and
+ @hasDecl(@TypeOf(entry), "format"))
+ try writer.print("{f},", .{entry})
+ else
+ try writer.print("{},", .{entry});
+ }
+ try writer.writeAll(
+ \\};
+ \\ };
+ \\}
+ \\
+ );
+ }
+ };
+}