a modern tui library written in zig
1const std = @import("std");
2const unicode = std.unicode;
3const testing = std.testing;
4const DisplayWidth = @import("DisplayWidth");
5const code_point = @import("code_point");
6
7/// the method to use when calculating the width of a grapheme
8pub const Method = enum {
9 unicode,
10 wcwidth,
11 no_zwj,
12};
13
14/// returns the width of the provided string, as measured by the method chosen
15pub fn gwidth(str: []const u8, method: Method, data: *const DisplayWidth.DisplayWidthData) usize {
16 switch (method) {
17 .unicode => {
18 const dw: DisplayWidth = .{ .data = data };
19 return dw.strWidth(str);
20 },
21 .wcwidth => {
22 var total: usize = 0;
23 var iter: code_point.Iterator = .{ .bytes = str };
24 while (iter.next()) |cp| {
25 const w = switch (cp.code) {
26 // undo an override in zg for emoji skintone selectors
27 0x1f3fb...0x1f3ff,
28 => 2,
29 else => data.codePointWidth(cp.code),
30 };
31 if (w < 0) continue;
32 total += @intCast(w);
33 }
34 return total;
35 },
36 .no_zwj => {
37 var iter = std.mem.split(u8, str, "\u{200D}");
38 var result: usize = 0;
39 while (iter.next()) |s| {
40 result += gwidth(s, .unicode, data);
41 }
42 return result;
43 },
44 }
45}
46
47test "gwidth: a" {
48 const alloc = testing.allocator_instance.allocator();
49 const data = try DisplayWidth.DisplayWidthData.init(alloc);
50 defer data.deinit();
51 try testing.expectEqual(1, gwidth("a", .unicode, &data));
52 try testing.expectEqual(1, gwidth("a", .wcwidth, &data));
53 try testing.expectEqual(1, gwidth("a", .no_zwj, &data));
54}
55
56test "gwidth: emoji with ZWJ" {
57 const alloc = testing.allocator_instance.allocator();
58 const data = try DisplayWidth.DisplayWidthData.init(alloc);
59 defer data.deinit();
60 try testing.expectEqual(2, gwidth("👩🚀", .unicode, &data));
61 try testing.expectEqual(4, gwidth("👩🚀", .wcwidth, &data));
62 try testing.expectEqual(4, gwidth("👩🚀", .no_zwj, &data));
63}
64
65test "gwidth: emoji with VS16 selector" {
66 const alloc = testing.allocator_instance.allocator();
67 const data = try DisplayWidth.DisplayWidthData.init(alloc);
68 defer data.deinit();
69 try testing.expectEqual(2, gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .unicode, &data));
70 try testing.expectEqual(1, gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .wcwidth, &data));
71 try testing.expectEqual(2, gwidth("\xE2\x9D\xA4\xEF\xB8\x8F", .no_zwj, &data));
72}
73
74test "gwidth: emoji with skin tone selector" {
75 const alloc = testing.allocator_instance.allocator();
76 const data = try DisplayWidth.DisplayWidthData.init(alloc);
77 defer data.deinit();
78 try testing.expectEqual(2, gwidth("👋🏿", .unicode, &data));
79 try testing.expectEqual(4, gwidth("👋🏿", .wcwidth, &data));
80 try testing.expectEqual(2, gwidth("👋🏿", .no_zwj, &data));
81}