From 1170cee5505e014a93421e91f9d98956105757a5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 17 Feb 2024 13:45:49 -0800 Subject: [PATCH] input: encode ctrl+shift+ in CSIu as lowercase + shift This follows a specific behavior Kitty has, but no other terminal seems to have, but it is a reasonable behavior that is in use by real programs as found by our beta testing community. We diverge from Kitty though in that we only apply this to ASCII letters. Kitty does not CSIu encode special characters like `@` or `$`. For these characters, Ghostty still encodes it as specified by fixterms. --- src/input/KeyEncoder.zig | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/input/KeyEncoder.zig b/src/input/KeyEncoder.zig index 83833bc4c..a14ba14a9 100644 --- a/src/input/KeyEncoder.zig +++ b/src/input/KeyEncoder.zig @@ -341,23 +341,36 @@ fn legacy( // effective mods. The fixterms spec states the shifted chars // should be sent uppercase but Kitty changes that behavior // so we'll send all the mods. - const csi_u_mods = mods: { + const csi_u_mods, const char = mods: { + var char: u21 = @intCast(utf8[0]); var mods = CsiUMods.fromInput(self.event.mods); + // If our character is A to Z and we have shift set, then + // we lowercase it. This is a Kitty-specific behavior that + // we choose to follow and diverge from the fixterms spec. + // This makes it easier for programs to detect shifted letters + // for keybindings and is not just theoretical but used by + // real programs. + if (char >= 'A' and char <= 'Z' and mods.shift) { + // We want to rely on apprt to send us the correct + // unshifted codepoint... + char = @intCast(std.ascii.toLower(@intCast(char))); + } + // If our unshifted codepoint is identical to the shifted // then we consider shift. Otherwise, we do not because the // shift key was used to obtain the character. This is specified // by fixterms. - if (self.event.unshifted_codepoint != @as(u21, @intCast(utf8[0]))) { + if (self.event.unshifted_codepoint != char) { mods.shift = false; } - break :mods mods; + break :mods .{ mods, char }; }; const result = try std.fmt.bufPrint( buf, "\x1B[{};{}u", - .{ utf8[0], csi_u_mods.seqInt() }, + .{ char, csi_u_mods.seqInt() }, ); // std.log.warn("CSI_U: {s}", .{result}); return result; @@ -1750,6 +1763,22 @@ test "legacy: fixterm awkward letters" { } } +// These tests apply Kitty's behavior to CSIu where ctrl+shift+letter +// is sent as the unshifted letter with the shift modifier present. +test "legacy: ctrl+shift+letter ascii" { + var buf: [128]u8 = undefined; + { + var enc: KeyEncoder = .{ .event = .{ + .key = .m, + .mods = .{ .ctrl = true, .shift = true }, + .utf8 = "M", + .unshifted_codepoint = 'm', + } }; + const actual = try enc.legacy(&buf); + try testing.expectEqualStrings("\x1b[109;6u", actual); + } +} + test "legacy: shift+function key should use all mods" { var buf: [128]u8 = undefined; var enc: KeyEncoder = .{