diff options
Diffstat (limited to 'test/panes.zig')
| -rw-r--r-- | test/panes.zig | 76 |
1 files changed, 71 insertions, 5 deletions
diff --git a/test/panes.zig b/test/panes.zig index d3c29856..e4e677e4 100644 --- a/test/panes.zig +++ b/test/panes.zig @@ -731,6 +731,72 @@ const TtySelectionTests = struct { } } } + + /// Every byte the pane's program would have read off its pty since the + /// last call, joined. + fn childInput(p: *Pardes, buf: []u8) []const u8 { + var n: usize = 0; + while (p.nextEffect()) |effect| switch (effect) { + .write => |w| { + const bytes = w.bytes.slice(); + if (n + bytes.len > buf.len) break; + @memcpy(buf[n..][0..bytes.len], bytes); + n += bytes.len; + }, + else => {}, + }; + return buf[0..n]; + } + + // Raw tty mode forwards nearly every key to the child; the two paste + // chords are the exception, and forwarding them instead is what broke + // pasting into a terminal pane. An application that gets Ctrl-V rather + // than the text goes and reads the desktop clipboard on its own account, + // and a coding agent reading it is looking for an image, not for words. + test "raw tty paste chords type at the program instead of reaching it as keys" { + if (comptime !panes.Terminal.enabled) return error.SkipZigTest; + for ([_]bool{ false, true }) |bracketed| { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 40, .rows = 12 }); + defer p.deinit(); + const pane = p.panes[0].?; + try std.testing.expectEqual(Pane.Mode.tty, pane.mode); + if (bracketed) p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?2004h" } }); + try std.testing.expectEqual(bracketed, panes.Terminal.bracketedPaste(pane)); + p.yank = try p.gpa.dupe(u8, "one\ntwo"); // what a `y` anywhere left behind + var buf: [256]u8 = undefined; + _ = childInput(p, &buf); + + // Ctrl-V types the register straight at the program, asking the + // desktop for nothing. Under DECSET 2004 the program's own fences + // carry it, which is what stops readline RUNNING a multi-line paste. + p.update(.{ .key = .{ .cp = 'v', .ctrl = true } }); + try std.testing.expectEqualStrings( + if (bracketed) "\x1b[200~one\ntwo\x1b[201~" else "one\rtwo", + childInput(p, &buf), + ); + try std.testing.expect(p.clip_pending == null); + + // Ctrl-Shift-V is the other store: it ASKS, types nothing yet, and + // the answer lands at the program rather than in an edit buffer. A + // host that folds the Shift into the letter says the same thing. + for ([_]Key{ + .{ .cp = 'v', .ctrl = true, .shift = true }, + .{ .cp = 'V', .ctrl = true }, + }) |chord| { + p.update(.{ .key = chord }); + try std.testing.expect(p.clip_pending != null); + try std.testing.expectEqualStrings("", childInput(p, &buf)); + p.update(.{ .paste = "from-desktop" }); + try std.testing.expect(p.clip_pending == null); + try std.testing.expectEqualStrings( + if (bracketed) "\x1b[200~from-desktop\x1b[201~" else "from-desktop", + childInput(p, &buf), + ); + } + // ...and neither chord put the clipboard in the register on its way. + try std.testing.expectEqualStrings("one\ntwo", p.yank orelse ""); + } + } }; const ClickCursorTests = struct { @@ -1616,7 +1682,7 @@ test "generated output refuses every pipe prompt before seeding the tag" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); defer p.deinit(); const content = try p.gpa.dupe(u8, "generated\n"); - const pane = try panes.Output.open(p, 1, "/", .{ .cmd = .Help }, "", content); + const pane = try panes.Output.open(p, p.freeSlot().?, "/", .{ .cmd = .Help }, "", content); p.active = 1; for ([_]Key{ .{ .cp = '|' }, .{ .cp = '|', .alt = true }, .{ .cp = '!' }, .{ .cp = '!', .alt = true } }) |key| { p.update(.{ .key = key }); @@ -1825,14 +1891,14 @@ test "file history construction failures preserve existing panes" { defer if (incoming) |bytes| p.gpa.free(bytes); failing.fail_index = failing.alloc_index + failure; const result = switch (case) { - .open => panes.File.open(p, 1, path, 0), + .open => panes.File.open(p, p.freeSlot().?, path, 0), .restore => panes.File.restore(p, 1, .{ .kind = .file, .tag = "", .body = "", .file = .{ .path = "/restored.zig", .content = "restored\n" }, }), - .output => panes.Output.open(p, 1, "/", .{ .cmd = .Help }, "", incoming.?), + .output => panes.Output.open(p, p.freeSlot().?, "/", .{ .cmd = .Help }, "", incoming.?), .tutor => p.openTutorView(1), .replace => p.setTestFile("replacement\n"), }; @@ -1900,14 +1966,14 @@ test "file history owns bounded snapshot storage without per-edit bookkeeping al try std.testing.expectEqual(@as(usize, 0), history.redo_len); } -test "file history allocation precedes a filesystem read with side effects" { +test "file history allocation precedes reading the served tree" { var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); const p = try Pardes.init(failing.allocator(), .{ .tty_only = true, .cols = 40, .rows = 12 }); defer p.deinit(); const original = p.panes[0].?; const serial = p.next_serial; failing.fail_index = failing.alloc_index + 2; - try std.testing.expectError(error.OutOfMemory, panes.File.open(p, 1, "/n/self/new", 0)); + try std.testing.expectError(error.OutOfMemory, panes.File.open(p, p.freeSlot().?, "/n/self/index", 0)); try std.testing.expect(failing.has_induced_failure); try std.testing.expectEqual(original, p.panes[0].?); try std.testing.expectEqual(serial, p.next_serial); |
