diff options
| author | 2024-02-26 12:24:42 -0400 | |
|---|---|---|
| committer | 2024-02-26 12:24:42 -0400 | |
| commit | 836a4b6e63ac4bd7beb406cb20edf23f0bd342a9 (patch) | |
| tree | 5f806a29594a9cb227aaa4d131209e10ff25aeee /src/display_width.zig | |
| parent | Replaced ccc_map with table. 20ms faster (diff) | |
| download | zg-836a4b6e63ac4bd7beb406cb20edf23f0bd342a9.tar.gz zg-836a4b6e63ac4bd7beb406cb20edf23f0bd342a9.tar.xz zg-836a4b6e63ac4bd7beb406cb20edf23f0bd342a9.zip | |
Using separate data struct model.
Diffstat (limited to '')
| -rw-r--r-- | src/DisplayWidth.zig (renamed from src/display_width.zig) | 205 |
1 files changed, 98 insertions, 107 deletions
diff --git a/src/display_width.zig b/src/DisplayWidth.zig index a916cac..85d04a0 100644 --- a/src/display_width.zig +++ b/src/DisplayWidth.zig | |||
| @@ -1,68 +1,38 @@ | |||
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const simd = std.simd; | 2 | const builtin = @import("builtin"); |
| 3 | const ArrayList = std.ArrayList; | ||
| 3 | const mem = std.mem; | 4 | const mem = std.mem; |
| 5 | const simd = std.simd; | ||
| 4 | const testing = std.testing; | 6 | const testing = std.testing; |
| 5 | 7 | ||
| 6 | const ascii = @import("ascii"); | 8 | const ascii = @import("ascii"); |
| 7 | const CodePointIterator = @import("code_point").Iterator; | 9 | const CodePointIterator = @import("code_point").Iterator; |
| 8 | const dwp = @import("dwp"); | ||
| 9 | const GraphemeIterator = @import("grapheme").Iterator; | 10 | const GraphemeIterator = @import("grapheme").Iterator; |
| 11 | pub const Data = @import("DisplayWidthData"); | ||
| 10 | 12 | ||
| 11 | /// codePointWidth returns the number of cells `cp` requires when rendered | 13 | data: *Data, |
| 12 | /// in a fixed-pitch font (i.e. a terminal screen). This can range from -1 to | ||
| 13 | /// 3, where BACKSPACE and DELETE return -1 and 3-em-dash returns 3. C0/C1 | ||
| 14 | /// control codes return 0. If `cjk` is true, ambiguous code points return 2, | ||
| 15 | /// otherwise they return 1. | ||
| 16 | pub fn codePointWidth(cp: u21) i3 { | ||
| 17 | return dwp.stage_2[dwp.stage_1[cp >> 8] + (cp & 0xff)]; | ||
| 18 | } | ||
| 19 | 14 | ||
| 20 | test "codePointWidth" { | 15 | const Self = @This(); |
| 21 | try testing.expectEqual(@as(i3, 0), codePointWidth(0x0000)); // null | ||
| 22 | try testing.expectEqual(@as(i3, -1), codePointWidth(0x8)); // \b | ||
| 23 | try testing.expectEqual(@as(i3, -1), codePointWidth(0x7f)); // DEL | ||
| 24 | try testing.expectEqual(@as(i3, 0), codePointWidth(0x0005)); // Cf | ||
| 25 | try testing.expectEqual(@as(i3, 0), codePointWidth(0x0007)); // \a BEL | ||
| 26 | try testing.expectEqual(@as(i3, 0), codePointWidth(0x000A)); // \n LF | ||
| 27 | try testing.expectEqual(@as(i3, 0), codePointWidth(0x000B)); // \v VT | ||
| 28 | try testing.expectEqual(@as(i3, 0), codePointWidth(0x000C)); // \f FF | ||
| 29 | try testing.expectEqual(@as(i3, 0), codePointWidth(0x000D)); // \r CR | ||
| 30 | try testing.expectEqual(@as(i3, 0), codePointWidth(0x000E)); // SQ | ||
| 31 | try testing.expectEqual(@as(i3, 0), codePointWidth(0x000F)); // SI | ||
| 32 | |||
| 33 | try testing.expectEqual(@as(i3, 0), codePointWidth(0x070F)); // Cf | ||
| 34 | try testing.expectEqual(@as(i3, 1), codePointWidth(0x0603)); // Cf Arabic | ||
| 35 | |||
| 36 | try testing.expectEqual(@as(i3, 1), codePointWidth(0x00AD)); // soft-hyphen | ||
| 37 | try testing.expectEqual(@as(i3, 2), codePointWidth(0x2E3A)); // two-em dash | ||
| 38 | try testing.expectEqual(@as(i3, 3), codePointWidth(0x2E3B)); // three-em dash | ||
| 39 | |||
| 40 | try testing.expectEqual(@as(i3, 1), codePointWidth(0x00BD)); // ambiguous halfwidth | ||
| 41 | |||
| 42 | try testing.expectEqual(@as(i3, 1), codePointWidth('é')); | ||
| 43 | try testing.expectEqual(@as(i3, 2), codePointWidth('😊')); | ||
| 44 | try testing.expectEqual(@as(i3, 2), codePointWidth('统')); | ||
| 45 | } | ||
| 46 | 16 | ||
| 47 | /// strWidth returns the total display width of `str` as the number of cells | 17 | /// strWidth returns the total display width of `str` as the number of cells |
| 48 | /// required in a fixed-pitch font (i.e. a terminal screen). | 18 | /// required in a fixed-pitch font (i.e. a terminal screen). |
| 49 | pub fn strWidth(str: []const u8) usize { | 19 | pub fn strWidth(self: Self, str: []const u8) usize { |
| 50 | var total: isize = 0; | 20 | var total: isize = 0; |
| 51 | 21 | ||
| 52 | // ASCII fast path | 22 | // ASCII fast path |
| 53 | if (ascii.isAsciiOnly(str)) { | 23 | if (ascii.isAsciiOnly(str)) { |
| 54 | for (str) |b| total += codePointWidth(b); | 24 | for (str) |b| total += self.data.codePointWidth(b); |
| 55 | return @intCast(@max(0, total)); | 25 | return @intCast(@max(0, total)); |
| 56 | } | 26 | } |
| 57 | 27 | ||
| 58 | var giter = GraphemeIterator.init(str); | 28 | var giter = GraphemeIterator.init(str, &self.data.g_data); |
| 59 | 29 | ||
| 60 | while (giter.next()) |gc| { | 30 | while (giter.next()) |gc| { |
| 61 | var cp_iter = CodePointIterator{ .bytes = gc.bytes(str) }; | 31 | var cp_iter = CodePointIterator{ .bytes = gc.bytes(str) }; |
| 62 | var gc_total: isize = 0; | 32 | var gc_total: isize = 0; |
| 63 | 33 | ||
| 64 | while (cp_iter.next()) |cp| { | 34 | while (cp_iter.next()) |cp| { |
| 65 | var w = codePointWidth(cp.code); | 35 | var w = self.data.codePointWidth(cp.code); |
| 66 | 36 | ||
| 67 | if (w != 0) { | 37 | if (w != 0) { |
| 68 | // Handle text emoji sequence. | 38 | // Handle text emoji sequence. |
| @@ -86,31 +56,35 @@ pub fn strWidth(str: []const u8) usize { | |||
| 86 | } | 56 | } |
| 87 | 57 | ||
| 88 | test "strWidth" { | 58 | test "strWidth" { |
| 89 | try testing.expectEqual(@as(usize, 5), strWidth("Hello\r\n")); | 59 | var data = try Data.init(testing.allocator); |
| 90 | try testing.expectEqual(@as(usize, 1), strWidth("\u{0065}\u{0301}")); | 60 | defer data.deinit(); |
| 91 | try testing.expectEqual(@as(usize, 2), strWidth("\u{1F476}\u{1F3FF}\u{0308}\u{200D}\u{1F476}\u{1F3FF}")); | 61 | const self = Self{ .data = &data }; |
| 92 | try testing.expectEqual(@as(usize, 8), strWidth("Hello 😊")); | 62 | |
| 93 | try testing.expectEqual(@as(usize, 8), strWidth("Héllo 😊")); | 63 | try testing.expectEqual(@as(usize, 5), self.strWidth("Hello\r\n")); |
| 94 | try testing.expectEqual(@as(usize, 8), strWidth("Héllo :)")); | 64 | try testing.expectEqual(@as(usize, 1), self.strWidth("\u{0065}\u{0301}")); |
| 95 | try testing.expectEqual(@as(usize, 8), strWidth("Héllo 🇪🇸")); | 65 | try testing.expectEqual(@as(usize, 2), self.strWidth("\u{1F476}\u{1F3FF}\u{0308}\u{200D}\u{1F476}\u{1F3FF}")); |
| 96 | try testing.expectEqual(@as(usize, 2), strWidth("\u{26A1}")); // Lone emoji | 66 | try testing.expectEqual(@as(usize, 8), self.strWidth("Hello 😊")); |
| 97 | try testing.expectEqual(@as(usize, 1), strWidth("\u{26A1}\u{FE0E}")); // Text sequence | 67 | try testing.expectEqual(@as(usize, 8), self.strWidth("Héllo 😊")); |
| 98 | try testing.expectEqual(@as(usize, 2), strWidth("\u{26A1}\u{FE0F}")); // Presentation sequence | 68 | try testing.expectEqual(@as(usize, 8), self.strWidth("Héllo :)")); |
| 99 | try testing.expectEqual(@as(usize, 0), strWidth("A\x08")); // Backspace | 69 | try testing.expectEqual(@as(usize, 8), self.strWidth("Héllo 🇪🇸")); |
| 100 | try testing.expectEqual(@as(usize, 0), strWidth("\x7FA")); // DEL | 70 | try testing.expectEqual(@as(usize, 2), self.strWidth("\u{26A1}")); // Lone emoji |
| 101 | try testing.expectEqual(@as(usize, 0), strWidth("\x7FA\x08\x08")); // never less than o | 71 | try testing.expectEqual(@as(usize, 1), self.strWidth("\u{26A1}\u{FE0E}")); // Text sequence |
| 72 | try testing.expectEqual(@as(usize, 2), self.strWidth("\u{26A1}\u{FE0F}")); // Presentation sequence | ||
| 73 | try testing.expectEqual(@as(usize, 0), self.strWidth("A\x08")); // Backspace | ||
| 74 | try testing.expectEqual(@as(usize, 0), self.strWidth("\x7FA")); // DEL | ||
| 75 | try testing.expectEqual(@as(usize, 0), self.strWidth("\x7FA\x08\x08")); // never less than o | ||
| 102 | 76 | ||
| 103 | // wcwidth Python lib tests. See: https://github.com/jquast/wcwidth/blob/master/tests/test_core.py | 77 | // wcwidth Python lib tests. See: https://github.com/jquast/wcwidth/blob/master/tests/test_core.py |
| 104 | const empty = ""; | 78 | const empty = ""; |
| 105 | try testing.expectEqual(@as(usize, 0), strWidth(empty)); | 79 | try testing.expectEqual(@as(usize, 0), self.strWidth(empty)); |
| 106 | const with_null = "hello\x00world"; | 80 | const with_null = "hello\x00world"; |
| 107 | try testing.expectEqual(@as(usize, 10), strWidth(with_null)); | 81 | try testing.expectEqual(@as(usize, 10), self.strWidth(with_null)); |
| 108 | const hello_jp = "コンニチハ, セカイ!"; | 82 | const hello_jp = "コンニチハ, セカイ!"; |
| 109 | try testing.expectEqual(@as(usize, 19), strWidth(hello_jp)); | 83 | try testing.expectEqual(@as(usize, 19), self.strWidth(hello_jp)); |
| 110 | const control = "\x1b[0m"; | 84 | const control = "\x1b[0m"; |
| 111 | try testing.expectEqual(@as(usize, 3), strWidth(control)); | 85 | try testing.expectEqual(@as(usize, 3), self.strWidth(control)); |
| 112 | const balinese = "\u{1B13}\u{1B28}\u{1B2E}\u{1B44}"; | 86 | const balinese = "\u{1B13}\u{1B28}\u{1B2E}\u{1B44}"; |
| 113 | try testing.expectEqual(@as(usize, 3), strWidth(balinese)); | 87 | try testing.expectEqual(@as(usize, 3), self.strWidth(balinese)); |
| 114 | 88 | ||
| 115 | // These commented out tests require a new specification for complex scripts. | 89 | // These commented out tests require a new specification for complex scripts. |
| 116 | // See: https://www.unicode.org/L2/L2023/23107-terminal-suppt.pdf | 90 | // See: https://www.unicode.org/L2/L2023/23107-terminal-suppt.pdf |
| @@ -124,17 +98,17 @@ test "strWidth" { | |||
| 124 | // try testing.expectEqual(@as(usize, 3), strWidth(kannada_1)); | 98 | // try testing.expectEqual(@as(usize, 3), strWidth(kannada_1)); |
| 125 | // The following passes but as a mere coincidence. | 99 | // The following passes but as a mere coincidence. |
| 126 | const kannada_2 = "\u{0cb0}\u{0cbc}\u{0ccd}\u{0c9a}"; | 100 | const kannada_2 = "\u{0cb0}\u{0cbc}\u{0ccd}\u{0c9a}"; |
| 127 | try testing.expectEqual(@as(usize, 2), strWidth(kannada_2)); | 101 | try testing.expectEqual(@as(usize, 2), self.strWidth(kannada_2)); |
| 128 | 102 | ||
| 129 | // From Rust https://github.com/jameslanska/unicode-display-width | 103 | // From Rust https://github.com/jameslanska/unicode-display-width |
| 130 | try testing.expectEqual(@as(usize, 15), strWidth("🔥🗡🍩👩🏻🚀⏰💃🏼🔦👍🏻")); | 104 | try testing.expectEqual(@as(usize, 15), self.strWidth("🔥🗡🍩👩🏻🚀⏰💃🏼🔦👍🏻")); |
| 131 | try testing.expectEqual(@as(usize, 2), strWidth("🦀")); | 105 | try testing.expectEqual(@as(usize, 2), self.strWidth("🦀")); |
| 132 | try testing.expectEqual(@as(usize, 2), strWidth("👨👩👧👧")); | 106 | try testing.expectEqual(@as(usize, 2), self.strWidth("👨👩👧👧")); |
| 133 | try testing.expectEqual(@as(usize, 2), strWidth("👩🔬")); | 107 | try testing.expectEqual(@as(usize, 2), self.strWidth("👩🔬")); |
| 134 | try testing.expectEqual(@as(usize, 9), strWidth("sane text")); | 108 | try testing.expectEqual(@as(usize, 9), self.strWidth("sane text")); |
| 135 | try testing.expectEqual(@as(usize, 9), strWidth("Ẓ̌á̲l͔̝̞̄̑͌g̖̘̘̔̔͢͞͝o̪̔T̢̙̫̈̍͞e̬͈͕͌̏͑x̺̍ṭ̓̓ͅ")); | 109 | try testing.expectEqual(@as(usize, 9), self.strWidth("Ẓ̌á̲l͔̝̞̄̑͌g̖̘̘̔̔͢͞͝o̪̔T̢̙̫̈̍͞e̬͈͕͌̏͑x̺̍ṭ̓̓ͅ")); |
| 136 | try testing.expectEqual(@as(usize, 17), strWidth("슬라바 우크라이나")); | 110 | try testing.expectEqual(@as(usize, 17), self.strWidth("슬라바 우크라이나")); |
| 137 | try testing.expectEqual(@as(usize, 1), strWidth("\u{378}")); | 111 | try testing.expectEqual(@as(usize, 1), self.strWidth("\u{378}")); |
| 138 | } | 112 | } |
| 139 | 113 | ||
| 140 | /// centers `str` in a new string of width `total_width` (in display cells) using `pad` as padding. | 114 | /// centers `str` in a new string of width `total_width` (in display cells) using `pad` as padding. |
| @@ -142,16 +116,17 @@ test "strWidth" { | |||
| 142 | /// receive one additional pad. This makes sure the returned string fills the requested width. | 116 | /// receive one additional pad. This makes sure the returned string fills the requested width. |
| 143 | /// Caller must free returned bytes with `allocator`. | 117 | /// Caller must free returned bytes with `allocator`. |
| 144 | pub fn center( | 118 | pub fn center( |
| 119 | self: Self, | ||
| 145 | allocator: mem.Allocator, | 120 | allocator: mem.Allocator, |
| 146 | str: []const u8, | 121 | str: []const u8, |
| 147 | total_width: usize, | 122 | total_width: usize, |
| 148 | pad: []const u8, | 123 | pad: []const u8, |
| 149 | ) ![]u8 { | 124 | ) ![]u8 { |
| 150 | const str_width = strWidth(str); | 125 | const str_width = self.strWidth(str); |
| 151 | if (str_width > total_width) return error.StrTooLong; | 126 | if (str_width > total_width) return error.StrTooLong; |
| 152 | if (str_width == total_width) return try allocator.dupe(u8, str); | 127 | if (str_width == total_width) return try allocator.dupe(u8, str); |
| 153 | 128 | ||
| 154 | const pad_width = strWidth(pad); | 129 | const pad_width = self.strWidth(pad); |
| 155 | if (pad_width > total_width or str_width + pad_width > total_width) return error.PadTooLong; | 130 | if (pad_width > total_width or str_width + pad_width > total_width) return error.PadTooLong; |
| 156 | 131 | ||
| 157 | const margin_width = @divFloor((total_width - str_width), 2); | 132 | const margin_width = @divFloor((total_width - str_width), 2); |
| @@ -181,59 +156,63 @@ pub fn center( | |||
| 181 | } | 156 | } |
| 182 | 157 | ||
| 183 | test "center" { | 158 | test "center" { |
| 184 | var allocator = std.testing.allocator; | 159 | const allocator = testing.allocator; |
| 160 | var data = try Data.init(allocator); | ||
| 161 | defer data.deinit(); | ||
| 162 | const self = Self{ .data = &data }; | ||
| 185 | 163 | ||
| 186 | // Input and width both have odd length | 164 | // Input and width both have odd length |
| 187 | var centered = try center(allocator, "abc", 9, "*"); | 165 | var centered = try self.center(allocator, "abc", 9, "*"); |
| 188 | try testing.expectEqualSlices(u8, "***abc***", centered); | 166 | try testing.expectEqualSlices(u8, "***abc***", centered); |
| 189 | 167 | ||
| 190 | // Input and width both have even length | 168 | // Input and width both have even length |
| 191 | allocator.free(centered); | 169 | testing.allocator.free(centered); |
| 192 | centered = try center(allocator, "w😊w", 10, "-"); | 170 | centered = try self.center(allocator, "w😊w", 10, "-"); |
| 193 | try testing.expectEqualSlices(u8, "---w😊w---", centered); | 171 | try testing.expectEqualSlices(u8, "---w😊w---", centered); |
| 194 | 172 | ||
| 195 | // Input has even length, width has odd length | 173 | // Input has even length, width has odd length |
| 196 | allocator.free(centered); | 174 | testing.allocator.free(centered); |
| 197 | centered = try center(allocator, "1234", 9, "-"); | 175 | centered = try self.center(allocator, "1234", 9, "-"); |
| 198 | try testing.expectEqualSlices(u8, "--1234---", centered); | 176 | try testing.expectEqualSlices(u8, "--1234---", centered); |
| 199 | 177 | ||
| 200 | // Input has odd length, width has even length | 178 | // Input has odd length, width has even length |
| 201 | allocator.free(centered); | 179 | testing.allocator.free(centered); |
| 202 | centered = try center(allocator, "123", 8, "-"); | 180 | centered = try self.center(allocator, "123", 8, "-"); |
| 203 | try testing.expectEqualSlices(u8, "--123---", centered); | 181 | try testing.expectEqualSlices(u8, "--123---", centered); |
| 204 | 182 | ||
| 205 | // Input is the same length as the width | 183 | // Input is the same length as the width |
| 206 | allocator.free(centered); | 184 | testing.allocator.free(centered); |
| 207 | centered = try center(allocator, "123", 3, "-"); | 185 | centered = try self.center(allocator, "123", 3, "-"); |
| 208 | try testing.expectEqualSlices(u8, "123", centered); | 186 | try testing.expectEqualSlices(u8, "123", centered); |
| 209 | 187 | ||
| 210 | // Input is empty | 188 | // Input is empty |
| 211 | allocator.free(centered); | 189 | testing.allocator.free(centered); |
| 212 | centered = try center(allocator, "", 3, "-"); | 190 | centered = try self.center(allocator, "", 3, "-"); |
| 213 | try testing.expectEqualSlices(u8, "---", centered); | 191 | try testing.expectEqualSlices(u8, "---", centered); |
| 214 | 192 | ||
| 215 | // Input is empty and width is zero | 193 | // Input is empty and width is zero |
| 216 | allocator.free(centered); | 194 | testing.allocator.free(centered); |
| 217 | centered = try center(allocator, "", 0, "-"); | 195 | centered = try self.center(allocator, "", 0, "-"); |
| 218 | try testing.expectEqualSlices(u8, "", centered); | 196 | try testing.expectEqualSlices(u8, "", centered); |
| 219 | 197 | ||
| 220 | // Input is longer than the width, which is an error | 198 | // Input is longer than the width, which is an error |
| 221 | allocator.free(centered); | 199 | testing.allocator.free(centered); |
| 222 | try testing.expectError(error.StrTooLong, center(allocator, "123", 2, "-")); | 200 | try testing.expectError(error.StrTooLong, self.center(allocator, "123", 2, "-")); |
| 223 | } | 201 | } |
| 224 | 202 | ||
| 225 | /// padLeft returns a new string of width `total_width` (in display cells) using `pad` as padding | 203 | /// padLeft returns a new string of width `total_width` (in display cells) using `pad` as padding |
| 226 | /// on the left side. Caller must free returned bytes with `allocator`. | 204 | /// on the left side. Caller must free returned bytes with `allocator`. |
| 227 | pub fn padLeft( | 205 | pub fn padLeft( |
| 228 | allocator: std.mem.Allocator, | 206 | self: Self, |
| 207 | allocator: mem.Allocator, | ||
| 229 | str: []const u8, | 208 | str: []const u8, |
| 230 | total_width: usize, | 209 | total_width: usize, |
| 231 | pad: []const u8, | 210 | pad: []const u8, |
| 232 | ) ![]u8 { | 211 | ) ![]u8 { |
| 233 | const str_width = strWidth(str); | 212 | const str_width = self.strWidth(str); |
| 234 | if (str_width > total_width) return error.StrTooLong; | 213 | if (str_width > total_width) return error.StrTooLong; |
| 235 | 214 | ||
| 236 | const pad_width = strWidth(pad); | 215 | const pad_width = self.strWidth(pad); |
| 237 | if (pad_width > total_width or str_width + pad_width > total_width) return error.PadTooLong; | 216 | if (pad_width > total_width or str_width + pad_width > total_width) return error.PadTooLong; |
| 238 | 217 | ||
| 239 | const margin_width = total_width - str_width; | 218 | const margin_width = total_width - str_width; |
| @@ -256,29 +235,33 @@ pub fn padLeft( | |||
| 256 | } | 235 | } |
| 257 | 236 | ||
| 258 | test "padLeft" { | 237 | test "padLeft" { |
| 259 | var allocator = std.testing.allocator; | 238 | const allocator = testing.allocator; |
| 239 | var data = try Data.init(allocator); | ||
| 240 | defer data.deinit(); | ||
| 241 | const self = Self{ .data = &data }; | ||
| 260 | 242 | ||
| 261 | var right_aligned = try padLeft(allocator, "abc", 9, "*"); | 243 | var right_aligned = try self.padLeft(allocator, "abc", 9, "*"); |
| 262 | defer allocator.free(right_aligned); | 244 | defer testing.allocator.free(right_aligned); |
| 263 | try testing.expectEqualSlices(u8, "******abc", right_aligned); | 245 | try testing.expectEqualSlices(u8, "******abc", right_aligned); |
| 264 | 246 | ||
| 265 | allocator.free(right_aligned); | 247 | testing.allocator.free(right_aligned); |
| 266 | right_aligned = try padLeft(allocator, "w😊w", 10, "-"); | 248 | right_aligned = try self.padLeft(allocator, "w😊w", 10, "-"); |
| 267 | try testing.expectEqualSlices(u8, "------w😊w", right_aligned); | 249 | try testing.expectEqualSlices(u8, "------w😊w", right_aligned); |
| 268 | } | 250 | } |
| 269 | 251 | ||
| 270 | /// padRight returns a new string of width `total_width` (in display cells) using `pad` as padding | 252 | /// padRight returns a new string of width `total_width` (in display cells) using `pad` as padding |
| 271 | /// on the right side. Caller must free returned bytes with `allocator`. | 253 | /// on the right side. Caller must free returned bytes with `allocator`. |
| 272 | pub fn padRight( | 254 | pub fn padRight( |
| 273 | allocator: std.mem.Allocator, | 255 | self: Self, |
| 256 | allocator: mem.Allocator, | ||
| 274 | str: []const u8, | 257 | str: []const u8, |
| 275 | total_width: usize, | 258 | total_width: usize, |
| 276 | pad: []const u8, | 259 | pad: []const u8, |
| 277 | ) ![]u8 { | 260 | ) ![]u8 { |
| 278 | const str_width = strWidth(str); | 261 | const str_width = self.strWidth(str); |
| 279 | if (str_width > total_width) return error.StrTooLong; | 262 | if (str_width > total_width) return error.StrTooLong; |
| 280 | 263 | ||
| 281 | const pad_width = strWidth(pad); | 264 | const pad_width = self.strWidth(pad); |
| 282 | if (pad_width > total_width or str_width + pad_width > total_width) return error.PadTooLong; | 265 | if (pad_width > total_width or str_width + pad_width > total_width) return error.PadTooLong; |
| 283 | 266 | ||
| 284 | const margin_width = total_width - str_width; | 267 | const margin_width = total_width - str_width; |
| @@ -302,14 +285,17 @@ pub fn padRight( | |||
| 302 | } | 285 | } |
| 303 | 286 | ||
| 304 | test "padRight" { | 287 | test "padRight" { |
| 305 | var allocator = std.testing.allocator; | 288 | const allocator = testing.allocator; |
| 289 | var data = try Data.init(allocator); | ||
| 290 | defer data.deinit(); | ||
| 291 | const self = Self{ .data = &data }; | ||
| 306 | 292 | ||
| 307 | var left_aligned = try padRight(allocator, "abc", 9, "*"); | 293 | var left_aligned = try self.padRight(allocator, "abc", 9, "*"); |
| 308 | defer allocator.free(left_aligned); | 294 | defer testing.allocator.free(left_aligned); |
| 309 | try testing.expectEqualSlices(u8, "abc******", left_aligned); | 295 | try testing.expectEqualSlices(u8, "abc******", left_aligned); |
| 310 | 296 | ||
| 311 | allocator.free(left_aligned); | 297 | testing.allocator.free(left_aligned); |
| 312 | left_aligned = try padRight(allocator, "w😊w", 10, "-"); | 298 | left_aligned = try self.padRight(allocator, "w😊w", 10, "-"); |
| 313 | try testing.expectEqualSlices(u8, "w😊w------", left_aligned); | 299 | try testing.expectEqualSlices(u8, "w😊w------", left_aligned); |
| 314 | } | 300 | } |
| 315 | 301 | ||
| @@ -317,12 +303,13 @@ test "padRight" { | |||
| 317 | /// `threshold` defines how far the last column of the last word can be | 303 | /// `threshold` defines how far the last column of the last word can be |
| 318 | /// from the edge. Caller must free returned bytes with `allocator`. | 304 | /// from the edge. Caller must free returned bytes with `allocator`. |
| 319 | pub fn wrap( | 305 | pub fn wrap( |
| 320 | allocator: std.mem.Allocator, | 306 | self: Self, |
| 307 | allocator: mem.Allocator, | ||
| 321 | str: []const u8, | 308 | str: []const u8, |
| 322 | columns: usize, | 309 | columns: usize, |
| 323 | threshold: usize, | 310 | threshold: usize, |
| 324 | ) ![]u8 { | 311 | ) ![]u8 { |
| 325 | var result = std.ArrayList(u8).init(allocator); | 312 | var result = ArrayList(u8).init(allocator); |
| 326 | defer result.deinit(); | 313 | defer result.deinit(); |
| 327 | 314 | ||
| 328 | var line_iter = mem.tokenizeAny(u8, str, "\r\n"); | 315 | var line_iter = mem.tokenizeAny(u8, str, "\r\n"); |
| @@ -334,7 +321,7 @@ pub fn wrap( | |||
| 334 | while (word_iter.next()) |word| { | 321 | while (word_iter.next()) |word| { |
| 335 | try result.appendSlice(word); | 322 | try result.appendSlice(word); |
| 336 | try result.append(' '); | 323 | try result.append(' '); |
| 337 | line_width += strWidth(word) + 1; | 324 | line_width += self.strWidth(word) + 1; |
| 338 | 325 | ||
| 339 | if (line_width > columns or columns - line_width <= threshold) { | 326 | if (line_width > columns or columns - line_width <= threshold) { |
| 340 | try result.append('\n'); | 327 | try result.append('\n'); |
| @@ -351,10 +338,14 @@ pub fn wrap( | |||
| 351 | } | 338 | } |
| 352 | 339 | ||
| 353 | test "wrap" { | 340 | test "wrap" { |
| 354 | var allocator = std.testing.allocator; | 341 | const allocator = testing.allocator; |
| 342 | var data = try Data.init(allocator); | ||
| 343 | defer data.deinit(); | ||
| 344 | const self = Self{ .data = &data }; | ||
| 345 | |||
| 355 | const input = "The quick brown fox\r\njumped over the lazy dog!"; | 346 | const input = "The quick brown fox\r\njumped over the lazy dog!"; |
| 356 | const got = try wrap(allocator, input, 10, 3); | 347 | const got = try self.wrap(allocator, input, 10, 3); |
| 357 | defer allocator.free(got); | 348 | defer testing.allocator.free(got); |
| 358 | const want = "The quick \nbrown fox \njumped \nover the \nlazy dog!"; | 349 | const want = "The quick \nbrown fox \njumped \nover the \nlazy dog!"; |
| 359 | try testing.expectEqualStrings(want, got); | 350 | try testing.expectEqualStrings(want, got); |
| 360 | } | 351 | } |