input: w3c names for keys

This commit is contained in:
Mitchell Hashimoto
2025-05-08 14:14:09 -07:00
parent 1e76222f19
commit cc748305fb
2 changed files with 102 additions and 0 deletions

View File

@@ -1174,6 +1174,12 @@ pub const Trigger = struct {
continue :loop;
}
// Look for a matching w3c name next.
if (key.Key.fromW3C(part)) |w3c_key| {
result.key = .{ .physical = w3c_key };
continue :loop;
}
// If we're still unset then we look for backwards compatible
// keys with Ghostty 1.1.x. We do this last so its least likely
// to impact performance for modern users.
@@ -1938,6 +1944,22 @@ test "parse: triggers" {
try testing.expectError(Error.InvalidFormat, parseSingle("a+b=ignore"));
}
test "parse: w3c key names" {
const testing = std.testing;
// Exact match
try testing.expectEqual(
Binding{
.trigger = .{ .key = .{ .physical = .key_a } },
.action = .{ .ignore = {} },
},
try parseSingle("KeyA=ignore"),
);
// Case-sensitive
try testing.expectError(Error.InvalidFormat, parseSingle("Keya=ignore"));
}
// For Ghostty 1.2+ we changed our key names to match the W3C and removed
// `physical:`. This tests the backwards compatibility with the old format.
// Note that our backwards compatibility isn't 100% perfect since triggers

View File

@@ -497,6 +497,74 @@ pub const Key = enum(c_int) {
};
}
/// Converts a W3C key code to a Ghostty key enum value.
///
/// All required W3C key codes are supported, but there are a number of
/// non-standard key codes that are not supported. In the case the value is
/// invalid or unsupported, this function will return null.
pub fn fromW3C(code: []const u8) ?Key {
var result: [128]u8 = undefined;
// If the code is bigger than our buffer it can't possibly match.
if (code.len > result.len) return null;
// First just check the whole thing lowercased, this is the simple case
if (std.meta.stringToEnum(
Key,
std.ascii.lowerString(&result, code),
)) |key| return key;
// We need to convert FooBar to foo_bar
var fbs = std.io.fixedBufferStream(&result);
const w = fbs.writer();
for (code, 0..) |ch, i| switch (ch) {
'a'...'z' => w.writeByte(ch) catch return null,
// Caps and numbers trigger underscores
'A'...'Z', '0'...'9' => {
if (i > 0) w.writeByte('_') catch return null;
w.writeByte(std.ascii.toLower(ch)) catch return null;
},
// We don't know of any key codes that aren't alphanumeric.
else => return null,
};
return std.meta.stringToEnum(Key, fbs.getWritten());
}
/// Converts a Ghostty key enum value to a W3C key code.
pub fn w3c(self: Key) []const u8 {
return switch (self) {
inline else => |tag| comptime w3c: {
@setEvalBranchQuota(50_000);
const name = @tagName(tag);
var buf: [128]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
const w = fbs.writer();
var i: usize = 0;
while (i < name.len) {
if (i == 0) {
w.writeByte(std.ascii.toUpper(name[i])) catch unreachable;
} else if (name[i] == '_') {
i += 1;
w.writeByte(std.ascii.toUpper(name[i])) catch unreachable;
} else {
w.writeByte(name[i]) catch unreachable;
}
i += 1;
}
const written = buf;
const result = written[0..fbs.getWritten().len];
break :w3c result;
},
};
}
/// True if this key represents a printable character.
pub fn printable(self: Key) bool {
return switch (self) {
@@ -781,6 +849,18 @@ pub const Key = enum(c_int) {
try testing.expect(!Key.digit_1.keypad());
}
test "w3c" {
// All our keys should convert to and from the W3C format.
// We don't support every key in the W3C spec, so we only
// check the enum fields.
const testing = std.testing;
inline for (@typeInfo(Key).@"enum".fields) |field| {
const key = @field(Key, field.name);
const w3c_name = key.w3c();
try testing.expectEqual(key, Key.fromW3C(w3c_name).?);
}
}
const codepoint_map: []const struct { u21, Key } = &.{
.{ 'a', .key_a },
.{ 'b', .key_b },