diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Terminal.zig | 6 | ||||
| -rw-r--r-- | src/tty/tty.zig | 65 |
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 }; |
