summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Terminal.zig6
-rw-r--r--src/tty/tty.zig65
2 files changed, 70 insertions, 1 deletions
diff --git a/src/Terminal.zig b/src/Terminal.zig
index 32595d25..a32819c3 100644
--- a/src/Terminal.zig
+++ b/src/Terminal.zig
@@ -1131,7 +1131,11 @@ pub fn forwardKey(p: *Pardes, id: usize, key: Key) void {
};
var utf8: [4]u8 = undefined;
const text_cp = if (key.shift and key.cp >= 'a' and key.cp <= 'z') key.cp - 'a' + 'A' else key.cp;
- const text = if (key.text.len > 0) key.text else if (key.cp >= 32 and key.cp < Key.up and key.cp != Key.backspace)
+ // A key with no text of its own types its code point only when that
+ // is a character: the kitty keyboard protocol numbers function and
+ // modifier keys in the private use area (F3 is U+E016), and a shell
+ // was typed those as junk.
+ const text = if (key.text.len > 0) key.text else if (key.cp >= 32 and key.cp < Key.up and key.cp != Key.backspace and !(key.cp >= 0xE000 and key.cp <= 0xF8FF))
utf8[0 .. std.unicode.utf8Encode(text_cp, &utf8) catch return]
else
"";
diff --git a/src/tty/tty.zig b/src/tty/tty.zig
index acd75e0a..206d5c58 100644
--- a/src/tty/tty.zig
+++ b/src/tty/tty.zig
@@ -164,6 +164,10 @@ fn readInput(loop: *Loop, tty: anytype, cache: *vaxis.GraphemeCache) !void {
const result = try parser.parse(buf[consumed..parse_end], loop.vaxis.opts.system_clipboard_allocator);
if (result.n == 0) break;
consumed += result.n;
+ // A modifier pressed on its own, which the kitty keyboard
+ // protocol reports: pardes binds none, it would break a pending
+ // chord, and a shell pane was typed its private-use code.
+ if (result.event) |event| if (event == .key_press and event.key_press.isModifier()) continue;
if (result.event) |event|
vaxis.loop.handleEventGeneric(loop, loop.vaxis, cache, @TypeOf(Command.value), event, loop.vaxis.opts.system_clipboard_allocator) catch |err| {
if (event == .paste) if (loop.vaxis.opts.system_clipboard_allocator) |gpa| gpa.free(@constCast(event.paste));
@@ -1962,6 +1966,67 @@ fn keyEvent(key: vaxis.Key) pardes.Event {
} };
}
+test "keys with no text of their own type nothing into a terminal pane" {
+ if (comptime builtin.os.tag == .windows) return error.SkipZigTest;
+ const gpa = std.testing.allocator;
+ const io = std.testing.io;
+ var env = try std.testing.environ.createMap(gpa);
+ defer env.deinit();
+ var vx = try vaxis.init(io, gpa, &env, .{});
+ var output: std.Io.Writer.Allocating = .init(gpa);
+ defer output.deinit();
+ defer vx.deinit(gpa, &output.writer);
+ vx.queries_done.store(false, .unordered);
+ var tty: vaxis.Tty = undefined;
+ var loop: Loop = .init(io, &tty, &vx);
+ var cache: vaxis.GraphemeCache = .{};
+ const Reader = struct {
+ parts: []const []const u8,
+ next: usize = 0,
+ fn getWinsize(_: *@This()) !vaxis.Winsize {
+ return .{ .rows = 24, .cols = 80, .x_pixel = 0, .y_pixel = 0 };
+ }
+ fn inputFollows(self: *@This(), _: u32) bool {
+ return self.next < self.parts.len;
+ }
+ fn read(self: *@This(), buf: []u8) !usize {
+ if (self.next == self.parts.len) return 0;
+ const part = self.parts[self.next];
+ self.next += 1;
+ @memcpy(buf[0..part.len], part);
+ return part.len;
+ }
+ };
+ var reader: Reader = .{
+ .parts = &.{
+ // The startup width queries' answers from a terminal without
+ // explicit width (foot, ghostty, tmux): two F3s, typed at the prompt.
+ "\x1b[1;1R\x1b[1;1R\x1b[?62;c",
+ // Kitty reports Ctrl and Shift pressed on their own: the Ctrl of a
+ // Ctrl-V typed `\x1b[57442;5u` into the shell.
+ "\x1b[57442;5u\x1b[57441;2u\x1b[57358u",
+ // F3, F5 and Insert, kitty's way and the legacy way.
+ "\x1b[13~\x1b[15~\x1b[2~\x1bOR",
+ "\x1b[104;;104u",
+ },
+ };
+ try readInput(&loop, &reader, &cache);
+ const core = try pardes.Pardes.init(gpa, .{ .tty_only = true });
+ defer core.deinit();
+ core.panes[0].?.body.mode = .tty;
+ while (core.nextEffect()) |_| {}
+ while (try loop.tryEvent()) |event| switch (event) {
+ .key_press => |key| core.update(keyEvent(key)),
+ else => {},
+ };
+ var written: std.Io.Writer.Allocating = .init(gpa);
+ defer written.deinit();
+ while (core.nextEffect()) |effect| {
+ if (effect == .write) try written.writer.writeAll(effect.write.bytes.slice());
+ }
+ try std.testing.expectEqualStrings("h", written.written());
+}
+
test "keypad Enter matches Return for editor input terminal writes and paste" {
for ([_]u21{ vaxis.Key.enter, vaxis.Key.kp_enter }) |codepoint| {
const key: vaxis.Key = .{ .codepoint = codepoint };