From 6493da0dd396c97c807bf64b4b75be308a365ce0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 17 Aug 2023 09:48:15 -0700 Subject: [PATCH] input: Kitty encodes alternate keys --- src/input/KeyEncoder.zig | 67 ++++++++++++++++++++++++++++++++++++++-- src/input/kitty.zig | 4 +-- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/input/KeyEncoder.zig b/src/input/KeyEncoder.zig index 4fa760314..10ccfeb41 100644 --- a/src/input/KeyEncoder.zig +++ b/src/input/KeyEncoder.zig @@ -51,10 +51,22 @@ fn kitty( // Find the entry for this key in the kitty table. const entry_: ?KittyEntry = entry: { + // Functional or predefined keys for (kitty_entries) |entry| { if (entry.key == self.event.key) break :entry entry; } + // Otherwise, we use our unicode codepoint from UTF8. We + // always use the unshifted value. + if (self.event.unshifted_codepoint > 0) { + break :entry .{ + .key = self.event.key, + .code = self.event.unshifted_codepoint, + .final = 'u', + .modifier = false, + }; + } + break :entry null; }; @@ -119,6 +131,16 @@ fn kitty( }; } + if (self.kitty_flags.report_alternates) alternates: { + const view = try std.unicode.Utf8View.init(self.event.utf8); + var it = view.iterator(); + const cp = it.nextCodepoint() orelse break :alternates; + if (it.nextCodepoint() != null) break :alternates; + if (cp != seq.key) { + seq.alternates = &.{cp}; + } + } + if (self.kitty_flags.report_associated) { seq.text = self.event.utf8; } @@ -521,11 +543,11 @@ const KittyMods = packed struct(u8) { /// /// CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u const KittySequence = struct { - key: u16, + key: u21, final: u8, mods: KittyMods = .{}, event: Event = .none, - alternates: []const u16 = &.{}, + alternates: []const u21 = &.{}, text: []const u8 = "", /// Values for the event code (see "event-type" in above comment). @@ -777,6 +799,47 @@ test "kitty: composing with modifier" { try testing.expectEqualStrings("\x1b[57441;2u", actual); } +test "kitty: shift+a on US keyboard" { + var buf: [128]u8 = undefined; + var enc: KeyEncoder = .{ + .event = .{ + .key = .a, + .mods = .{ .shift = true }, + .utf8 = "A", + .unshifted_codepoint = 97, // lowercase A + }, + .kitty_flags = .{ + .disambiguate = true, + .report_alternates = true, + }, + }; + + const actual = try enc.kitty(&buf); + try testing.expectEqualStrings("\x1b[97:65;2u", actual); +} + +test "kitty: matching unshifted codepoint" { + var buf: [128]u8 = undefined; + var enc: KeyEncoder = .{ + .event = .{ + .key = .a, + .mods = .{ .shift = true }, + .utf8 = "A", + .unshifted_codepoint = 65, + }, + .kitty_flags = .{ + .disambiguate = true, + .report_alternates = true, + }, + }; + + // WARNING: This is not a valid encoding. This is a hypothetical encoding + // just to test that our logic is correct around matching unshifted + // codepoints. + const actual = try enc.kitty(&buf); + try testing.expectEqualStrings("\x1b[65;2u", actual); +} + test "legacy: ctrl+alt+c" { var buf: [128]u8 = undefined; var enc: KeyEncoder = .{ diff --git a/src/input/kitty.zig b/src/input/kitty.zig index e0120ab32..6668b7fe4 100644 --- a/src/input/kitty.zig +++ b/src/input/kitty.zig @@ -6,7 +6,7 @@ const key = @import("key.zig"); /// for a given key. pub const Entry = struct { key: key.Key, - code: u16, + code: u21, final: u8, modifier: bool, }; @@ -28,7 +28,7 @@ pub const entries: []const Entry = entries: { /// Raw entry is the tuple form of an entry for easy human management. /// This should never be used in a real program so it is not pub. For /// real programs, use `entries` which has properly typed, structured data. -const RawEntry = struct { key.Key, u16, u8, bool }; +const RawEntry = struct { key.Key, u21, u8, bool }; /// The raw data for how to map keys to Kitty data. Based on the information: /// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#functional-key-definitions