diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 14:29:44 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | 8262f315538b70ab8a123ef1e9346f25b25ec939 (patch) | |
| tree | 7e0058b229bcf974758f748cbca5b7730aef856c /src | |
| parent | 505d15e238e4e9c5020a8e567d700bdbf67a8528 (diff) | |
| download | pardes-8262f315538b70ab8a123ef1e9346f25b25ec939.tar.gz pardes-8262f315538b70ab8a123ef1e9346f25b25ec939.zip | |
The window's F1-F24, Insert, Print Screen, Scroll Lock, Pause and Menu keys reach a terminal's program, which they never did; every key a program gets follows its cursor keys' mode and kitty keyboard flags, pinned for both shells
The keys went through ghostty's encoder already (DECCKM, the kitty flags);
the GUI's native key map simply dropped the function and editing keys it
did not name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/gui/gui.zig | 48 | ||||
| -rw-r--r-- | src/tty/tty.zig | 38 |
2 files changed, 86 insertions, 0 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 14205906..1e58d3ba 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -3444,6 +3444,47 @@ test "the GUI's stdin keys reach a terminal pane as the tty's do: F5, Insert and try std.testing.expectEqualStrings("\x1b[15~\x1b[2~\x1b[A", written.written()); } +test "the window's function and editing keys reach a terminal as its cursor keys' mode and keyboard flags say" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .tty_only = true }); + defer core.deinit(); + core.panes[0].?.body.mode = .tty; + while (core.nextEffect()) |_| {} + var in: Input = .{ .core = core }; + var written: std.Io.Writer.Allocating = .init(gpa); + defer written.deinit(); + const Case = struct { modes: []const u8, sym: c.SDL_Keycode, mod: c.SDL_Keymod = 0, sent: []const u8 }; + const cases = [_]Case{ + .{ .modes = "", .sym = c.SDLK_UP, .sent = "\x1b[A" }, + .{ .modes = "", .sym = c.SDLK_HOME, .sent = "\x1b[H" }, + .{ .modes = "", .sym = c.SDLK_F1, .sent = "\x1bOP" }, + .{ .modes = "", .sym = c.SDLK_F5, .sent = "\x1b[15~" }, + .{ .modes = "", .sym = c.SDLK_F12, .mod = c.SDL_KMOD_LSHIFT, .sent = "\x1b[24;2~" }, + .{ .modes = "", .sym = c.SDLK_INSERT, .sent = "\x1b[2~" }, + // DECCKM: less, vim, htop. + .{ .modes = "\x1b[?1h", .sym = c.SDLK_UP, .sent = "\x1bOA" }, + .{ .modes = "", .sym = c.SDLK_END, .sent = "\x1bOF" }, + .{ .modes = "", .sym = c.SDLK_LEFT, .mod = c.SDL_KMOD_LCTRL, .sent = "\x1b[1;5D" }, + // codex's kitty flags (>7u): Ctrl-C and Alt-a are unambiguous. + // (A bare Escape at a shell prompt is pardes's way out of raw input.) + .{ .modes = "\x1b[?1l\x1b[>7u", .sym = 'c', .mod = c.SDL_KMOD_LCTRL, .sent = "\x1b[99;5u" }, + .{ .modes = "", .sym = 'a', .mod = c.SDL_KMOD_LALT, .sent = "\x1b[97;3u" }, + // With event types asked for (2), an arrow says it is a press, as + // ghostty says it. + .{ .modes = "", .sym = c.SDLK_UP, .sent = "\x1b[1;1:1A" }, + }; + for (cases) |case| { + core.update(.{ .output = .{ .pane = 0, .bytes = case.modes } }); + while (core.nextEffect()) |_| {} + keyDown(&in, case.sym, case.mod); + written.clearRetainingCapacity(); + while (core.nextEffect()) |effect| { + if (effect == .write) try written.writer.writeAll(effect.write.bytes.slice()); + } + try std.testing.expectEqualStrings(case.sent, written.written()); + } +} + const Input = struct { core: ?*pardes.Pardes = null, client: ?*detached_client.Client = null, @@ -3783,6 +3824,13 @@ fn keyDown(in: *Input, sym: c.SDL_Keycode, mod: c.SDL_Keymod) void { c.SDLK_PAGEUP => pardes.Key.page_up, c.SDLK_PAGEDOWN => pardes.Key.page_down, c.SDLK_DELETE => pardes.Key.delete, + c.SDLK_INSERT => pardes.Key.insert, + c.SDLK_PRINTSCREEN => pardes.Key.print_screen, + c.SDLK_SCROLLLOCK => pardes.Key.scroll_lock, + c.SDLK_PAUSE => pardes.Key.pause, + c.SDLK_APPLICATION => pardes.Key.menu, + c.SDLK_F1...c.SDLK_F12 => pardes.Key.f1 + @as(u21, @intCast(sym - c.SDLK_F1)), + c.SDLK_F13...c.SDLK_F24 => pardes.Key.f1 + 12 + @as(u21, @intCast(sym - c.SDLK_F13)), else => blk: { if (!(ctrl or alt or gui_mod)) break :blk 0; if (sym < 128 and sym >= ' ') break :blk @intCast(sym); diff --git a/src/tty/tty.zig b/src/tty/tty.zig index 67807cd5..0a9d65c4 100644 --- a/src/tty/tty.zig +++ b/src/tty/tty.zig @@ -2211,6 +2211,44 @@ test "a terminal pane gets function keys as their sequences and lone modifiers n try std.testing.expectEqualStrings("\x1bOR\x1b[15~\x1b[2~\x1bOR\x1b[Ah", written.written()); } +test "the terminal's keys reach a program as its cursor keys' mode and keyboard flags say" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .tty_only = true }); + defer core.deinit(); + core.panes[0].?.body.mode = .tty; + while (core.nextEffect()) |_| {} + var written: std.Io.Writer.Allocating = .init(gpa); + defer written.deinit(); + // What the outer terminal sends, what the program asked for, and what + // it should read. + const Case = struct { modes: []const u8, in: []const u8, sent: []const u8 }; + const cases = [_]Case{ + .{ .modes = "", .in = "\x1b[A\x1b[H\x1bOP\x1b[15~", .sent = "\x1b[A\x1b[H\x1bOP\x1b[15~" }, + .{ .modes = "\x1b[?1h", .in = "\x1b[A\x1b[F\x1b[1;5D", .sent = "\x1bOA\x1bOF\x1b[1;5D" }, + .{ .modes = "\x1b[?1l\x1b[>7u", .in = "\x1b[99;5u\x1b[97;3u\x1b[A", .sent = "\x1b[99;5u\x1b[97;3u\x1b[1;1:1A" }, + }; + for (cases) |case| { + core.update(.{ .output = .{ .pane = 0, .bytes = case.modes } }); + while (core.nextEffect()) |_| {} + var parser: vaxis.Parser = .{}; + var bytes = case.in; + while (bytes.len > 0) { + const result = try parser.parse(bytes, gpa); + if (result.n == 0) break; + bytes = bytes[result.n..]; + if (result.event) |event| switch (event) { + .key_press => |key| core.update(vaxis_input.keyEvent(key)), + else => {}, + }; + } + written.clearRetainingCapacity(); + while (core.nextEffect()) |effect| { + if (effect == .write) try written.writer.writeAll(effect.write.bytes.slice()); + } + try std.testing.expectEqualStrings(case.sent, 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 }; |
