summaryrefslogtreecommitdiff
path: root/src/File.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/File.zig')
-rw-r--r--src/File.zig31
1 files changed, 30 insertions, 1 deletions
diff --git a/src/File.zig b/src/File.zig
index 1d838c4f..99fccea8 100644
--- a/src/File.zig
+++ b/src/File.zig
@@ -266,7 +266,36 @@ test "stacked location metadata requires matching adjacent preview ownership" {
pub fn graphemeDisplayWidth(grapheme: []const u8) usize {
if (std.mem.eql(u8, grapheme, "\t")) return config.tab_width;
if (grapheme.len == 1 and grapheme[0] >= 0x20 and grapheme[0] < 0x7f) return 1;
- return @max(1, @as(usize, vaxis.gwidth.gwidth(grapheme, .unicode)));
+ if (std.unicode.utf8ValidateSlice(grapheme)) return @max(1, @as(usize, vaxis.gwidth.gwidth(grapheme, .unicode)));
+ // vaxis's gwidth counts back a replacement rune's 3 bytes over the one
+ // invalid byte it stood for (`\xee` before `e\u{301}` overflowed it):
+ // each invalid byte is a cell, as surface.zig draws it, and each valid
+ // run between is measured alone.
+ var width: usize = 0;
+ var run: usize = 0;
+ var i: usize = 0;
+ while (i < grapheme.len) {
+ const n = std.unicode.utf8ByteSequenceLength(grapheme[i]) catch 0;
+ if (n > 0 and i + n <= grapheme.len and std.unicode.utf8ValidateSlice(grapheme[i .. i + n])) {
+ i += n;
+ continue;
+ }
+ if (run < i) width += vaxis.gwidth.gwidth(grapheme[run..i], .unicode);
+ width += 1;
+ i += 1;
+ run = i;
+ }
+ if (run < grapheme.len) width += vaxis.gwidth.gwidth(grapheme[run..], .unicode);
+ return @max(1, width);
+}
+
+test "an invalid byte in a grapheme is one cell, and never reaches vaxis's width" {
+ try std.testing.expectEqual(@as(usize, 2), graphemeDisplayWidth("\xeee\xcc\x81"));
+ try std.testing.expectEqual(@as(usize, 3), graphemeDisplayWidth("\xee\xffe"));
+ try std.testing.expectEqual(@as(usize, 3), graphemeDisplayWidth("\u{4e16}\x80"));
+ // The whole path the 9P fuzzer took: a body holding it, fitted to a width.
+ const text = "\xeee\xcc\x81";
+ try std.testing.expectEqual(@as(usize, 4), fitEnd(text, 0, 10));
}
pub fn byteDisplayWidth(byte: u8) usize {