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");
// 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"));

View file

@ -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",

View file

@ -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;
}
var buf: [4]u8 = undefined;
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 {
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

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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,

View file

@ -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));
}

View file

@ -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");