diff --git a/build.zig.zon b/build.zig.zon index 237720f35..e1bf7c814 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -41,6 +41,11 @@ .hash = "ziglyph-0.11.2-AAAAAHPtHwB4Mbzn1KvOV7Wpjo82NYEc_v0WC8oCLrkf", .lazy = true, }, + .zg = .{ + .url = "https://codeberg.org/jacobsandlund/zg/archive/21be4c72f2aff537d7630a7aeb641befc935db89.tar.gz", + .hash = "zg-0.14.0-oGqU3IkvswJcetRSabEnsUe_jsWz48WkZebbVS-pJ5jB", + .lazy = true, + }, .zig_wayland = .{ // codeberg ifreund/zig-wayland .url = "https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz", diff --git a/src/bench/grapheme-break.sh b/src/bench/grapheme-break.sh index 24f475caa..832728951 100755 --- a/src/bench/grapheme-break.sh +++ b/src/bench/grapheme-break.sh @@ -27,6 +27,8 @@ hyperfine \ "./zig-out/bin/bench-grapheme-break --mode=noop${ARGS} try benchNoop(reader, buf), .ziglyph => try benchZiglyph(reader, buf), + .zg => try benchZg(&graphemes, reader, buf), .table => try benchTable(reader, buf), } } @@ -118,6 +127,32 @@ noinline fn benchTable( } } +noinline fn benchZg( + graphemes: *const Graphemes, + reader: anytype, + buf: []u8, +) !void { + var d: UTF8Decoder = .{}; + var state: Graphemes.State = .{}; + var cp1: u21 = 0; + while (true) { + const n = try reader.read(buf); + if (n == 0) break; + + // Using stream.next directly with a for loop applies a naive + // scalar approach. + for (buf[0..n]) |c| { + const cp_, const consumed = d.next(c); + assert(consumed); + if (cp_) |cp2| { + const v = Graphemes.graphemeBreak(cp1, @intCast(cp2), graphemes, &state); + buf[0] = @intCast(@intFromBool(v)); + cp1 = cp2; + } + } + } +} + noinline fn benchZiglyph( reader: anytype, buf: []u8, diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index f173e4856..5ec6664f3 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -417,6 +417,18 @@ pub fn add( })) |dep| { step.root_module.addImport("ziglyph", dep.module("ziglyph")); } + if (b.lazyDependency("zg", .{ + .target = target, + .optimize = optimize, + })) |dep| { + step.root_module.addImport("CaseFolding", dep.module("CaseFolding")); + // Only needed for table gen, but still include to have build succeeed + step.root_module.addImport("DisplayWidth", dep.module("DisplayWidth")); + step.root_module.addImport("Emoji", dep.module("Emoji")); + step.root_module.addImport("GeneralCategories", dep.module("GeneralCategories")); + step.root_module.addImport("Graphemes", dep.module("Graphemes")); + step.root_module.addImport("LetterCasing", dep.module("LetterCasing")); + } if (b.lazyDependency("zf", .{ .target = target, .optimize = optimize, diff --git a/src/build/UnicodeTables.zig b/src/build/UnicodeTables.zig index 5bba2341b..10f2064f7 100644 --- a/src/build/UnicodeTables.zig +++ b/src/build/UnicodeTables.zig @@ -21,13 +21,11 @@ pub fn init(b: *std.Build) !UnicodeTables { }), }); - if (b.lazyDependency("ziglyph", .{ + if (b.lazyDependency("zg", .{ .target = b.graph.host, - })) |ziglyph_dep| { - exe.root_module.addImport( - "ziglyph", - ziglyph_dep.module("ziglyph"), - ); + })) |dep| { + exe.root_module.addImport("Graphemes", dep.module("Graphemes")); + exe.root_module.addImport("DisplayWidth", dep.module("DisplayWidth")); } const run = b.addRunArtifact(exe); diff --git a/src/config/Config.zig b/src/config/Config.zig index f36132ea9..7d4155362 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -16,7 +16,6 @@ const build_config = @import("../build_config.zig"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; -const global_state = &@import("../global.zig").state; const fontpkg = @import("../font/main.zig"); const inputpkg = @import("../input.zig"); const terminal = @import("../terminal/main.zig"); @@ -5765,6 +5764,8 @@ pub const Keybinds = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var set: Keybinds = .{}; try set.parseCLI(alloc, "shift+a=copy_to_clipboard"); @@ -5779,6 +5780,8 @@ pub const Keybinds = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var list: Keybinds = .{}; try list.parseCLI(alloc, "shift+a=csi:hello"); @@ -5795,6 +5798,8 @@ pub const Keybinds = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var list: Keybinds = .{}; try list.parseCLI(alloc, "ctrl+z>1=goto_tab:1"); @@ -5819,6 +5824,8 @@ pub const Keybinds = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var list: Keybinds = .{}; try list.parseCLI(alloc, "ctrl+a>ctrl+b>n=new_window"); @@ -6030,6 +6037,8 @@ pub const RepeatableCodepointMap = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var list: Self = .{}; try list.parseCLI(alloc, "U+ABCD=Comic Sans"); @@ -6067,6 +6076,8 @@ pub const RepeatableCodepointMap = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var list: Self = .{}; try list.parseCLI(alloc, "U+ABCD=Comic Sans"); @@ -6082,6 +6093,8 @@ pub const RepeatableCodepointMap = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var list: Self = .{}; try list.parseCLI(alloc, "U+0001 - U+0005=Verdana"); @@ -6097,6 +6110,8 @@ pub const RepeatableCodepointMap = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var list: Self = .{}; try list.parseCLI(alloc, "U+0006-U+0009, U+ABCD=Courier"); @@ -6175,6 +6190,8 @@ pub const FontStyle = union(enum) { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var p: Self = .{ .default = {} }; try p.parseCLI(alloc, "default"); @@ -6195,6 +6212,8 @@ pub const FontStyle = union(enum) { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var p: Self = .{ .default = {} }; try p.parseCLI(alloc, "default"); @@ -6210,6 +6229,8 @@ pub const FontStyle = union(enum) { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var p: Self = .{ .default = {} }; try p.parseCLI(alloc, "false"); @@ -6225,6 +6246,8 @@ pub const FontStyle = union(enum) { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var p: Self = .{ .default = {} }; try p.parseCLI(alloc, "bold"); @@ -6402,6 +6425,8 @@ pub const RepeatableCommand = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var list: RepeatableCommand = .{}; try list.parseCLI(alloc, "title:Foo,action:ignore"); @@ -6447,6 +6472,8 @@ pub const RepeatableCommand = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var list: RepeatableCommand = .{}; try list.parseCLI(alloc, "title:Bobr, action:text:Bober"); @@ -6462,6 +6489,8 @@ pub const RepeatableCommand = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var list: RepeatableCommand = .{}; try list.parseCLI(alloc, "title:Bobr, action:text:kurwa"); @@ -7180,6 +7209,8 @@ pub const Theme = struct { var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); // Single { @@ -7597,6 +7628,8 @@ const TestIterator = struct { test "parse hook: invalid command" { const testing = std.testing; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var cfg = try Config.default(testing.allocator); defer cfg.deinit(); const alloc = cfg._arena.?.allocator(); @@ -7608,6 +7641,8 @@ test "parse hook: invalid command" { test "parse e: command only" { const testing = std.testing; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var cfg = try Config.default(testing.allocator); defer cfg.deinit(); const alloc = cfg._arena.?.allocator(); @@ -7623,6 +7658,8 @@ test "parse e: command only" { test "parse e: command and args" { const testing = std.testing; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var cfg = try Config.default(testing.allocator); defer cfg.deinit(); const alloc = cfg._arena.?.allocator(); @@ -7641,6 +7678,8 @@ test "parse e: command and args" { test "clone default" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var source = try Config.default(alloc); defer source.deinit(); @@ -7658,6 +7697,8 @@ test "clone default" { test "clone preserves conditional state" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var a = try Config.default(alloc); defer a.deinit(); @@ -7686,6 +7727,8 @@ test "clone can then change conditional state" { var arena = ArenaAllocator.init(alloc); defer arena.deinit(); const alloc_arena = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); // Setup our test theme var td = try internal_os.TempDir.init(); @@ -7746,6 +7789,8 @@ test "clone can then change conditional state" { test "clone preserves conditional set" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var cfg = try Config.default(alloc); defer cfg.deinit(); @@ -7765,6 +7810,8 @@ test "clone preserves conditional set" { test "changed" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var source = try Config.default(alloc); defer source.deinit(); @@ -7779,6 +7826,8 @@ test "changed" { test "changeConditionalState ignores irrelevant changes" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); { var cfg = try Config.default(alloc); @@ -7798,6 +7847,8 @@ test "changeConditionalState ignores irrelevant changes" { test "changeConditionalState applies relevant changes" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); { var cfg = try Config.default(alloc); @@ -7820,6 +7871,8 @@ test "theme loading" { var arena = ArenaAllocator.init(alloc); defer arena.deinit(); const alloc_arena = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); // Setup our test theme var td = try internal_os.TempDir.init(); @@ -7856,6 +7909,8 @@ test "theme loading preserves conditional state" { var arena = ArenaAllocator.init(alloc); defer arena.deinit(); const alloc_arena = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); // Setup our test theme var td = try internal_os.TempDir.init(); @@ -7886,6 +7941,8 @@ test "theme priority is lower than config" { var arena = ArenaAllocator.init(alloc); defer arena.deinit(); const alloc_arena = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); // Setup our test theme var td = try internal_os.TempDir.init(); @@ -7920,6 +7977,8 @@ test "theme loading correct light/dark" { var arena = ArenaAllocator.init(alloc); defer arena.deinit(); const alloc_arena = arena.allocator(); + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); // Setup our test theme var td = try internal_os.TempDir.init(); @@ -8009,6 +8068,8 @@ test "theme loading correct light/dark" { test "theme specifying light/dark changes window-theme from auto" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); { var cfg = try Config.default(alloc); @@ -8027,6 +8088,8 @@ test "theme specifying light/dark changes window-theme from auto" { test "theme specifying light/dark sets theme usage in conditional state" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); { var cfg = try Config.default(alloc); diff --git a/src/config/c_get.zig b/src/config/c_get.zig index f235f596a..6be62bbc6 100644 --- a/src/config/c_get.zig +++ b/src/config/c_get.zig @@ -121,6 +121,8 @@ fn fieldByKey(self: *const Config, comptime k: Key) Value(k) { test "c_get: u8" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var c = try Config.default(alloc); defer c.deinit(); @@ -134,6 +136,8 @@ test "c_get: u8" { test "c_get: enum" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var c = try Config.default(alloc); defer c.deinit(); @@ -149,6 +153,8 @@ test "c_get: enum" { test "c_get: color" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var c = try Config.default(alloc); defer c.deinit(); @@ -164,6 +170,8 @@ test "c_get: color" { test "c_get: optional" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var c = try Config.default(alloc); defer c.deinit(); @@ -187,6 +195,8 @@ test "c_get: optional" { test "c_get: background-blur" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var c = try Config.default(alloc); defer c.deinit(); diff --git a/src/config/formatter.zig b/src/config/formatter.zig index cabf80953..89298d425 100644 --- a/src/config/formatter.zig +++ b/src/config/formatter.zig @@ -193,6 +193,8 @@ pub const FileFormatter = struct { test "format default config" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var cfg = try Config.default(alloc); defer cfg.deinit(); @@ -212,6 +214,8 @@ test "format default config" { test "format default config changed" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var cfg = try Config.default(alloc); defer cfg.deinit(); cfg.@"font-size" = 42; diff --git a/src/font/CodepointResolver.zig b/src/font/CodepointResolver.zig index 16536300c..5c87bfdb2 100644 --- a/src/font/CodepointResolver.zig +++ b/src/font/CodepointResolver.zig @@ -13,8 +13,9 @@ const CodepointResolver = @This(); const std = @import("std"); const Allocator = std.mem.Allocator; -const ziglyph = @import("ziglyph"); +const Emoji = @import("Emoji"); const font = @import("main.zig"); +const zg = &@import("../global.zig").state.zg; const Atlas = font.Atlas; const CodepointMap = font.CodepointMap; const Collection = font.Collection; @@ -150,7 +151,7 @@ pub fn getIndex( // we'll do this multiple times if we recurse, but this is a cached function // call higher up (GroupCache) so this should be rare. const p_mode: Collection.PresentationMode = if (p) |v| .{ .explicit = v } else .{ - .default = if (ziglyph.emoji.isEmojiPresentation(@intCast(cp))) + .default = if (Emoji.isEmojiPresentation(zg.emoji, @intCast(cp))) .emoji else .text, @@ -379,6 +380,8 @@ test getIndex { const testFont = font.embedded.regular; const testEmoji = font.embedded.emoji; const testEmojiText = font.embedded.emoji_text; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var lib = try Library.init(alloc); defer lib.deinit(); @@ -457,6 +460,8 @@ test "getIndex disabled font style" { const testing = std.testing; const alloc = testing.allocator; const testFont = font.embedded.regular; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); defer atlas_grayscale.deinit(alloc); @@ -512,6 +517,8 @@ test "getIndex disabled font style" { test "getIndex box glyph" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var lib = try Library.init(alloc); defer lib.deinit(); diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig index dcfa0a551..f24a7d816 100644 --- a/src/font/SharedGrid.zig +++ b/src/font/SharedGrid.zig @@ -369,6 +369,8 @@ fn testGrid(mode: TestMode, alloc: Allocator, lib: Library) !SharedGrid { test getIndex { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); // const testEmoji = @import("test.zig").fontEmoji; var lib = try Library.init(alloc); diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index e3e61907b..bba2d3e0c 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -694,6 +694,8 @@ pub const Key = struct { test "Key" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var cfg = try Config.default(alloc); defer cfg.deinit(); @@ -713,6 +715,8 @@ test "Key" { test "Key different font points" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var cfg = try Config.default(alloc); defer cfg.deinit(); @@ -731,6 +735,8 @@ test "Key different font points" { test "Key different font DPI" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var cfg = try Config.default(alloc); defer cfg.deinit(); @@ -749,6 +755,8 @@ test "Key different font DPI" { test SharedGridSet { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var set = try SharedGridSet.init(alloc); defer set.deinit(); diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig index 1aaa029dc..ae663ea39 100644 --- a/src/font/shaper/coretext.zig +++ b/src/font/shaper/coretext.zig @@ -576,6 +576,8 @@ pub const Shaper = struct { test "run iterator" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -656,6 +658,8 @@ test "run iterator" { test "run iterator: empty cells with background set" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -704,6 +708,8 @@ test "run iterator: empty cells with background set" { test "shape" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -737,6 +743,8 @@ test "shape" { test "shape nerd fonts" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaperWithFont(alloc, .nerd_font); defer testdata.deinit(); @@ -770,6 +778,8 @@ test "shape nerd fonts" { test "shape inconsolata ligs" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -824,6 +834,8 @@ test "shape inconsolata ligs" { test "shape monaspace ligs" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaperWithFont(alloc, .monaspace_neon); defer testdata.deinit(); @@ -856,6 +868,8 @@ test "shape monaspace ligs" { test "shape left-replaced lig in last run" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaperWithFont(alloc, .geist_mono); defer testdata.deinit(); @@ -888,6 +902,8 @@ test "shape left-replaced lig in last run" { test "shape left-replaced lig in early run" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaperWithFont(alloc, .geist_mono); defer testdata.deinit(); @@ -917,6 +933,8 @@ test "shape left-replaced lig in early run" { test "shape U+3C9 with JB Mono" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaperWithFont(alloc, .jetbrains_mono); defer testdata.deinit(); @@ -948,6 +966,8 @@ test "shape U+3C9 with JB Mono" { test "shape emoji width" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -977,6 +997,8 @@ test "shape emoji width" { test "shape emoji width long" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -1024,6 +1046,8 @@ test "shape emoji width long" { test "shape variation selector VS15" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -1057,6 +1081,8 @@ test "shape variation selector VS15" { test "shape variation selector VS16" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -1090,6 +1116,8 @@ test "shape variation selector VS16" { test "shape with empty cells in between" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -1121,6 +1149,8 @@ test "shape with empty cells in between" { test "shape Chinese characters" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -1161,6 +1191,8 @@ test "shape Chinese characters" { test "shape box glyphs" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -1198,6 +1230,8 @@ test "shape box glyphs" { test "shape selection boundary" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -1321,6 +1355,8 @@ test "shape selection boundary" { test "shape cursor boundary" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -1458,6 +1494,8 @@ test "shape cursor boundary" { test "shape cursor boundary and colored emoji" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -1552,6 +1590,8 @@ test "shape cursor boundary and colored emoji" { test "shape cell attribute change" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); @@ -1680,6 +1720,8 @@ test "shape high plane sprite font codepoint" { const testing = std.testing; const alloc = testing.allocator; + const zg = try @import("../../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var testdata = try testShaper(alloc); defer testdata.deinit(); diff --git a/src/font/shaper/web_canvas.zig b/src/font/shaper/web_canvas.zig index 4ed4b7db6..f6b39c7cd 100644 --- a/src/font/shaper/web_canvas.zig +++ b/src/font/shaper/web_canvas.zig @@ -1,9 +1,10 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const ziglyph = @import("ziglyph"); +const Graphemes = @import("Graphemes"); const font = @import("../main.zig"); const terminal = @import("../../terminal/main.zig"); +const zg = &@import("../../global.zig").state.zg; const log = std.log.scoped(.font_shaper); @@ -111,7 +112,7 @@ pub const Shaper = struct { // font ligatures. However, we do support grapheme clustering. // This means we can render things like skin tone emoji but // we can't render things like single glyph "=>". - var break_state: u3 = 0; + var break_state: Graphemes.State = .{}; var cp1: u21 = @intCast(codepoints[0]); var start: usize = 0; @@ -126,9 +127,10 @@ pub const Shaper = struct { const cp2: u21 = @intCast(codepoints[i]); defer cp1 = cp2; - break :blk ziglyph.graphemeBreak( + break :blk Graphemes.graphemeBreak( cp1, cp2, + zg.graphemes, &break_state, ); }; diff --git a/src/global.zig b/src/global.zig index 668d2faec..cdd3541dc 100644 --- a/src/global.zig +++ b/src/global.zig @@ -10,6 +10,12 @@ const oni = @import("oniguruma"); const crash = @import("crash/main.zig"); const renderer = @import("renderer.zig"); const apprt = @import("apprt.zig"); +const CaseFolding = @import("CaseFolding"); +const Emoji = @import("Emoji"); +const GeneralCategories = @import("GeneralCategories"); +const Graphemes = @import("Graphemes"); +const LetterCasing = @import("LetterCasing"); +const unicode = @import("unicode/main.zig"); /// We export the xev backend we want to use so that the rest of /// Ghostty can import this once and have access to the proper @@ -33,6 +39,7 @@ pub const GlobalState = struct { action: ?cli.Action, logging: Logging, rlimits: ResourceLimits = .{}, + zg: Zg, /// The app resources directory, equivalent to zig-out/share when we build /// from source. This is null if we can't detect it. @@ -64,6 +71,7 @@ pub const GlobalState = struct { .logging = .{ .stderr = {} }, .rlimits = .{}, .resources_dir = .{}, + .zg = undefined, }; errdefer self.deinit(); @@ -150,6 +158,8 @@ pub const GlobalState = struct { ); }; + self.zg = try Zg.init(self.alloc); + // const sentrylib = @import("sentry"); // if (sentrylib.captureEvent(sentrylib.Value.initMessageEvent( // .info, @@ -188,6 +198,8 @@ pub const GlobalState = struct { // Flush our crash logs crash.deinit(); + self.zg.deinit(self.alloc); + if (self.gpa) |*value| { // We want to ensure that we deinit the GPA because this is // the point at which it will output if there were safety violations. @@ -216,6 +228,42 @@ pub const GlobalState = struct { } }; +pub const Zg = struct { + case_folding: CaseFolding, + emoji: Emoji, + general_categories: GeneralCategories, + graphemes: Graphemes, + letter_casing: LetterCasing, + + pub fn init(alloc: std.mem.Allocator) !Zg { + return .{ + .case_folding = try CaseFolding.init(alloc), + .emoji = try Emoji.init(alloc), + .general_categories = try GeneralCategories.init(alloc), + .graphemes = try Graphemes.init(alloc), + .letter_casing = try LetterCasing.init(alloc), + }; + } + + pub fn deinit(self: *Zg, alloc: std.mem.Allocator) void { + self.case_folding.deinit(alloc); + self.emoji.deinit(alloc); + self.general_categories.deinit(alloc); + self.graphemes.deinit(alloc); + self.letter_casing.deinit(alloc); + } + + pub fn initForTesting() !*Zg { + const zg = &state.zg; + zg.* = try Zg.init(std.testing.allocator); + return zg; + } + + pub fn deinitForTesting(self: *Zg) void { + self.deinit(std.testing.allocator); + } +}; + /// Maintains the Unix resource limits that we set for our process. This /// can be used to restore the limits to their original values. pub const ResourceLimits = struct { diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 7cdb8047c..d87ac12c0 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -5,9 +5,11 @@ const Binding = @This(); const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const ziglyph = @import("ziglyph"); +const LetterCasing = @import("LetterCasing"); +const CaseFolding = @import("CaseFolding"); const key = @import("key.zig"); const KeyEvent = key.KeyEvent; +const zg = &@import("../global.zig").state.zg; /// The trigger that needs to be performed to execute the action. trigger: Trigger, @@ -1551,15 +1553,17 @@ pub const Trigger = struct { /// in more codepoints so we need to use a 3 element array. fn foldedCodepoint(cp: u21) [3]u21 { // ASCII fast path - if (ziglyph.letter.isAsciiLetter(cp)) { - return .{ ziglyph.letter.toLower(cp), 0, 0 }; + if (('A' <= cp and cp <= 'Z') or ('a' <= cp and cp <= 'z')) { + return .{ LetterCasing.toLower(zg.letter_casing, cp), 0, 0 }; } - // Unicode slow path. Case folding can resultin more codepoints. + // Unicode slow path. Case folding can result in more codepoints. // If more codepoints are produced then we return the codepoint // as-is which isn't correct but until we have a failing test // then I don't want to handle this. - return ziglyph.letter.toCaseFold(cp); + var buf: [3]u21 = .{ 0, 0, 0 }; + _ = CaseFolding.caseFold(&zg.case_folding, cp, &buf); + return buf; } /// Convert the trigger to a C API compatible trigger. @@ -2612,6 +2616,8 @@ test "parse: sequences" { test "set: parseAndPut typical binding" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2635,6 +2641,8 @@ test "set: parseAndPut typical binding" { test "set: parseAndPut unconsumed binding" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2659,6 +2667,8 @@ test "set: parseAndPut unconsumed binding" { test "set: parseAndPut removed binding" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2677,6 +2687,8 @@ test "set: parseAndPut removed binding" { test "set: parseAndPut sequence" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2701,6 +2713,8 @@ test "set: parseAndPut sequence" { test "set: parseAndPut sequence with two actions" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2733,6 +2747,8 @@ test "set: parseAndPut sequence with two actions" { test "set: parseAndPut overwrite sequence" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2758,6 +2774,8 @@ test "set: parseAndPut overwrite sequence" { test "set: parseAndPut overwrite leader" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2783,6 +2801,8 @@ test "set: parseAndPut overwrite leader" { test "set: parseAndPut unbind sequence unbinds leader" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2799,6 +2819,8 @@ test "set: parseAndPut unbind sequence unbinds leader" { test "set: parseAndPut unbind sequence unbinds leader if not set" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2814,6 +2836,8 @@ test "set: parseAndPut unbind sequence unbinds leader if not set" { test "set: parseAndPut sequence preserves reverse mapping" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2831,6 +2855,8 @@ test "set: parseAndPut sequence preserves reverse mapping" { test "set: put overwrites sequence" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2851,6 +2877,8 @@ test "set: put overwrites sequence" { test "set: maintains reverse mapping" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2879,6 +2907,8 @@ test "set: maintains reverse mapping" { test "set: performable is not part of reverse mappings" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2912,6 +2942,8 @@ test "set: performable is not part of reverse mappings" { test "set: overriding a mapping updates reverse" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2933,6 +2965,8 @@ test "set: overriding a mapping updates reverse" { test "set: consumed state" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2958,6 +2992,8 @@ test "set: consumed state" { test "set: getEvent physical" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -2988,6 +3024,8 @@ test "set: getEvent physical" { test "set: getEvent codepoint" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -3028,6 +3066,8 @@ test "set: getEvent codepoint" { test "set: getEvent codepoint case folding" { const testing = std.testing; const alloc = testing.allocator; + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); var s: Set = .{}; defer s.deinit(alloc); @@ -3070,6 +3110,8 @@ test "Action: clone" { var arena = std.heap.ArenaAllocator.init(testing.allocator); defer arena.deinit(); const alloc = arena.allocator(); + _ = try @import("../global.zig").Zg.initForTesting(); + defer zg.deinitForTesting(); { var a: Action = .ignore; diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index ef7122699..39ddc6ee1 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -1,12 +1,13 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const ziglyph = @import("ziglyph"); const font = @import("../font/main.zig"); const terminal = @import("../terminal/main.zig"); const renderer = @import("../renderer.zig"); const shaderpkg = renderer.Renderer.API.shaders; const ArrayListCollection = @import("../datastruct/array_list_collection.zig").ArrayListCollection; +const GeneralCategories = @import("GeneralCategories"); +const zg = &@import("../global.zig").state.zg; /// The possible cell content keys that exist. pub const Key = enum { @@ -246,8 +247,10 @@ pub fn fgMode( const cell = cell_pin.rowAndCell().cell; const cp = cell.codepoint(); - if (!ziglyph.general_category.isPrivateUse(cp) and - !ziglyph.blocks.isDingbats(cp)) + // If it's not Private Use (Co) or Dingbats (0x2700-0x27bf), use + // normal mode. + if (GeneralCategories.gc(zg.general_categories, cp) != .Co and + !(cp >= 0x2700 and cp <= 0x27bf)) { break :text .normal; } @@ -278,7 +281,8 @@ pub fn fgMode( // Powerline is whitespace if (isPowerline(prev_cp)) break :prev; - if (ziglyph.general_category.isPrivateUse(prev_cp)) { + // If it's Private Use (Co), then we use constrained mode. + if (GeneralCategories.gc(zg.general_categories, cp) == .Co) { break :text .constrained; } } diff --git a/src/simd/codepoint_width.zig b/src/simd/codepoint_width.zig index aab4bdd95..e94b9a300 100644 --- a/src/simd/codepoint_width.zig +++ b/src/simd/codepoint_width.zig @@ -4,7 +4,9 @@ const std = @import("std"); extern "c" fn ghostty_simd_codepoint_width(u32) i8; pub fn codepointWidth(cp: u32) i8 { - //return @import("ziglyph").display_width.codePointWidth(@intCast(cp), .half); + // const zg = try @import("../../global.zig").Zg.initForTesting(); + // defer zg.deinitForTesting(); + // try testing.expectEqual(@as(i8, 1), @import("DisplayWidth").codePointWidth(zg.display_width, @intCast(cp))); return ghostty_simd_codepoint_width(cp); } @@ -19,27 +21,41 @@ test "codepointWidth basic" { try testing.expectEqual(@as(i8, 2), codepointWidth(0xF900)); // 豈 try testing.expectEqual(@as(i8, 2), codepointWidth(0x20000)); // 𠀀 try testing.expectEqual(@as(i8, 2), codepointWidth(0x30000)); // 𠀀 - // try testing.expectEqual(@as(i8, 1), @import("ziglyph").display_width.codePointWidth(0x100, .half)); + // const zg = try @import("../../global.zig").Zg.initForTesting(); + // defer zg.deinitForTesting(); + // try testing.expectEqual(@as(i8, 1), @import("DisplayWidth").codePointWidth(zg.display_width, 0x100)); } // This is not very fast in debug modes, so its commented by default. // IMPORTANT: UNCOMMENT THIS WHENEVER MAKING CODEPOINTWIDTH CHANGES. -// test "codepointWidth matches ziglyph" { -// const testing = std.testing; -// const ziglyph = @import("ziglyph"); +//test "codepointWidth matches zg" { +// const testing = std.testing; +// const DisplayWidth = @import("DisplayWidth"); +// const display_width = try DisplayWidth.init(std.testing.allocator); +// defer display_width.deinit(std.testing.allocator); +// var success: bool = true; // -// const min = 0xFF + 1; // start outside ascii -// for (min..std.math.maxInt(u21)) |cp| { -// const simd = codepointWidth(@intCast(cp)); -// const zg = ziglyph.display_width.codePointWidth(@intCast(cp), .half); -// if (simd != zg) mismatch: { -// if (cp == 0x2E3B) { -// try testing.expectEqual(@as(i8, 2), simd); -// break :mismatch; -// } +// const min = 0xFF + 1; // start outside ascii +// for (min..0x110000) |cp| { +// const simd = codepointWidth(@intCast(cp)); +// const zg_width = DisplayWidth.codePointWidth(display_width, @intCast(cp)); +// if (simd != zg_width) mismatch: { +// if (cp == 0x2E3B) { +// try testing.expectEqual(@as(i8, 2), simd); +// std.log.warn("mismatch for 0x2e3b cp=U+{x} simd={} zg={}", .{ cp, simd, zg_width }); +// break :mismatch; +// } // -// std.log.warn("mismatch cp=U+{x} simd={} zg={}", .{ cp, simd, zg }); -// try testing.expect(false); -// } -// } -// } +// if (cp == 0x890) { +// try testing.expectEqual(@as(i8, 0), simd); +// try testing.expectEqual(@as(i8, 1), zg_width); +// break :mismatch; +// } +// +// std.log.warn("mismatch cp=U+{x} simd={} zg={}", .{ cp, simd, zg_width }); +// success = false; +// } +// } +// +// try testing.expect(success); +//} diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index dd7207f6d..11ac1346e 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -344,7 +344,7 @@ pub fn print(self: *Terminal, c: u21) !void { // VS15 makes it narrow. if (c == 0xFE0F or c == 0xFE0E) { // This only applies to emoji - const prev_props = unicode.getProperties(prev.cell.content.codepoint); + const prev_props = unicode.table.get(prev.cell.content.codepoint); const emoji = prev_props.grapheme_boundary_class.isExtendedPictographic(); if (!emoji) return; @@ -416,7 +416,7 @@ pub fn print(self: *Terminal, c: u21) !void { const width: usize = if (c <= 0xFF) 1 else @intCast(unicode.table.get(c).width); // Note: it is possible to have a width of "3" and a width of "-1" - // from ziglyph. We should look into those cases and handle them + // from zg. We should look into those cases and handle them // appropriately. assert(width <= 2); // log.debug("c={x} width={}", .{ c, width }); @@ -452,7 +452,7 @@ pub fn print(self: *Terminal, c: u21) !void { // If this is a emoji variation selector, prev must be an emoji if (c == 0xFE0F or c == 0xFE0E) { - const prev_props = unicode.getProperties(prev.content.codepoint); + const prev_props = unicode.table.get(prev.content.codepoint); const emoji = prev_props.grapheme_boundary_class == .extended_pictographic; if (!emoji) return; } diff --git a/src/unicode/grapheme.zig b/src/unicode/grapheme.zig index 7847ef6f5..2ba36161f 100644 --- a/src/unicode/grapheme.zig +++ b/src/unicode/grapheme.zig @@ -149,21 +149,19 @@ fn graphemeBreakClass( return true; } -/// If you build this file as a binary, we will verify the grapheme break -/// implementation. This iterates over billions of codepoints so it is -/// SLOW. It's not meant to be run in CI, but it's useful for debugging. -pub fn main() !void { +// This test will verify the grapheme break implementation. This iterates over billions of codepoints so it is SLOW. +// It's not meant to be run in CI, but it's useful for debugging. +test "grapheme break check against ziglyph" { const ziglyph = @import("ziglyph"); // Set the min and max to control the test range. const min = 0; const max = std.math.maxInt(u21) + 1; + var success: bool = true; var state: BreakState = .{}; var zg_state: u3 = 0; for (min..max) |cp1| { - if (cp1 % 1000 == 0) std.log.warn("progress cp1={}", .{cp1}); - if (cp1 == '\r' or cp1 == '\n' or ziglyph.grapheme_break.isControl(@intCast(cp1))) continue; @@ -174,6 +172,7 @@ pub fn main() !void { const gb = graphemeBreak(@intCast(cp1), @intCast(cp2), &state); const zg_gb = ziglyph.graphemeBreak(@intCast(cp1), @intCast(cp2), &zg_state); if (gb != zg_gb) { + success = false; std.log.warn("cp1={x} cp2={x} gb={} state={} zg_gb={} zg_state={}", .{ cp1, cp2, @@ -185,6 +184,8 @@ pub fn main() !void { } } } + + try std.testing.expect(success); } pub const std_options = struct { diff --git a/src/unicode/lut.zig b/src/unicode/lut.zig index 95c6a3688..84d51792d 100644 --- a/src/unicode/lut.zig +++ b/src/unicode/lut.zig @@ -11,7 +11,7 @@ const Allocator = std.mem.Allocator; /// can in theory be generated at runtime. /// /// Context must have two functions: -/// - `get(Context, u21) Elem`: returns the mapping for a given codepoint +/// - `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( diff --git a/src/unicode/main.zig b/src/unicode/main.zig index f5b911948..e8ba05b72 100644 --- a/src/unicode/main.zig +++ b/src/unicode/main.zig @@ -4,7 +4,6 @@ const grapheme = @import("grapheme.zig"); const props = @import("props.zig"); pub const table = props.table; pub const Properties = props.Properties; -pub const getProperties = props.get; pub const graphemeBreak = grapheme.graphemeBreak; pub const GraphemeBreakState = grapheme.BreakState; diff --git a/src/unicode/props.zig b/src/unicode/props.zig index 99c57aa0a..c4c147c70 100644 --- a/src/unicode/props.zig +++ b/src/unicode/props.zig @@ -1,9 +1,26 @@ const props = @This(); const std = @import("std"); const assert = std.debug.assert; -const ziglyph = @import("ziglyph"); +const Graphemes = @import("Graphemes"); +const DisplayWidth = @import("DisplayWidth"); const lut = @import("lut.zig"); +graphemes: Graphemes, +display_width: DisplayWidth, + +fn init(alloc: std.mem.Allocator) !props { + const graphemes = try Graphemes.init(alloc); + return .{ + .graphemes = graphemes, + .display_width = try DisplayWidth.initWithGraphemes(alloc, graphemes), + }; +} + +fn deinit(self: *props, alloc: std.mem.Allocator) void { + self.graphemes.deinit(alloc); + self.display_width.deinit(alloc); +} + /// The lookup tables for Ghostty. pub const table = table: { // This is only available after running main() below as part of the Ghostty @@ -79,31 +96,26 @@ pub const GraphemeBoundaryClass = enum(u4) { /// Gets the grapheme boundary class for a codepoint. This is VERY /// SLOW. The use case for this is only in generating lookup tables. - pub fn init(cp: u21) GraphemeBoundaryClass { - // We special-case modifier bases because we should not break - // if a modifier isn't next to a base. - if (ziglyph.emoji.isEmojiModifierBase(cp)) { - assert(ziglyph.emoji.isExtendedPictographic(cp)); - return .extended_pictographic_base; - } - - if (ziglyph.emoji.isEmojiModifier(cp)) return .emoji_modifier; - if (ziglyph.emoji.isExtendedPictographic(cp)) return .extended_pictographic; - if (ziglyph.grapheme_break.isL(cp)) return .L; - if (ziglyph.grapheme_break.isV(cp)) return .V; - if (ziglyph.grapheme_break.isT(cp)) return .T; - if (ziglyph.grapheme_break.isLv(cp)) return .LV; - if (ziglyph.grapheme_break.isLvt(cp)) return .LVT; - if (ziglyph.grapheme_break.isPrepend(cp)) return .prepend; - if (ziglyph.grapheme_break.isExtend(cp)) return .extend; - if (ziglyph.grapheme_break.isZwj(cp)) return .zwj; - if (ziglyph.grapheme_break.isSpacingmark(cp)) return .spacing_mark; - if (ziglyph.grapheme_break.isRegionalIndicator(cp)) return .regional_indicator; - - // This is obviously not INVALID invalid, there is SOME grapheme - // boundary class for every codepoint. But we don't care about - // anything that doesn't fit into the above categories. - return .invalid; + pub fn init(ctx: props, cp: u21) GraphemeBoundaryClass { + return switch (Graphemes.gbp(ctx.graphemes, cp)) { + .Emoji_Modifier_Base => .extended_pictographic_base, + .Emoji_Modifier => .emoji_modifier, + .Extended_Pictographic => .extended_pictographic, + .L => .L, + .V => .V, + .T => .T, + .LV => .LV, + .LVT => .LVT, + .Prepend => .prepend, + .Extend => .extend, + .ZWJ => .zwj, + .SpacingMark => .spacing_mark, + .Regional_Indicator => .regional_indicator, + // This is obviously not INVALID invalid, there is SOME grapheme + // boundary class for every codepoint. But we don't care about + // anything that doesn't fit into the above categories. + .none, .Control, .CR, .LF => .invalid, + }; } /// Returns true if this is an extended pictographic type. This @@ -120,13 +132,25 @@ pub const GraphemeBoundaryClass = enum(u4) { } }; -pub fn get(cp: u21) Properties { - const zg_width = ziglyph.display_width.codePointWidth(cp, .half); +pub fn get(ctx: props, cp: u21) !Properties { + if (cp > 0x10FFFF) { + return .{ + .width = 0, + .grapheme_boundary_class = .invalid, + }; + } else { + const zg_width = DisplayWidth.codePointWidth(ctx.display_width, cp); - return .{ - .width = @intCast(@min(2, @max(0, zg_width))), - .grapheme_boundary_class = .init(cp), - }; + return .{ + .width = @intCast(@min(2, @max(0, zg_width))), + .grapheme_boundary_class = .init(ctx, cp), + }; + } +} + +pub fn eql(ctx: props, a: Properties, b: Properties) bool { + _ = ctx; + return a.eql(b); } /// Runnable binary to generate the lookup tables and output to stdout. @@ -135,20 +159,13 @@ pub fn main() !void { defer arena_state.deinit(); const alloc = arena_state.allocator(); + var self = try init(alloc); + defer self.deinit(alloc); + const gen: lut.Generator( Properties, - struct { - pub fn get(ctx: @This(), cp: u21) !Properties { - _ = ctx; - return props.get(cp); - } - - pub fn eql(ctx: @This(), a: Properties, b: Properties) bool { - _ = ctx; - return a.eql(b); - } - }, - ) = .{}; + props, + ) = .{ .ctx = self }; const t = try gen.generate(alloc); defer alloc.free(t.stage1); @@ -166,16 +183,19 @@ pub fn main() !void { // This is not very fast in debug modes, so its commented by default. // IMPORTANT: UNCOMMENT THIS WHENEVER MAKING CODEPOINTWIDTH CHANGES. -// test "tables match ziglyph" { -// const testing = std.testing; +//test "tables match zg" { +// const testing = std.testing; // -// const min = 0xFF + 1; // start outside ascii -// for (min..std.math.maxInt(u21)) |cp| { -// const t = table.get(@intCast(cp)); -// const zg = @min(2, @max(0, ziglyph.display_width.codePointWidth(@intCast(cp), .half))); -// if (t.width != zg) { -// std.log.warn("mismatch cp=U+{x} t={} zg={}", .{ cp, t, zg }); -// try testing.expect(false); -// } -// } -// } +// const display_width = try DisplayWidth.init(std.testing.allocator); +// defer display_width.deinit(std.testing.allocator); +// +// const min = 0xFF + 1; // start outside ascii +// for (min..0x110000) |cp| { +// const t = table.get(@intCast(cp)); +// const zg = @min(2, @max(0, DisplayWidth.codePointWidth(display_width, @intCast(cp)))); +// if (t.width != zg) { +// std.log.warn("mismatch cp=U+{x} t={} zg={}", .{ cp, t, zg }); +// try testing.expect(false); +// } +// } +//}