summaryrefslogtreecommitdiff
path: root/test/panes.zig
diff options
context:
space:
mode:
Diffstat (limited to 'test/panes.zig')
-rw-r--r--test/panes.zig76
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);