zg: complete replacement of ziglyph with zg

This commit is contained in:
Tim Culverhouse 2024-04-29 14:00:08 -05:00
parent 9fec6f122b
commit 8a71cd4c85
11 changed files with 88 additions and 79 deletions

View file

@ -6,10 +6,6 @@ pub fn build(b: *std.Build) void {
const root_source_file = b.path("src/main.zig"); const root_source_file = b.path("src/main.zig");
// Dependencies // Dependencies
const ziglyph_dep = b.dependency("ziglyph", .{
.optimize = optimize,
.target = target,
});
const zg_dep = b.dependency("zg", .{ const zg_dep = b.dependency("zg", .{
.optimize = optimize, .optimize = optimize,
.target = target, .target = target,
@ -33,8 +29,9 @@ pub fn build(b: *std.Build) void {
.target = target, .target = target,
.optimize = optimize, .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("grapheme", zg_dep.module("grapheme"));
vaxis_mod.addImport("DisplayWidth", zg_dep.module("DisplayWidth"));
vaxis_mod.addImport("zigimg", zigimg_dep.module("zigimg")); vaxis_mod.addImport("zigimg", zigimg_dep.module("zigimg"));
vaxis_mod.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer")); vaxis_mod.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer"));
vaxis_mod.addImport("znvim", znvim_dep.module("znvim")); vaxis_mod.addImport("znvim", znvim_dep.module("znvim"));
@ -71,8 +68,9 @@ pub fn build(b: *std.Build) void {
.target = target, .target = target,
.optimize = optimize, .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("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("zigimg", zigimg_dep.module("zigimg"));
tests.root_module.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer")); tests.root_module.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer"));
tests.root_module.addImport("znvim", znvim_dep.module("znvim")); tests.root_module.addImport("znvim", znvim_dep.module("znvim"));

View file

@ -4,13 +4,9 @@
.paths = .{""}, .paths = .{""},
.minimum_zig_version = "0.12.0-dev.3397+43edd53c3", .minimum_zig_version = "0.12.0-dev.3397+43edd53c3",
.dependencies = .{ .dependencies = .{
.ziglyph = .{
.url = "git+https://codeberg.org/dude_the_builder/ziglyph/#947ed39203bf90412e3d16cbcf936518b6f23af0",
.hash = "12208b23d1eb6dcb929e85346524db8f8b8aa1401bdf8a97dee1e0cfb55da8d5fb42",
},
.zigimg = .{ .zigimg = .{
.url = "git+https://github.com/zigimg/zigimg#8873f29fc449e1b63400e9f4ad86d3c76204f962", .url = "git+https://github.com/zigimg/zigimg#637974e2d31dcdbc33f1e9cc8ffb2e46abd2e215",
.hash = "122019f6439545235af116d0d8eb81fde1ff05fdb070da57c723772c557f84c5bf39", .hash = "122012026c3a65ff1d4acba3b3fe80785f7cee9c6b4cdaff7ed0fbf23b0a6c803989",
}, },
.gap_buffer = .{ .gap_buffer = .{
.url = "https://github.com/ryleelyman/GapBuffer.zig/archive/6a746497d5a2494026d0f471e42556f1f153f153.tar.gz", .url = "https://github.com/ryleelyman/GapBuffer.zig/archive/6a746497d5a2494026d0f471e42556f1f153f153.tar.gz",

View file

@ -1,6 +1,5 @@
const std = @import("std"); const std = @import("std");
const testing = std.testing; const testing = std.testing;
const ziglyph = @import("ziglyph");
const Key = @This(); const Key = @This();
@ -101,25 +100,12 @@ pub fn matchText(self: Key, cp: u21, mods: Modifiers) bool {
var self_mods = self.mods; var self_mods = self.mods;
self_mods.num_lock = false; self_mods.num_lock = false;
self_mods.shift = false;
self_mods.caps_lock = false;
var arg_mods = mods; var arg_mods = mods;
arg_mods.num_lock = false; 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.shift = false;
arg_mods.caps_lock = 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;
}
var buf: [4]u8 = undefined; var buf: [4]u8 = undefined;
const n = std.unicode.utf8Encode(cp, buf[0..]) catch return false; const n = std.unicode.utf8Encode(cp, buf[0..]) catch return false;

View file

@ -20,7 +20,12 @@ pub fn Loop(comptime T: type) type {
pub fn run(self: *Self) !void { pub fn run(self: *Self) !void {
if (self.thread) |_| return; if (self.thread) |_| return;
if (self.vaxis.tty == null) self.vaxis.tty = try Tty.init(); 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 /// stops reading from the tty and returns it to it's initial state

View file

@ -3,8 +3,8 @@ const testing = std.testing;
const Event = @import("event.zig").Event; const Event = @import("event.zig").Event;
const Key = @import("Key.zig"); const Key = @import("Key.zig");
const Mouse = @import("Mouse.zig"); const Mouse = @import("Mouse.zig");
const CodePointIterator = @import("ziglyph").CodePointIterator; const code_point = @import("code_point");
const graphemeBreak = @import("ziglyph").graphemeBreak; const grapheme = @import("grapheme");
const log = std.log.scoped(.parser); const log = std.log.scoped(.parser);
@ -59,6 +59,8 @@ const State = enum {
// text-as-codepoints // text-as-codepoints
buf: [128]u8 = undefined, buf: [128]u8 = undefined,
grapheme_data: *const grapheme.GraphemeData,
pub fn parse(self: *Parser, input: []const u8) !Result { pub fn parse(self: *Parser, input: []const u8) !Result {
const n = input.len; const n = input.len;
@ -104,15 +106,15 @@ pub fn parse(self: *Parser, input: []const u8) !Result {
}, },
0x7F => .{ .codepoint = Key.backspace }, 0x7F => .{ .codepoint = Key.backspace },
else => blk: { 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 // return null if we don't have a valid codepoint
var cp = iter.next() orelse return .{ .event = null, .n = 0 }; var cp = iter.next() orelse return .{ .event = null, .n = 0 };
var code = cp.code; var code = cp.code;
i += cp.len - 1; // subtract one for the loop iter 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| { 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; break;
} }
code = Key.multicodepoint; code = Key.multicodepoint;

View file

@ -5,6 +5,7 @@ const Loop = @import("Loop.zig").Loop;
const Parser = @import("Parser.zig"); const Parser = @import("Parser.zig");
const GraphemeCache = @import("GraphemeCache.zig"); const GraphemeCache = @import("GraphemeCache.zig");
const ctlseqs = @import("ctlseqs.zig"); const ctlseqs = @import("ctlseqs.zig");
const grapheme = @import("grapheme");
const log = std.log.scoped(.tty); const log = std.log.scoped(.tty);
@ -58,6 +59,7 @@ pub fn run(
self: *Tty, self: *Tty,
comptime Event: type, comptime Event: type,
loop: *Loop(Event), loop: *Loop(Event),
grapheme_data: *const grapheme.GraphemeData,
) !void { ) !void {
// get our initial winsize // get our initial winsize
@ -105,7 +107,9 @@ pub fn run(
// initialize a grapheme cache // initialize a grapheme cache
var cache: GraphemeCache = .{}; var cache: GraphemeCache = .{};
var parser: Parser = .{}; var parser: Parser = .{
.grapheme_data = grapheme_data,
};
// initialize the read buffer // initialize the read buffer
var buf: [1024]u8 = undefined; var buf: [1024]u8 = undefined;

View file

@ -1,23 +1,25 @@
const std = @import("std"); const std = @import("std");
const grapheme = @import("grapheme"); const grapheme = @import("grapheme");
const DisplayWidth = @import("DisplayWidth");
/// A thin wrapper around zg data /// A thin wrapper around zg data
const Unicode = @This(); const Unicode = @This();
grapheme_data: grapheme.GraphemeData, grapheme_data: grapheme.GraphemeData,
width_data: DisplayWidth.DisplayWidthData,
/// initialize all unicode data vaxis may possibly need /// initialize all unicode data vaxis may possibly need
pub fn init(alloc: std.mem.Allocator) !Unicode { pub fn init(alloc: std.mem.Allocator) !Unicode {
const grapheme_data = try grapheme.GraphemeData.init(alloc);
return .{ return .{
.grapheme_data = grapheme_data, .grapheme_data = try grapheme.GraphemeData.init(alloc),
.width_data = try DisplayWidth.DisplayWidthData.init(alloc),
}; };
} }
/// free all data /// free all data
pub fn deinit(self: *Unicode) void { pub fn deinit(self: *const Unicode) void {
self.grapheme_data.deinit(); self.grapheme_data.deinit();
self.width_data.deinit();
} }
/// creates a grapheme iterator based on str /// creates a grapheme iterator based on str

View file

@ -261,7 +261,7 @@ pub fn render(self: *Vaxis) !void {
if (cell.char.width != 0) break :blk cell.char.width; if (cell.char.width != 0) break :blk cell.char.width;
const method: gwidth.Method = self.caps.unicode; 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); break :blk @max(1, width);
}; };
std.debug.assert(w > 0); std.debug.assert(w > 0);

View file

@ -4,6 +4,7 @@ const Screen = @import("Screen.zig");
const Cell = @import("Cell.zig"); const Cell = @import("Cell.zig");
const Mouse = @import("Mouse.zig"); const Mouse = @import("Mouse.zig");
const Segment = @import("Cell.zig").Segment; const Segment = @import("Cell.zig").Segment;
const Unicode = @import("Unicode.zig");
const gw = @import("gwidth.zig"); const gw = @import("gwidth.zig");
const log = std.log.scoped(.window); 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 /// returns the width of the grapheme. This depends on the terminal capabilities
pub fn gwidth(self: Window, str: []const u8) usize { 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 /// 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); var iter = self.screen.unicode.graphemeIterator(segment.text);
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
if (row >= self.height) break :blk true; 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")) { if (std.mem.eql(u8, s, "\n")) {
row += 1; row += 1;
col = 0; col = 0;
@ -350,9 +351,9 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !PrintResult
else else
word; word;
defer soft_wrapped = false; defer soft_wrapped = false;
var iter = self.screen.unicode.graphemeIterator(segment.text); var iter = self.screen.unicode.graphemeIterator(printed_word);
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
const s = grapheme.slice(printed_word); const s = grapheme.bytes(printed_word);
const w = self.gwidth(s); const w = self.gwidth(s);
if (opts.commit) self.writeCell(col, row, .{ if (opts.commit) self.writeCell(col, row, .{
.char = .{ .char = .{
@ -405,7 +406,7 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !PrintResult
var iter = self.screen.unicode.graphemeIterator(segment.text); var iter = self.screen.unicode.graphemeIterator(segment.text);
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
if (col >= self.width) break :blk true; 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; if (std.mem.eql(u8, s, "\n")) break :blk true;
const w = self.gwidth(s); const w = self.gwidth(s);
if (w == 0) continue; if (w == 0) continue;
@ -521,9 +522,10 @@ test "Window size nested offsets" {
} }
test "print: grapheme" { test "print: grapheme" {
var screen: Screen = .{ const alloc = std.testing.allocator_instance.allocator();
.unicode = true, const unicode = try Unicode.init(alloc);
}; defer unicode.deinit();
var screen: Screen = .{ .width_method = .unicode, .unicode = &unicode };
const win: Window = .{ const win: Window = .{
.x_off = 0, .x_off = 0,
.y_off = 0, .y_off = 0,
@ -584,8 +586,12 @@ test "print: grapheme" {
} }
test "print: word" { test "print: word" {
const alloc = std.testing.allocator_instance.allocator();
const unicode = try Unicode.init(alloc);
defer unicode.deinit();
var screen: Screen = .{ var screen: Screen = .{
.unicode = true, .width_method = .unicode,
.unicode = &unicode,
}; };
const win: Window = .{ const win: Window = .{
.x_off = 0, .x_off = 0,

View file

@ -1,7 +1,8 @@
const std = @import("std"); const std = @import("std");
const unicode = std.unicode; const unicode = std.unicode;
const testing = std.testing; 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 /// the method to use when calculating the width of a grapheme
pub const Method = enum { 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 /// 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) { switch (method) {
.unicode => { .unicode => {
return try ziglyph.display_width.strWidth(str, .half); const dw: DisplayWidth = .{ .data = data };
return dw.strWidth(str);
}, },
.wcwidth => { .wcwidth => {
var total: usize = 0; var total: usize = 0;
const utf8 = try unicode.Utf8View.init(str); var iter: code_point.Iterator = .{ .bytes = str };
var iter = utf8.iterator(); while (iter.next()) |cp| {
const w = switch (cp.code) {
while (iter.nextCodepoint()) |cp| { // undo an override in zg for emoji skintone selectors
const w = ziglyph.display_width.codePointWidth(cp, .half); 0x1f3fb...0x1f3ff,
=> 2,
else => data.codePointWidth(cp.code),
};
if (w < 0) continue; if (w < 0) continue;
total += @intCast(w); total += @intCast(w);
} }
@ -33,37 +38,43 @@ pub fn gwidth(str: []const u8, method: Method) !usize {
if (str.len > out.len) return error.OutOfMemory; if (str.len > out.len) return error.OutOfMemory;
const n = std.mem.replacementSize(u8, str, "\u{200D}", ""); const n = std.mem.replacementSize(u8, str, "\u{200D}", "");
_ = std.mem.replace(u8, str, "\u{200D}", "", &out); _ = std.mem.replace(u8, str, "\u{200D}", "", &out);
return gwidth(out[0..n], .unicode); return gwidth(out[0..n], .unicode, data);
}, },
} }
} }
test "gwidth: a" { test "gwidth: a" {
try testing.expectEqual(1, try gwidth("a", .unicode)); const alloc = testing.allocator_instance.allocator();
try testing.expectEqual(1, try gwidth("a", .wcwidth)); const data = try DisplayWidth.DisplayWidthData.init(alloc);
try testing.expectEqual(1, try gwidth("a", .no_zwj)); 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" { test "gwidth: emoji with ZWJ" {
try testing.expectEqual(2, try gwidth("👩‍🚀", .unicode)); const alloc = testing.allocator_instance.allocator();
try testing.expectEqual(4, try gwidth("👩‍🚀", .wcwidth)); const data = try DisplayWidth.DisplayWidthData.init(alloc);
try testing.expectEqual(4, try gwidth("👩‍🚀", .no_zwj)); 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" { test "gwidth: emoji with VS16 selector" {
try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .unicode)); const alloc = testing.allocator_instance.allocator();
try testing.expectEqual(1, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .wcwidth)); const data = try DisplayWidth.DisplayWidthData.init(alloc);
try testing.expectEqual(2, try gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .no_zwj)); 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" { test "gwidth: emoji with skin tone selector" {
try testing.expectEqual(2, try gwidth("👋🏿", .unicode)); const alloc = testing.allocator_instance.allocator();
try testing.expectEqual(4, try gwidth("👋🏿", .wcwidth)); const data = try DisplayWidth.DisplayWidthData.init(alloc);
try testing.expectEqual(2, try gwidth("👋🏿", .no_zwj)); defer data.deinit();
} try testing.expectEqual(2, try gwidth("👋🏿", .unicode, &data));
try testing.expectEqual(4, try gwidth("👋🏿", .wcwidth, &data));
test "gwidth: invalid string" { try testing.expectEqual(2, try gwidth("👋🏿", .no_zwj, &data));
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));
} }

View file

@ -17,7 +17,6 @@ pub const AllocatingScreen = @import("InternalScreen.zig");
pub const Winsize = @import("Tty.zig").Winsize; pub const Winsize = @import("Tty.zig").Winsize;
pub const Window = @import("Window.zig"); pub const Window = @import("Window.zig");
pub const ziglyph = @import("ziglyph");
pub const widgets = @import("widgets.zig"); pub const widgets = @import("widgets.zig");
pub const gwidth = @import("gwidth.zig"); pub const gwidth = @import("gwidth.zig");