vaxis: add mode 2027 query parsing, wcwidth measurement

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
Tim Culverhouse 2024-01-23 21:06:02 -06:00
parent ef7b30dbee
commit a2dc97c039
5 changed files with 109 additions and 7 deletions

View file

@ -281,20 +281,19 @@ pub fn parse(self: *Parser, input: []const u8) !Result {
'u' => blk: { 'u' => blk: {
if (seq.private_indicator) |priv| { if (seq.private_indicator) |priv| {
// response to our kitty query // response to our kitty query
// TODO: kitty query handling
if (priv == '?') { if (priv == '?') {
return .{ return .{
.event = .cap_kitty_keyboard, .event = .cap_kitty_keyboard,
.n = i + 1, .n = i + 1,
}; };
} } else {
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]}); log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
return .{ return .{
.event = null, .event = null,
.n = i + 1, .n = i + 1,
}; };
} }
}
if (seq.param_idx == 0) { if (seq.param_idx == 0) {
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]}); log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
return .{ return .{
@ -313,6 +312,51 @@ pub fn parse(self: *Parser, input: []const u8) !Result {
'O' => { // focus out 'O' => { // focus out
return .{ .event = .focus_out, .n = i + 1 }; return .{ .event = .focus_out, .n = i + 1 };
}, },
'y' => { // DECRQM response
const priv = seq.private_indicator orelse {
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
return .{ .event = null, .n = i + 1 };
};
if (priv != '?') {
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
return .{ .event = null, .n = i + 1 };
}
const intm = seq.intermediate orelse {
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
return .{ .event = null, .n = i + 1 };
};
if (intm != '$') {
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
return .{ .event = null, .n = i + 1 };
}
if (seq.param_idx != 2) {
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
return .{ .event = null, .n = i + 1 };
}
// We'll get two fields, the first is the mode
// we requested, the second is the status of the
// mode
// 0: not recognize
// 1: set
// 2: reset
// 3: permanently set
// 4: permanently reset
switch (seq.params[0]) {
2027 => {
switch (seq.params[1]) {
0, 4 => return .{ .event = null, .n = i + 1 },
else => return .{ .event = .cap_unicode, .n = i + 1 },
}
},
2031 => {},
else => {
log.warn("unhandled DECRPM: CSI {s}", .{input[start + 1 .. i + 1]});
return .{ .event = null, .n = i + 1 };
},
}
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
return .{ .event = null, .n = i + 1 };
},
else => { else => {
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]}); log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
return .{ return .{

View file

@ -187,6 +187,11 @@ pub fn run(
vx.postEvent(.cap_rgb); vx.postEvent(.cap_rgb);
} }
}, },
.cap_unicode => {
if (@hasField(EventType, "cap_unicode")) {
vx.postEvent(.cap_unicode);
}
},
} }
} }
} }

View file

@ -12,4 +12,5 @@ pub const Event = union(enum) {
// these are delivered as discovered terminal capabilities // these are delivered as discovered terminal capabilities
cap_kitty_keyboard, cap_kitty_keyboard,
cap_rgb, cap_rgb,
cap_unicode,
}; };

51
src/gwidth.zig Normal file
View file

@ -0,0 +1,51 @@
const std = @import("std");
const unicode = std.unicode;
const testing = std.testing;
const ziglyph = @import("ziglyph");
/// the method to use when calculating the width of a grapheme
pub const Method = enum {
unicode,
wcwidth,
};
/// returns the width of the provided string, as measured by the method chosen
pub fn gwidth(str: []const u8, method: Method) !usize {
switch (method) {
.unicode => {
return try ziglyph.display_width.strWidth(str, .half);
},
.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);
if (w < 0) continue;
total += @intCast(w);
}
return total;
},
}
}
test "gwidth: a" {
try testing.expectEqual(1, try gwidth("a", .unicode));
try testing.expectEqual(1, try gwidth("a", .wcwidth));
}
test "gwidth: emoji with ZWJ" {
try testing.expectEqual(2, try gwidth("👩‍🚀", .unicode));
try testing.expectEqual(4, try gwidth("👩‍🚀", .wcwidth));
}
test "gwidth: emoji with skin tone selector" {
try testing.expectEqual(2, try gwidth("👋🏿", .unicode));
try testing.expectEqual(4, try gwidth("👋🏿", .wcwidth));
}
test "gwidth: invalid string" {
try testing.expectError(error.InvalidUtf8, gwidth("\xc3\x28", .unicode));
try testing.expectError(error.InvalidUtf8, gwidth("\xc3\x28", .wcwidth));
}

View file

@ -27,6 +27,7 @@ test {
_ = @import("cell.zig"); _ = @import("cell.zig");
_ = @import("ctlseqs.zig"); _ = @import("ctlseqs.zig");
_ = @import("event.zig"); _ = @import("event.zig");
_ = @import("gwidth.zig");
_ = @import("queue.zig"); _ = @import("queue.zig");
_ = @import("vaxis.zig"); _ = @import("vaxis.zig");
} }