summaryrefslogtreecommitdiff
path: root/src/gui/gui.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 14:23:50 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit505d15e238e4e9c5020a8e567d700bdbf67a8528 (patch)
treed0e136a29cc5f3416046157824f07c5e3bb1f67e /src/gui/gui.zig
parent17c0d06618085de8d0bbf8152750ffa862db0080 (diff)
downloadpardes-505d15e238e4e9c5020a8e567d700bdbf67a8528.tar.gz
pardes-505d15e238e4e9c5020a8e567d700bdbf67a8528.zip
A paste into a terminal is xterm's paste: bracketed when the program set 2004, and the control bytes a paste could smuggle a command in by, ESC first, become spaces, so no ESC[201~ in the text ends the brackets early
Every paste path goes through edit.typeToTty (the GUI's and the tty's paste, p from a register, the acme chord's paste, a REPL's send), which now takes ghostty's paste encoder. A 9P write to a terminal's body or pty/data stays typed input, not a paste. The GUI test shell's stdin now takes a bracketed paste as one paste, as the tty does, not as keys. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/gui/gui.zig')
-rw-r--r--src/gui/gui.zig52
1 files changed, 51 insertions, 1 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 81b68afd..14205906 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -3097,6 +3097,9 @@ fn runGrid(init: std.process.Init, opts_in: pardes.Options) !void {
const StdinFeed = struct {
parser: vaxis.Parser = .{},
+ /// Inside the driver's bracketed paste: its keys are the paste's text.
+ in_paste: bool = false,
+ paste: std.ArrayList(u8) = .empty,
cache: vaxis.GraphemeCache = .{},
buf: [1024]u8 = undefined,
fill: usize = 0,
@@ -3241,10 +3244,24 @@ const StdinFeed = struct {
seq_start += result.n;
const event = result.event orelse continue;
switch (event) {
- .key_press => |key| {
+ .key_press => |key| if (f.in_paste) {
+ // A newline reads as Ctrl-J, as the tty's paste has it.
+ const text = key.text orelse if (key.codepoint == vaxis.Key.enter or (key.mods.ctrl and key.codepoint == 'j')) "\n" else if (key.codepoint == vaxis.Key.tab) "\t" else "";
+ f.paste.appendSlice(gpa, text) catch {};
+ } else {
postKey(in, key, if (key.text) |t| f.cache.put(t) else "");
out.n_events += 1;
},
+ .paste_start => {
+ f.in_paste = true;
+ f.paste.clearRetainingCapacity();
+ },
+ .paste_end => {
+ f.in_paste = false;
+ if (f.paste.items.len > 0) in.post(.{ .paste = f.paste.items });
+ f.paste.clearAndFree(gpa);
+ out.n_events += 1;
+ },
.mouse => |m| {
const button: ?pardes.Mouse.Button = switch (m.button) {
.left => .left,
@@ -4820,6 +4837,39 @@ test "the headless grid host round-trips a yank back as a paste" {
try std.testing.expectEqual(before + yanked.len, pane.file.?.content.len);
}
+test "the clipboard pasted into a terminal reaches its pty as xterm pastes: bracketed when asked, no ESC[201~ smuggled through" {
+ if (comptime !pardes.panes.terminal.enabled) return error.SkipZigTest;
+ const gpa = std.testing.allocator;
+ const core = try pardes.Pardes.init(gpa, .{ .cols = 60, .rows = 16, .tty_only = true });
+ defer core.deinit();
+ var shell: Shell = undefined;
+ shell.core = core;
+ var sent: std.ArrayList(u8) = .empty;
+ defer sent.deinit(gpa);
+ const take = struct {
+ fn f(core_: *pardes.Pardes, out: *std.ArrayList(u8)) ![]const u8 {
+ out.clearRetainingCapacity();
+ while (core_.nextEffect()) |effect| {
+ if (effect == .write and effect.write.pane == 0) try out.appendSlice(std.testing.allocator, effect.write.bytes.slice());
+ }
+ return out.items;
+ }
+ }.f;
+ _ = try take(core, &sent);
+ // The clipboard is SDL's own with no display to hold one.
+ _ = c.SDL_SetHint(c.SDL_HINT_VIDEO_DRIVER, "dummy");
+ defer _ = c.SDL_ResetHint(c.SDL_HINT_VIDEO_DRIVER);
+ if (!c.SDL_InitSubSystem(c.SDL_INIT_VIDEO)) return error.SkipZigTest;
+ defer c.SDL_QuitSubSystem(c.SDL_INIT_VIDEO);
+ putClipboard(gpa, "ls\x1b[201~; rm -rf ~\nx");
+ readClipboard(&shell);
+ try std.testing.expectEqualStrings("ls [201~; rm -rf ~\rx", try take(core, &sent));
+ core.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?2004h" } });
+ _ = try take(core, &sent);
+ readClipboard(&shell);
+ try std.testing.expectEqualStrings("\x1b[200~ls [201~; rm -rf ~\nx\x1b[201~", try take(core, &sent));
+}
+
fn openLink(ctx: ?*anyopaque, url: []const u8) void {
_ = ctx;
look.openLink(url); // desktop browser