input: send shifted uppercase ASCII letters as CSIu

Related to #1507

The comments in this change have most of the detail. The primary gist:

  - caps-lock is handled correctly so #1501 is still fixed
  - only characters pressed with the shift key are affected
This commit is contained in:
Mitchell Hashimoto
2024-02-17 08:39:27 -08:00
parent 7cc9ea02c4
commit 4a6ba59f73

View File

@ -519,22 +519,40 @@ fn ctrlSeq(
// logic separately.
unset_mods.alt = false;
// Remove shift because shifted characters are allowed to send
// control sequences. i.e. ctrl+A == ctrl+a
if (unset_mods.shift) shift: {
// Remove shift if we have something outside of the US letter
// range. This is so that characters such as `ctrl+shift+-`
// generate the correct ctrl-seq (used by emacs).
if (unset_mods.shift and (char < 'A' or char > 'Z')) shift: {
// Special case for fixterms awkward case as specified.
if (char == '@') break :shift;
unset_mods.shift = false;
}
// If the character is uppercase, we convert it to lowercase. We
// rely on the unshifted codepoint to do this.
// rely on the unshifted codepoint to do this. This handles
// the scenario where we have caps lock pressed. Note that
// shifted characters are handled above, if we are just pressing
// shift then the ctrl-only check will fail later and we won't
// ctrl-seq encode.
if (char >= 'A' and char <= 'Z' and unshifted_codepoint > 0) {
if (std.math.cast(u8, unshifted_codepoint)) |byte| {
char = byte;
}
}
// An additional note on caps lock and shift interaction.
// If we have caps lock set and an ASCII letter is pressed,
// we lowercase it (above). If we have only control pressed,
// we process it as a ctrl seq. For example ctrl+M with caps
// lock but no shift will encode as 0x0D.
//
// But, if you press ctrl+shift+m, this will not encode as a
// ctrl-seq and falls through to CSIu encoding. This lets programs
// detect the difference between ctrl+M and ctrl+shift+M. This
// diverges from the fixterms "spec" and most terminals. This
// only matches Kitty in behavior. But I believe this is a
// justified divergence because it's a useful distinction.
break :unset_mods .{ char, unset_mods.binding() };
};
@ -1931,7 +1949,7 @@ test "ctrlseq: caps ascii letter" {
try testing.expectEqual(@as(u8, 0x03), seq.?);
}
test "ctrlseq: shifted ascii letter" {
const seq = ctrlSeq("C", 'c', .{ .ctrl = true, .shift = true });
try testing.expectEqual(@as(u8, 0x03), seq.?);
test "ctrlseq: shift does not generate ctrl seq" {
try testing.expect(ctrlSeq("C", 'c', .{ .shift = true }) == null);
try testing.expect(ctrlSeq("C", 'c', .{ .shift = true, .ctrl = true }) == null);
}