From 0695997f93faac165b2f4d740fab63059bf3d5bc Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Wed, 4 Feb 2026 15:10:59 -0500 Subject: Port DisplayWidth --- build.zig | 1 + src/DisplayWidth.zig | 289 +++++++++++++++++++-------------------------------- 2 files changed, 106 insertions(+), 184 deletions(-) diff --git a/build.zig b/build.zig index aab8516..0e3d5aa 100644 --- a/build.zig +++ b/build.zig @@ -516,6 +516,7 @@ pub fn build(b: *std.Build) void { const test_unicode_step = b.step("unicode", "Rune unicode tests"); test_unicode_step.dependOn(&run_unicode_tests.step); + test_unicode_step.dependOn(&display_width_tr.step); const test_step = b.step("test", "Run all module tests"); test_step.dependOn(&run_unicode_tests.step); diff --git a/src/DisplayWidth.zig b/src/DisplayWidth.zig index dee7ebd..767c985 100644 --- a/src/DisplayWidth.zig +++ b/src/DisplayWidth.zig @@ -3,112 +3,66 @@ //! Answers questions about the printable width in monospaced fonts of the //! string of interest. -graphemes: Graphemes = undefined, -s1: []u16 = undefined, -s2: []i4 = undefined, -owns_graphemes: bool = true, +const Data = struct { + s1: []const u16 = undefined, + s2: []const i4 = undefined, +}; + +const display_width = display_width: { + const data = @import("dwp"); + break :display_width Data{ + .s1 = &data.s1, + .s2 = &data.s2, + }; +}; const DisplayWidth = @This(); -pub fn init(allocator: Allocator) Allocator.Error!DisplayWidth { - var dw = DisplayWidth{}; - try dw.setup(allocator); - errdefer { - allocator.free(dw.s1); - allocator.free(dw.s2); - } - dw.owns_graphemes = true; - dw.graphemes = try Graphemes.init(allocator); - errdefer dw.graphemes.deinit(allocator); - return dw; -} - -pub fn initWithGraphemes(allocator: Allocator, graphemes: Graphemes) Allocator.Error!DisplayWidth { - var dw = DisplayWidth{}; - try dw.setup(allocator); - dw.graphemes = graphemes; - dw.owns_graphemes = false; - return dw; -} - -pub fn setupWithGraphemes(dw: *DisplayWidth, allocator: Allocator, graphemes: Graphemes) Allocator.Error!void { - try dw.setup(allocator); - dw.graphemes = graphemes; - dw.owns_graphemes = false; -} - -// Sets up the DisplayWidthData, leaving the GraphemeData undefined. -pub fn setup(dw: *DisplayWidth, allocator: Allocator) Allocator.Error!void { - const in_bytes = @embedFile("dwp"); - var in_fbs = std.io.fixedBufferStream(in_bytes); - var reader = in_fbs.reader(); - - const endian = builtin.cpu.arch.endian(); - - const stage_1_len: u16 = reader.readInt(u16, endian) catch unreachable; - dw.s1 = try allocator.alloc(u16, stage_1_len); - errdefer allocator.free(dw.s1); - for (0..stage_1_len) |i| dw.s1[i] = reader.readInt(u16, endian) catch unreachable; - - const stage_2_len: u16 = reader.readInt(u16, endian) catch unreachable; - dw.s2 = try allocator.alloc(i4, stage_2_len); - errdefer allocator.free(dw.s2); - for (0..stage_2_len) |i| dw.s2[i] = @intCast(reader.readInt(i8, endian) catch unreachable); -} - -pub fn deinit(dw: *const DisplayWidth, allocator: Allocator) void { - allocator.free(dw.s1); - allocator.free(dw.s2); - if (dw.owns_graphemes) dw.graphemes.deinit(allocator); -} - /// codePointWidth returns the number of cells `cp` requires when rendered /// in a fixed-pitch font (i.e. a terminal screen). This can range from -1 to /// 3, where BACKSPACE and DELETE return -1 and 3-em-dash returns 3. C0/C1 /// control codes return 0. If `cjk` is true, ambiguous code points return 2, /// otherwise they return 1. -pub fn codePointWidth(dw: DisplayWidth, cp: u21) i4 { - return dw.s2[dw.s1[cp >> 8] + (cp & 0xff)]; +pub fn codePointWidth(cp: u21) i4 { + return display_width.s2[display_width.s1[cp >> 8] + (cp & 0xff)]; } test "codePointWidth" { - const dw = try DisplayWidth.init(std.testing.allocator); - defer dw.deinit(std.testing.allocator); - try testing.expectEqual(@as(i4, 0), dw.codePointWidth(0x0000)); // null - try testing.expectEqual(@as(i4, -1), dw.codePointWidth(0x8)); // \b - try testing.expectEqual(@as(i4, -1), dw.codePointWidth(0x7f)); // DEL - try testing.expectEqual(@as(i4, 0), dw.codePointWidth(0x0005)); // Cf - try testing.expectEqual(@as(i4, 0), dw.codePointWidth(0x0007)); // \a BEL - try testing.expectEqual(@as(i4, 0), dw.codePointWidth(0x000A)); // \n LF - try testing.expectEqual(@as(i4, 0), dw.codePointWidth(0x000B)); // \v VT - try testing.expectEqual(@as(i4, 0), dw.codePointWidth(0x000C)); // \f FF - try testing.expectEqual(@as(i4, 0), dw.codePointWidth(0x000D)); // \r CR - try testing.expectEqual(@as(i4, 0), dw.codePointWidth(0x000E)); // SQ - try testing.expectEqual(@as(i4, 0), dw.codePointWidth(0x000F)); // SI - - try testing.expectEqual(@as(i4, 0), dw.codePointWidth(0x070F)); // Cf - try testing.expectEqual(@as(i4, 1), dw.codePointWidth(0x0603)); // Cf Arabic - - try testing.expectEqual(@as(i4, 1), dw.codePointWidth(0x00AD)); // soft-hyphen - try testing.expectEqual(@as(i4, 2), dw.codePointWidth(0x2E3A)); // two-em dash - try testing.expectEqual(@as(i4, 3), dw.codePointWidth(0x2E3B)); // three-em dash - - try testing.expectEqual(@as(i4, 1), dw.codePointWidth(0x00BD)); // ambiguous halfwidth - - try testing.expectEqual(@as(i4, 1), dw.codePointWidth('Γ©')); - try testing.expectEqual(@as(i4, 2), dw.codePointWidth('😊')); - try testing.expectEqual(@as(i4, 2), dw.codePointWidth('统')); + try testing.expectEqual(@as(i4, 0), DisplayWidth.codePointWidth(0x0000)); // null + try testing.expectEqual(@as(i4, -1), DisplayWidth.codePointWidth(0x8)); // \b + try testing.expectEqual(@as(i4, -1), DisplayWidth.codePointWidth(0x7f)); // DEL + try testing.expectEqual(@as(i4, 0), DisplayWidth.codePointWidth(0x0005)); // Cf + try testing.expectEqual(@as(i4, 0), DisplayWidth.codePointWidth(0x0007)); // \a BEL + try testing.expectEqual(@as(i4, 0), DisplayWidth.codePointWidth(0x000A)); // \n LF + try testing.expectEqual(@as(i4, 0), DisplayWidth.codePointWidth(0x000B)); // \v VT + try testing.expectEqual(@as(i4, 0), DisplayWidth.codePointWidth(0x000C)); // \f FF + try testing.expectEqual(@as(i4, 0), DisplayWidth.codePointWidth(0x000D)); // \r CR + try testing.expectEqual(@as(i4, 0), DisplayWidth.codePointWidth(0x000E)); // SQ + try testing.expectEqual(@as(i4, 0), DisplayWidth.codePointWidth(0x000F)); // SI + + try testing.expectEqual(@as(i4, 0), DisplayWidth.codePointWidth(0x070F)); // Cf + try testing.expectEqual(@as(i4, 1), DisplayWidth.codePointWidth(0x0603)); // Cf Arabic + + try testing.expectEqual(@as(i4, 1), DisplayWidth.codePointWidth(0x00AD)); // soft-hyphen + try testing.expectEqual(@as(i4, 2), DisplayWidth.codePointWidth(0x2E3A)); // two-em dash + try testing.expectEqual(@as(i4, 3), DisplayWidth.codePointWidth(0x2E3B)); // three-em dash + + try testing.expectEqual(@as(i4, 1), DisplayWidth.codePointWidth(0x00BD)); // ambiguous halfwidth + + try testing.expectEqual(@as(i4, 1), DisplayWidth.codePointWidth('Γ©')); + try testing.expectEqual(@as(i4, 2), DisplayWidth.codePointWidth('😊')); + try testing.expectEqual(@as(i4, 2), DisplayWidth.codePointWidth('统')); } /// graphemeClusterWidth returns the total display width of `gc` as the number /// of cells required in a fixed-pitch font (i.e. a terminal screen). /// `gc` is a slice corresponding to one grapheme cluster. -pub fn graphemeClusterWidth(dw: DisplayWidth, gc: []const u8) isize { +pub fn graphemeClusterWidth(gc: []const u8) isize { var cp_iter = CodePointIterator{ .bytes = gc }; var gc_total: isize = 0; while (cp_iter.next()) |cp| { - var w = dw.codePointWidth(cp.code); + var w = DisplayWidth.codePointWidth(cp.code); if (w != 0) { // Handle text emoji sequence. @@ -130,59 +84,57 @@ pub fn graphemeClusterWidth(dw: DisplayWidth, gc: []const u8) isize { /// strWidth returns the total display width of `str` as the number of cells /// required in a fixed-pitch font (i.e. a terminal screen). -pub fn strWidth(dw: DisplayWidth, str: []const u8) usize { +pub fn strWidth(str: []const u8) usize { var total: isize = 0; // ASCII fast path if (ascii.isAsciiOnly(str)) { - for (str) |b| total += dw.codePointWidth(b); + for (str) |b| total += DisplayWidth.codePointWidth(b); return @intCast(@max(0, total)); } - var giter = dw.graphemes.iterator(str); + var giter = Graphemes.iterator(str); while (giter.next()) |gc| { - total += dw.graphemeClusterWidth(gc.bytes(str)); + total += DisplayWidth.graphemeClusterWidth(gc.bytes(str)); } return @intCast(@max(0, total)); } test "strWidth" { - const dw = try DisplayWidth.init(testing.allocator); - defer dw.deinit(testing.allocator); const c0 = options.c0_width orelse 0; - try testing.expectEqual(@as(usize, 5), dw.strWidth("Hello\r\n")); - try testing.expectEqual(@as(usize, 1), dw.strWidth("\u{0065}\u{0301}")); - try testing.expectEqual(@as(usize, 2), dw.strWidth("\u{1F476}\u{1F3FF}\u{0308}\u{200D}\u{1F476}\u{1F3FF}")); - try testing.expectEqual(@as(usize, 8), dw.strWidth("Hello 😊")); - try testing.expectEqual(@as(usize, 8), dw.strWidth("HΓ©llo 😊")); - try testing.expectEqual(@as(usize, 8), dw.strWidth("HΓ©llo :)")); - try testing.expectEqual(@as(usize, 8), dw.strWidth("HΓ©llo πŸ‡ͺπŸ‡Έ")); - try testing.expectEqual(@as(usize, 2), dw.strWidth("\u{26A1}")); // Lone emoji - try testing.expectEqual(@as(usize, 1), dw.strWidth("\u{26A1}\u{FE0E}")); // Text sequence - try testing.expectEqual(@as(usize, 2), dw.strWidth("\u{26A1}\u{FE0F}")); // Presentation sequence - try testing.expectEqual(@as(usize, 1), dw.strWidth("\u{2764}")); // Default text presentation - try testing.expectEqual(@as(usize, 1), dw.strWidth("\u{2764}\u{FE0E}")); // Default text presentation with VS15 selector - try testing.expectEqual(@as(usize, 2), dw.strWidth("\u{2764}\u{FE0F}")); // Default text presentation with VS16 selector + try testing.expectEqual(@as(usize, 5), DisplayWidth.strWidth("Hello\r\n")); + try testing.expectEqual(@as(usize, 1), DisplayWidth.strWidth("\u{0065}\u{0301}")); + try testing.expectEqual(@as(usize, 2), DisplayWidth.strWidth("\u{1F476}\u{1F3FF}\u{0308}\u{200D}\u{1F476}\u{1F3FF}")); + try testing.expectEqual(@as(usize, 8), DisplayWidth.strWidth("Hello 😊")); + try testing.expectEqual(@as(usize, 8), DisplayWidth.strWidth("HΓ©llo 😊")); + try testing.expectEqual(@as(usize, 8), DisplayWidth.strWidth("HΓ©llo :)")); + try testing.expectEqual(@as(usize, 8), DisplayWidth.strWidth("HΓ©llo πŸ‡ͺπŸ‡Έ")); + try testing.expectEqual(@as(usize, 2), DisplayWidth.strWidth("\u{26A1}")); // Lone emoji + try testing.expectEqual(@as(usize, 1), DisplayWidth.strWidth("\u{26A1}\u{FE0E}")); // Text sequence + try testing.expectEqual(@as(usize, 2), DisplayWidth.strWidth("\u{26A1}\u{FE0F}")); // Presentation sequence + try testing.expectEqual(@as(usize, 1), DisplayWidth.strWidth("\u{2764}")); // Default text presentation + try testing.expectEqual(@as(usize, 1), DisplayWidth.strWidth("\u{2764}\u{FE0E}")); // Default text presentation with VS15 selector + try testing.expectEqual(@as(usize, 2), DisplayWidth.strWidth("\u{2764}\u{FE0F}")); // Default text presentation with VS16 selector const expect_bs: usize = if (c0 == 0) 0 else 1 + c0; - try testing.expectEqual(expect_bs, dw.strWidth("A\x08")); // Backspace - try testing.expectEqual(expect_bs, dw.strWidth("\x7FA")); // DEL + try testing.expectEqual(expect_bs, DisplayWidth.strWidth("A\x08")); // Backspace + try testing.expectEqual(expect_bs, DisplayWidth.strWidth("\x7FA")); // DEL const expect_long_del: usize = if (c0 == 0) 0 else 1 + (c0 * 3); - try testing.expectEqual(expect_long_del, dw.strWidth("\x7FA\x08\x08")); // never less than 0 + try testing.expectEqual(expect_long_del, DisplayWidth.strWidth("\x7FA\x08\x08")); // never less than 0 // wcwidth Python lib tests. See: https://github.com/jquast/wcwidth/blob/master/tests/test_core.py const empty = ""; - try testing.expectEqual(@as(usize, 0), dw.strWidth(empty)); + try testing.expectEqual(@as(usize, 0), DisplayWidth.strWidth(empty)); const with_null = "hello\x00world"; - try testing.expectEqual(@as(usize, 10 + c0), dw.strWidth(with_null)); + try testing.expectEqual(@as(usize, 10 + c0), DisplayWidth.strWidth(with_null)); const hello_jp = "コンニチハ, γ‚»γ‚«γ‚€!"; - try testing.expectEqual(@as(usize, 19), dw.strWidth(hello_jp)); + try testing.expectEqual(@as(usize, 19), DisplayWidth.strWidth(hello_jp)); const control = "\x1b[0m"; - try testing.expectEqual(@as(usize, 3 + c0), dw.strWidth(control)); + try testing.expectEqual(@as(usize, 3 + c0), DisplayWidth.strWidth(control)); const balinese = "\u{1B13}\u{1B28}\u{1B2E}\u{1B44}"; - try testing.expectEqual(@as(usize, 3), dw.strWidth(balinese)); + try testing.expectEqual(@as(usize, 3), DisplayWidth.strWidth(balinese)); // These commented out tests require a new specification for complex scripts. // See: https://www.unicode.org/L2/L2023/23107-terminal-suppt.pdf @@ -196,20 +148,20 @@ test "strWidth" { // try testing.expectEqual(@as(usize, 3), strWidth(kannada_1)); // The following passes but as a mere coincidence. const kannada_2 = "\u{0cb0}\u{0cbc}\u{0ccd}\u{0c9a}"; - try testing.expectEqual(@as(usize, 2), dw.strWidth(kannada_2)); + try testing.expectEqual(@as(usize, 2), DisplayWidth.strWidth(kannada_2)); // From Rust https://github.com/jameslanska/unicode-display-width - try testing.expectEqual(@as(usize, 15), dw.strWidth("πŸ”₯πŸ—‘πŸ©πŸ‘©πŸ»β€πŸš€β°πŸ’ƒπŸΌπŸ”¦πŸ‘πŸ»")); - try testing.expectEqual(@as(usize, 2), dw.strWidth("πŸ¦€")); - try testing.expectEqual(@as(usize, 2), dw.strWidth("πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§")); - try testing.expectEqual(@as(usize, 2), dw.strWidth("πŸ‘©β€πŸ”¬")); - try testing.expectEqual(@as(usize, 9), dw.strWidth("sane text")); - try testing.expectEqual(@as(usize, 9), dw.strWidth("αΊ’ΜŒΓ‘Μ²lΝ”ΜΜžΜ„Μ‘ΝŒgΜ–Μ˜Μ˜Μ”Μ”Ν’ΝžΝoΜͺΜ”TΜ’Μ™Μ«ΜˆΜΝžeΜ¬ΝˆΝ•ΝŒΜΝ‘x̺̍ṭ̓̓ͅ")); - try testing.expectEqual(@as(usize, 17), dw.strWidth("μŠ¬λΌλ°” μš°ν¬λΌμ΄λ‚˜")); - try testing.expectEqual(@as(usize, 1), dw.strWidth("\u{378}")); + try testing.expectEqual(@as(usize, 15), DisplayWidth.strWidth("πŸ”₯πŸ—‘πŸ©πŸ‘©πŸ»β€πŸš€β°πŸ’ƒπŸΌπŸ”¦πŸ‘πŸ»")); + try testing.expectEqual(@as(usize, 2), DisplayWidth.strWidth("πŸ¦€")); + try testing.expectEqual(@as(usize, 2), DisplayWidth.strWidth("πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§")); + try testing.expectEqual(@as(usize, 2), DisplayWidth.strWidth("πŸ‘©β€πŸ”¬")); + try testing.expectEqual(@as(usize, 9), DisplayWidth.strWidth("sane text")); + try testing.expectEqual(@as(usize, 9), DisplayWidth.strWidth("αΊ’ΜŒΓ‘Μ²lΝ”ΜΜžΜ„Μ‘ΝŒgΜ–Μ˜Μ˜Μ”Μ”Ν’ΝžΝoΜͺΜ”TΜ’Μ™Μ«ΜˆΜΝžeΜ¬ΝˆΝ•ΝŒΜΝ‘x̺̍ṭ̓̓ͅ")); + try testing.expectEqual(@as(usize, 17), DisplayWidth.strWidth("μŠ¬λΌλ°” μš°ν¬λΌμ΄λ‚˜")); + try testing.expectEqual(@as(usize, 1), DisplayWidth.strWidth("\u{378}")); // https://codeberg.org/atman/zg/issues/82 - try testing.expectEqual(@as(usize, 12), dw.strWidth("✍️✍🏻✍🏼✍🏽✍🏾✍🏿")); + try testing.expectEqual(@as(usize, 12), DisplayWidth.strWidth("✍️✍🏻✍🏼✍🏽✍🏾✍🏿")); } /// centers `str` in a new string of width `total_width` (in display cells) using `pad` as padding. @@ -217,17 +169,16 @@ test "strWidth" { /// receive one additional pad. This makes sure the returned string fills the requested width. /// Caller must free returned bytes with `allocator`. pub fn center( - dw: DisplayWidth, allocator: mem.Allocator, str: []const u8, total_width: usize, pad: []const u8, ) ![]u8 { - const str_width = dw.strWidth(str); + const str_width = DisplayWidth.strWidth(str); if (str_width > total_width) return error.StrTooLong; if (str_width == total_width) return try allocator.dupe(u8, str); - const pad_width = dw.strWidth(pad); + const pad_width = DisplayWidth.strWidth(pad); if (pad_width > total_width or str_width + pad_width > total_width) return error.PadTooLong; const margin_width = @divFloor((total_width - str_width), 2); @@ -258,61 +209,58 @@ pub fn center( test "center" { const allocator = testing.allocator; - const dw = try DisplayWidth.init(allocator); - defer dw.deinit(allocator); // Input and width both have odd length - var centered = try dw.center(allocator, "abc", 9, "*"); + var centered = try DisplayWidth.center(allocator, "abc", 9, "*"); try testing.expectEqualSlices(u8, "***abc***", centered); // Input and width both have even length - testing.allocator.free(centered); - centered = try dw.center(allocator, "w😊w", 10, "-"); + allocator.free(centered); + centered = try DisplayWidth.center(allocator, "w😊w", 10, "-"); try testing.expectEqualSlices(u8, "---w😊w---", centered); // Input has even length, width has odd length - testing.allocator.free(centered); - centered = try dw.center(allocator, "1234", 9, "-"); + allocator.free(centered); + centered = try DisplayWidth.center(allocator, "1234", 9, "-"); try testing.expectEqualSlices(u8, "--1234---", centered); // Input has odd length, width has even length - testing.allocator.free(centered); - centered = try dw.center(allocator, "123", 8, "-"); + allocator.free(centered); + centered = try DisplayWidth.center(allocator, "123", 8, "-"); try testing.expectEqualSlices(u8, "--123---", centered); // Input is the same length as the width - testing.allocator.free(centered); - centered = try dw.center(allocator, "123", 3, "-"); + allocator.free(centered); + centered = try DisplayWidth.center(allocator, "123", 3, "-"); try testing.expectEqualSlices(u8, "123", centered); // Input is empty - testing.allocator.free(centered); - centered = try dw.center(allocator, "", 3, "-"); + allocator.free(centered); + centered = try DisplayWidth.center(allocator, "", 3, "-"); try testing.expectEqualSlices(u8, "---", centered); // Input is empty and width is zero - testing.allocator.free(centered); - centered = try dw.center(allocator, "", 0, "-"); + allocator.free(centered); + centered = try DisplayWidth.center(allocator, "", 0, "-"); try testing.expectEqualSlices(u8, "", centered); // Input is longer than the width, which is an error - testing.allocator.free(centered); - try testing.expectError(error.StrTooLong, dw.center(allocator, "123", 2, "-")); + allocator.free(centered); + try testing.expectError(error.StrTooLong, DisplayWidth.center(allocator, "123", 2, "-")); } /// padLeft returns a new string of width `total_width` (in display cells) using `pad` as padding /// on the left side. Caller must free returned bytes with `allocator`. pub fn padLeft( - dw: DisplayWidth, allocator: mem.Allocator, str: []const u8, total_width: usize, pad: []const u8, ) ![]u8 { - const str_width = dw.strWidth(str); + const str_width = DisplayWidth.strWidth(str); if (str_width > total_width) return error.StrTooLong; - const pad_width = dw.strWidth(pad); + const pad_width = DisplayWidth.strWidth(pad); if (pad_width > total_width or str_width + pad_width > total_width) return error.PadTooLong; const margin_width = total_width - str_width; @@ -336,31 +284,28 @@ pub fn padLeft( test "padLeft" { const allocator = testing.allocator; - const dw = try DisplayWidth.init(allocator); - defer dw.deinit(allocator); - var right_aligned = try dw.padLeft(allocator, "abc", 9, "*"); - defer testing.allocator.free(right_aligned); + var right_aligned = try DisplayWidth.padLeft(allocator, "abc", 9, "*"); + defer allocator.free(right_aligned); try testing.expectEqualSlices(u8, "******abc", right_aligned); - testing.allocator.free(right_aligned); - right_aligned = try dw.padLeft(allocator, "w😊w", 10, "-"); + allocator.free(right_aligned); + right_aligned = try DisplayWidth.padLeft(allocator, "w😊w", 10, "-"); try testing.expectEqualSlices(u8, "------w😊w", right_aligned); } /// padRight returns a new string of width `total_width` (in display cells) using `pad` as padding /// on the right side. Caller must free returned bytes with `allocator`. pub fn padRight( - dw: DisplayWidth, allocator: mem.Allocator, str: []const u8, total_width: usize, pad: []const u8, ) ![]u8 { - const str_width = dw.strWidth(str); + const str_width = DisplayWidth.strWidth(str); if (str_width > total_width) return error.StrTooLong; - const pad_width = dw.strWidth(pad); + const pad_width = DisplayWidth.strWidth(pad); if (pad_width > total_width or str_width + pad_width > total_width) return error.PadTooLong; const margin_width = total_width - str_width; @@ -385,15 +330,13 @@ pub fn padRight( test "padRight" { const allocator = testing.allocator; - const dw = try DisplayWidth.init(allocator); - defer dw.deinit(allocator); - var left_aligned = try dw.padRight(allocator, "abc", 9, "*"); + var left_aligned = try DisplayWidth.padRight(allocator, "abc", 9, "*"); defer testing.allocator.free(left_aligned); try testing.expectEqualSlices(u8, "abc******", left_aligned); testing.allocator.free(left_aligned); - left_aligned = try dw.padRight(allocator, "w😊w", 10, "-"); + left_aligned = try DisplayWidth.padRight(allocator, "w😊w", 10, "-"); try testing.expectEqualSlices(u8, "w😊w------", left_aligned); } @@ -401,7 +344,6 @@ test "padRight" { /// `threshold` defines how far the last column of the last word can be /// from the edge. Caller must free returned bytes with `allocator`. pub fn wrap( - dw: DisplayWidth, allocator: mem.Allocator, str: []const u8, columns: usize, @@ -419,7 +361,7 @@ pub fn wrap( while (word_iter.next()) |word| { try result.appendSlice(word); try result.append(' '); - line_width += dw.strWidth(word) + 1; + line_width += DisplayWidth.strWidth(word) + 1; if (line_width > columns or columns - line_width <= threshold) { try result.append('\n'); @@ -439,11 +381,9 @@ pub fn wrap( test "wrap" { const allocator = testing.allocator; - const dw = try DisplayWidth.init(allocator); - defer dw.deinit(allocator); const input = "The quick brown fox\r\njumped over the lazy dog!"; - const got = try dw.wrap(allocator, input, 10, 3); + const got = try DisplayWidth.wrap(allocator, input, 10, 3); defer testing.allocator.free(got); const want = "The quick \nbrown fox \njumped \nover the \nlazy dog!"; try testing.expectEqualStrings(want, got); @@ -453,31 +393,12 @@ test "zg/74" { var debug_alloc = std.heap.DebugAllocator(.{}).init; const allocator = debug_alloc.allocator(); defer _ = debug_alloc.deinit(); - const dw = try DisplayWidth.init(allocator); - defer dw.deinit(allocator); - const wrapped = try dw.wrap(allocator, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pellentesque pulvinar felis, sit amet commodo ligula feugiat sed. Sed quis malesuada elit, nec eleifend lectus. Sed tincidunt finibus aliquet. Praesent consectetur nibh libero, tempus imperdiet lorem congue eget.", 16, 1); + const wrapped = try DisplayWidth.wrap(allocator, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pellentesque pulvinar felis, sit amet commodo ligula feugiat sed. Sed quis malesuada elit, nec eleifend lectus. Sed tincidunt finibus aliquet. Praesent consectetur nibh libero, tempus imperdiet lorem congue eget.", 16, 1); defer allocator.free(wrapped); const expected_wrap = "Lorem ipsum dolor \nsit amet, consectetur \nadipiscing elit. \nNullam pellentesque \npulvinar felis, \nsit amet commodo \nligula feugiat \nsed. Sed quis malesuada \nelit, nec eleifend \nlectus. Sed tincidunt \nfinibus aliquet. \nPraesent consectetur \nnibh libero, tempus \nimperdiet lorem \ncongue eget."; try std.testing.expectEqualStrings(expected_wrap, wrapped); } -fn testAllocation(allocator: Allocator) !void { - { - var dw = try DisplayWidth.init(allocator); - dw.deinit(allocator); - } - { - var graph = try Graphemes.init(allocator); - defer graph.deinit(allocator); - var dw = try DisplayWidth.initWithGraphemes(allocator, graph); - dw.deinit(allocator); - } -} - -test "allocation test" { - try testing.checkAllAllocationFailures(testing.allocator, testAllocation, .{}); -} - const std = @import("std"); const builtin = @import("builtin"); const options = @import("options"); -- cgit v1.2.3