From 8a71cd4c8557c87dcf86a960b57a5f60d82b2f2d Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Mon, 29 Apr 2024 14:00:08 -0500 Subject: [PATCH] zg: complete replacement of ziglyph with zg --- build.zig | 10 +++----- build.zig.zon | 8 ++---- src/Key.zig | 22 +++-------------- src/Loop.zig | 7 +++++- src/Parser.zig | 12 +++++---- src/Tty.zig | 6 ++++- src/Unicode.zig | 10 +++++--- src/Vaxis.zig | 2 +- src/Window.zig | 24 +++++++++++------- src/gwidth.zig | 65 +++++++++++++++++++++++++++++-------------------- src/main.zig | 1 - 11 files changed, 88 insertions(+), 79 deletions(-) diff --git a/build.zig b/build.zig index fecddf9..fa7e0bf 100644 --- a/build.zig +++ b/build.zig @@ -6,10 +6,6 @@ pub fn build(b: *std.Build) void { const root_source_file = b.path("src/main.zig"); // Dependencies - const ziglyph_dep = b.dependency("ziglyph", .{ - .optimize = optimize, - .target = target, - }); const zg_dep = b.dependency("zg", .{ .optimize = optimize, .target = target, @@ -33,8 +29,9 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); - vaxis_mod.addImport("ziglyph", ziglyph_dep.module("ziglyph")); + vaxis_mod.addImport("code_point", zg_dep.module("code_point")); vaxis_mod.addImport("grapheme", zg_dep.module("grapheme")); + vaxis_mod.addImport("DisplayWidth", zg_dep.module("DisplayWidth")); vaxis_mod.addImport("zigimg", zigimg_dep.module("zigimg")); vaxis_mod.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer")); vaxis_mod.addImport("znvim", znvim_dep.module("znvim")); @@ -71,8 +68,9 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); - tests.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph")); + tests.root_module.addImport("code_point", zg_dep.module("code_point")); tests.root_module.addImport("grapheme", zg_dep.module("grapheme")); + tests.root_module.addImport("DisplayWidth", zg_dep.module("DisplayWidth")); tests.root_module.addImport("zigimg", zigimg_dep.module("zigimg")); tests.root_module.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer")); tests.root_module.addImport("znvim", znvim_dep.module("znvim")); diff --git a/build.zig.zon b/build.zig.zon index 4e8b7b1..1eb6420 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,13 +4,9 @@ .paths = .{""}, .minimum_zig_version = "0.12.0-dev.3397+43edd53c3", .dependencies = .{ - .ziglyph = .{ - .url = "git+https://codeberg.org/dude_the_builder/ziglyph/#947ed39203bf90412e3d16cbcf936518b6f23af0", - .hash = "12208b23d1eb6dcb929e85346524db8f8b8aa1401bdf8a97dee1e0cfb55da8d5fb42", - }, .zigimg = .{ - .url = "git+https://github.com/zigimg/zigimg#8873f29fc449e1b63400e9f4ad86d3c76204f962", - .hash = "122019f6439545235af116d0d8eb81fde1ff05fdb070da57c723772c557f84c5bf39", + .url = "git+https://github.com/zigimg/zigimg#637974e2d31dcdbc33f1e9cc8ffb2e46abd2e215", + .hash = "122012026c3a65ff1d4acba3b3fe80785f7cee9c6b4cdaff7ed0fbf23b0a6c803989", }, .gap_buffer = .{ .url = "https://github.com/ryleelyman/GapBuffer.zig/archive/6a746497d5a2494026d0f471e42556f1f153f153.tar.gz", diff --git a/src/Key.zig b/src/Key.zig index f3a46ed..8d8f6f6 100644 --- a/src/Key.zig +++ b/src/Key.zig @@ -1,6 +1,5 @@ const std = @import("std"); const testing = std.testing; -const ziglyph = @import("ziglyph"); const Key = @This(); @@ -101,25 +100,12 @@ pub fn matchText(self: Key, cp: u21, mods: Modifiers) bool { var self_mods = self.mods; self_mods.num_lock = false; + self_mods.shift = false; + self_mods.caps_lock = false; var arg_mods = mods; arg_mods.num_lock = false; - var code = cp; - // if the passed codepoint is upper, we consume all shift and caps mods for - // checking - if (ziglyph.isUpper(cp)) { - // consume mods - self_mods.shift = false; - self_mods.caps_lock = false; - arg_mods.shift = false; - arg_mods.caps_lock = false; - } else if (mods.shift or mods.caps_lock) { - // uppercase the cp and consume all mods - code = ziglyph.toUpper(cp); - self_mods.shift = false; - self_mods.caps_lock = false; - arg_mods.shift = false; - arg_mods.caps_lock = false; - } + arg_mods.shift = false; + arg_mods.caps_lock = false; var buf: [4]u8 = undefined; const n = std.unicode.utf8Encode(cp, buf[0..]) catch return false; diff --git a/src/Loop.zig b/src/Loop.zig index 38009d0..41d2285 100644 --- a/src/Loop.zig +++ b/src/Loop.zig @@ -20,7 +20,12 @@ pub fn Loop(comptime T: type) type { pub fn run(self: *Self) !void { if (self.thread) |_| return; if (self.vaxis.tty == null) self.vaxis.tty = try Tty.init(); - self.thread = try std.Thread.spawn(.{}, Tty.run, .{ &self.vaxis.tty.?, T, self }); + self.thread = try std.Thread.spawn(.{}, Tty.run, .{ + &self.vaxis.tty.?, + T, + self, + &self.vaxis.unicode.grapheme_data, + }); } /// stops reading from the tty and returns it to it's initial state diff --git a/src/Parser.zig b/src/Parser.zig index 066558b..03207c3 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -3,8 +3,8 @@ const testing = std.testing; const Event = @import("event.zig").Event; const Key = @import("Key.zig"); const Mouse = @import("Mouse.zig"); -const CodePointIterator = @import("ziglyph").CodePointIterator; -const graphemeBreak = @import("ziglyph").graphemeBreak; +const code_point = @import("code_point"); +const grapheme = @import("grapheme"); const log = std.log.scoped(.parser); @@ -59,6 +59,8 @@ const State = enum { // text-as-codepoints buf: [128]u8 = undefined, +grapheme_data: *const grapheme.GraphemeData, + pub fn parse(self: *Parser, input: []const u8) !Result { const n = input.len; @@ -104,15 +106,15 @@ pub fn parse(self: *Parser, input: []const u8) !Result { }, 0x7F => .{ .codepoint = Key.backspace }, else => blk: { - var iter: CodePointIterator = .{ .bytes = input[i..] }; + var iter: code_point.Iterator = .{ .bytes = input[i..] }; // return null if we don't have a valid codepoint var cp = iter.next() orelse return .{ .event = null, .n = 0 }; var code = cp.code; i += cp.len - 1; // subtract one for the loop iter - var g_state: u3 = 0; + var g_state: grapheme.State = .{}; while (iter.next()) |next_cp| { - if (graphemeBreak(cp.code, next_cp.code, &g_state)) { + if (grapheme.graphemeBreak(cp.code, next_cp.code, self.grapheme_data, &g_state)) { break; } code = Key.multicodepoint; diff --git a/src/Tty.zig b/src/Tty.zig index 360c7f5..a6d0d01 100644 --- a/src/Tty.zig +++ b/src/Tty.zig @@ -5,6 +5,7 @@ const Loop = @import("Loop.zig").Loop; const Parser = @import("Parser.zig"); const GraphemeCache = @import("GraphemeCache.zig"); const ctlseqs = @import("ctlseqs.zig"); +const grapheme = @import("grapheme"); const log = std.log.scoped(.tty); @@ -58,6 +59,7 @@ pub fn run( self: *Tty, comptime Event: type, loop: *Loop(Event), + grapheme_data: *const grapheme.GraphemeData, ) !void { // get our initial winsize @@ -105,7 +107,9 @@ pub fn run( // initialize a grapheme cache var cache: GraphemeCache = .{}; - var parser: Parser = .{}; + var parser: Parser = .{ + .grapheme_data = grapheme_data, + }; // initialize the read buffer var buf: [1024]u8 = undefined; diff --git a/src/Unicode.zig b/src/Unicode.zig index 8b3ea19..b9c3b4e 100644 --- a/src/Unicode.zig +++ b/src/Unicode.zig @@ -1,23 +1,25 @@ const std = @import("std"); const grapheme = @import("grapheme"); +const DisplayWidth = @import("DisplayWidth"); /// A thin wrapper around zg data const Unicode = @This(); grapheme_data: grapheme.GraphemeData, +width_data: DisplayWidth.DisplayWidthData, /// initialize all unicode data vaxis may possibly need pub fn init(alloc: std.mem.Allocator) !Unicode { - const grapheme_data = try grapheme.GraphemeData.init(alloc); - return .{ - .grapheme_data = grapheme_data, + .grapheme_data = try grapheme.GraphemeData.init(alloc), + .width_data = try DisplayWidth.DisplayWidthData.init(alloc), }; } /// free all data -pub fn deinit(self: *Unicode) void { +pub fn deinit(self: *const Unicode) void { self.grapheme_data.deinit(); + self.width_data.deinit(); } /// creates a grapheme iterator based on str diff --git a/src/Vaxis.zig b/src/Vaxis.zig index bd7216e..44580be 100644 --- a/src/Vaxis.zig +++ b/src/Vaxis.zig @@ -261,7 +261,7 @@ pub fn render(self: *Vaxis) !void { if (cell.char.width != 0) break :blk cell.char.width; const method: gwidth.Method = self.caps.unicode; - const width = gwidth.gwidth(cell.char.grapheme, method) catch 1; + const width = gwidth.gwidth(cell.char.grapheme, method, &self.unicode.width_data) catch 1; break :blk @max(1, width); }; std.debug.assert(w > 0); diff --git a/src/Window.zig b/src/Window.zig index c4d377e..2490567 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -4,6 +4,7 @@ const Screen = @import("Screen.zig"); const Cell = @import("Cell.zig"); const Mouse = @import("Mouse.zig"); const Segment = @import("Cell.zig").Segment; +const Unicode = @import("Unicode.zig"); const gw = @import("gwidth.zig"); const log = std.log.scoped(.window); @@ -200,7 +201,7 @@ pub fn clear(self: Window) void { /// returns the width of the grapheme. This depends on the terminal capabilities pub fn gwidth(self: Window, str: []const u8) usize { - return gw.gwidth(str, self.screen.width_method) catch 1; + return gw.gwidth(str, self.screen.width_method, &self.screen.unicode.width_data) catch 1; } /// fills the window with the provided cell @@ -270,7 +271,7 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !PrintResult var iter = self.screen.unicode.graphemeIterator(segment.text); while (iter.next()) |grapheme| { if (row >= self.height) break :blk true; - const s = grapheme.slice(segment.text); + const s = grapheme.bytes(segment.text); if (std.mem.eql(u8, s, "\n")) { row += 1; col = 0; @@ -350,9 +351,9 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !PrintResult else word; defer soft_wrapped = false; - var iter = self.screen.unicode.graphemeIterator(segment.text); + var iter = self.screen.unicode.graphemeIterator(printed_word); while (iter.next()) |grapheme| { - const s = grapheme.slice(printed_word); + const s = grapheme.bytes(printed_word); const w = self.gwidth(s); if (opts.commit) self.writeCell(col, row, .{ .char = .{ @@ -405,7 +406,7 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !PrintResult var iter = self.screen.unicode.graphemeIterator(segment.text); while (iter.next()) |grapheme| { if (col >= self.width) break :blk true; - const s = grapheme.slice(segment.text); + const s = grapheme.bytes(segment.text); if (std.mem.eql(u8, s, "\n")) break :blk true; const w = self.gwidth(s); if (w == 0) continue; @@ -521,9 +522,10 @@ test "Window size nested offsets" { } test "print: grapheme" { - var screen: Screen = .{ - .unicode = true, - }; + const alloc = std.testing.allocator_instance.allocator(); + const unicode = try Unicode.init(alloc); + defer unicode.deinit(); + var screen: Screen = .{ .width_method = .unicode, .unicode = &unicode }; const win: Window = .{ .x_off = 0, .y_off = 0, @@ -584,8 +586,12 @@ test "print: grapheme" { } test "print: word" { + const alloc = std.testing.allocator_instance.allocator(); + const unicode = try Unicode.init(alloc); + defer unicode.deinit(); var screen: Screen = .{ - .unicode = true, + .width_method = .unicode, + .unicode = &unicode, }; const win: Window = .{ .x_off = 0, diff --git a/src/gwidth.zig b/src/gwidth.zig index f41f37d..cccf316 100644 --- a/src/gwidth.zig +++ b/src/gwidth.zig @@ -1,7 +1,8 @@ const std = @import("std"); const unicode = std.unicode; const testing = std.testing; -const ziglyph = @import("ziglyph"); +const DisplayWidth = @import("DisplayWidth"); +const code_point = @import("code_point"); /// the method to use when calculating the width of a grapheme pub const Method = enum { @@ -11,18 +12,22 @@ pub const Method = enum { }; /// returns the width of the provided string, as measured by the method chosen -pub fn gwidth(str: []const u8, method: Method) !usize { +pub fn gwidth(str: []const u8, method: Method, data: *const DisplayWidth.DisplayWidthData) !usize { switch (method) { .unicode => { - return try ziglyph.display_width.strWidth(str, .half); + const dw: DisplayWidth = .{ .data = data }; + return dw.strWidth(str); }, .wcwidth => { var total: usize = 0; - const utf8 = try unicode.Utf8View.init(str); - var iter = utf8.iterator(); - - while (iter.nextCodepoint()) |cp| { - const w = ziglyph.display_width.codePointWidth(cp, .half); + var iter: code_point.Iterator = .{ .bytes = str }; + while (iter.next()) |cp| { + const w = switch (cp.code) { + // undo an override in zg for emoji skintone selectors + 0x1f3fb...0x1f3ff, + => 2, + else => data.codePointWidth(cp.code), + }; if (w < 0) continue; total += @intCast(w); } @@ -33,37 +38,43 @@ pub fn gwidth(str: []const u8, method: Method) !usize { if (str.len > out.len) return error.OutOfMemory; const n = std.mem.replacementSize(u8, str, "\u{200D}", ""); _ = std.mem.replace(u8, str, "\u{200D}", "", &out); - return gwidth(out[0..n], .unicode); + return gwidth(out[0..n], .unicode, data); }, } } test "gwidth: a" { - try testing.expectEqual(1, try gwidth("a", .unicode)); - try testing.expectEqual(1, try gwidth("a", .wcwidth)); - try testing.expectEqual(1, try gwidth("a", .no_zwj)); + const alloc = testing.allocator_instance.allocator(); + const data = try DisplayWidth.DisplayWidthData.init(alloc); + defer data.deinit(); + try testing.expectEqual(1, try gwidth("a", .unicode, &data)); + try testing.expectEqual(1, try gwidth("a", .wcwidth, &data)); + try testing.expectEqual(1, try gwidth("a", .no_zwj, &data)); } test "gwidth: emoji with ZWJ" { - try testing.expectEqual(2, try gwidth("👩‍🚀", .unicode)); - try testing.expectEqual(4, try gwidth("👩‍🚀", .wcwidth)); - try testing.expectEqual(4, try gwidth("👩‍🚀", .no_zwj)); + const alloc = testing.allocator_instance.allocator(); + const data = try DisplayWidth.DisplayWidthData.init(alloc); + defer data.deinit(); + try testing.expectEqual(2, try gwidth("👩‍🚀", .unicode, &data)); + try testing.expectEqual(4, try gwidth("👩‍🚀", .wcwidth, &data)); + try testing.expectEqual(4, try gwidth("👩‍🚀", .no_zwj, &data)); } test "gwidth: emoji with VS16 selector" { - try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .unicode)); - try testing.expectEqual(1, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .wcwidth)); - try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .no_zwj)); + const alloc = testing.allocator_instance.allocator(); + const data = try DisplayWidth.DisplayWidthData.init(alloc); + defer data.deinit(); + try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .unicode, &data)); + try testing.expectEqual(1, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .wcwidth, &data)); + try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .no_zwj, &data)); } test "gwidth: emoji with skin tone selector" { - try testing.expectEqual(2, try gwidth("👋🏿", .unicode)); - try testing.expectEqual(4, try gwidth("👋🏿", .wcwidth)); - try testing.expectEqual(2, try gwidth("👋🏿", .no_zwj)); -} - -test "gwidth: invalid string" { - try testing.expectError(error.InvalidUtf8, gwidth("\xc3\x28", .unicode)); - try testing.expectError(error.InvalidUtf8, gwidth("\xc3\x28", .wcwidth)); - try testing.expectError(error.InvalidUtf8, gwidth("\xc3\x28", .no_zwj)); + const alloc = testing.allocator_instance.allocator(); + const data = try DisplayWidth.DisplayWidthData.init(alloc); + defer data.deinit(); + try testing.expectEqual(2, try gwidth("👋🏿", .unicode, &data)); + try testing.expectEqual(4, try gwidth("👋🏿", .wcwidth, &data)); + try testing.expectEqual(2, try gwidth("👋🏿", .no_zwj, &data)); } diff --git a/src/main.zig b/src/main.zig index 72d7fc7..8461cfc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -17,7 +17,6 @@ pub const AllocatingScreen = @import("InternalScreen.zig"); pub const Winsize = @import("Tty.zig").Winsize; pub const Window = @import("Window.zig"); -pub const ziglyph = @import("ziglyph"); pub const widgets = @import("widgets.zig"); pub const gwidth = @import("gwidth.zig");