summaryrefslogtreecommitdiff
path: root/test/panes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-27 23:42:53 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit9584b0682b45f17aaa42157540947904d867777a (patch)
treefaac80b84c02e9611e9df9ff0e210c1d1c895a84 /test/panes.zig
parent95a3a1bb822e583988bfe0f312d0ef219b27a454 (diff)
downloadpardes-9584b0682b45f17aaa42157540947904d867777a.tar.gz
pardes-9584b0682b45f17aaa42157540947904d867777a.zip
Give prompts their own line to type into instead of the tag's tail
A search, pipe or Save prompt wrote its marker and answer onto the end of the tag, so the tag's cursor, its selection, its 9P writes and a prompt abandoned by a click all shared one buffer: the abandoned text stayed in the tag and a 9P write landed inside the prompt. The answer is now its own Text on the pane, typed in insert mode and shown on the notice band where it was already drawn, and the tag never holds any of it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'test/panes.zig')
-rw-r--r--test/panes.zig154
1 files changed, 36 insertions, 118 deletions
diff --git a/test/panes.zig b/test/panes.zig
index 4c969ab5..f1adafee 100644
--- a/test/panes.zig
+++ b/test/panes.zig
@@ -1353,8 +1353,9 @@ test "paste into pane commands and prompts never inserts into the body" {
try std.testing.expect(std.mem.endsWith(u8, pane.tagSlice(), " CustomCommand"));
p.update(.{ .key = .{ .cp = Key.escape } });
p.startPrompt(pane, .{ .search = config.search_marker });
- p.update(.{ .paste = "two" });
- try std.testing.expect(std.mem.endsWith(u8, pane.tagSlice(), "two"));
+ p.update(.{ .paste = "t\r\nwo" });
+ try std.testing.expectEqualStrings(config.search_marker ++ "two", pane.promptText().?);
+ try std.testing.expect(std.mem.endsWith(u8, pane.tagSlice(), " CustomCommand"));
try std.testing.expectEqualStrings("one two one\n", pane.file.?.content);
p.update(.{ .key = .{ .cp = Key.escape } });
try std.testing.expect(std.mem.endsWith(u8, pane.tagSlice(), " CustomCommand"));
@@ -1646,7 +1647,7 @@ test "terminal eviction and lower-region scrolling in one read preserve a surviv
try std.testing.expectEqualStrings("kept edit", pane.ovl.?.text);
}
-test "tag prompt entry and cancellation preserve exact text and cursor state" {
+test "a prompt's entry and cancellation leave the tag, the body and its cursor as they were" {
const Case = struct { key: ?Key = null, save: bool = false, marker: []const u8 };
const cases = [_]Case{
.{ .marker = config.search_marker },
@@ -1665,8 +1666,6 @@ test "tag prompt entry and cancellation preserve exact text and cursor state" {
@memcpy(pane.tag_tail[0..tail.len], tail);
pane.tag_tail_len = tail.len;
pane.tag_init = true;
- pane.tag_sel = true;
- pane.tag_col = 1;
pane.body.cur_row = 1;
pane.body.cur_col = 2;
pane.body.cur_pinned = true;
@@ -1677,16 +1676,16 @@ test "tag prompt entry and cancellation preserve exact text and cursor state" {
} else {
p.startPrompt(pane, .{ .search = config.search_marker });
}
- try std.testing.expect(pane.tag_edit);
- try std.testing.expect(!pane.tag_sel);
- try std.testing.expectEqual(.insert, pane.body.mode);
+ // The answer is typed into the prompt's own line, never the tag.
+ try std.testing.expect(!pane.tag_edit);
+ try std.testing.expectEqual(.insert, pane.input.mode);
+ try std.testing.expectEqualStrings(case.marker, pane.promptText().?);
+ try std.testing.expectEqual(@as(i32, @intCast(case.marker.len)), pane.input.cur_col);
+ try std.testing.expectEqualStrings(tail, pane.tag_tail[0..pane.tag_tail_len]);
try std.testing.expectEqualDeep(modal.Normal.State{}, pane.body.normal);
- try std.testing.expectEqualStrings(tail, pane.tag_tail[0..tail.len]);
- try std.testing.expectEqualStrings(case.marker, pane.tag_tail[tail.len..pane.tag_tail_len]);
- try std.testing.expectEqual(pane.file.?.path.len + pane.tag_tail_len, pane.tag_col);
p.update(.{ .key = .{ .cp = Key.escape } });
try std.testing.expect(pane.prompt == .none);
- try std.testing.expect(!pane.tag_edit);
+ try std.testing.expect(pane.input.own == null);
try std.testing.expectEqual(.normal, pane.body.mode);
try std.testing.expectEqualStrings(tail, pane.tag_tail[0..pane.tag_tail_len]);
try std.testing.expectEqual(@as(i32, 1), pane.body.cur_row);
@@ -1695,28 +1694,23 @@ test "tag prompt entry and cancellation preserve exact text and cursor state" {
}
}
-test "tag prompt capacity refusal leaves the existing tail and input state untouched" {
+test "a prompt opens whatever the tag already holds" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
const pane = try p.setTestFile("one\n");
@memset(&pane.tag_tail, 'x');
- pane.tag_tail_len = pane.tag_tail.len - 1;
+ pane.tag_tail_len = pane.tag_tail.len;
pane.tag_init = true;
- pane.tag_col = 17;
- pane.tag_sel = true;
for (0..3) |case| {
switch (case) {
0 => p.startPrompt(pane, .{ .search = config.search_marker }),
1 => p.startPrompt(pane, .save),
else => p.update(.{ .key = .{ .cp = '|' } }),
}
- try std.testing.expect(pane.prompt == .none);
- try std.testing.expect(!pane.tag_edit);
- try std.testing.expect(pane.tag_sel);
- try std.testing.expectEqual(.normal, pane.body.mode);
- try std.testing.expectEqual(@as(u16, 17), pane.tag_col);
- try std.testing.expectEqual(pane.tag_tail.len - 1, pane.tag_tail_len);
+ try std.testing.expect(pane.prompt != .none);
+ try std.testing.expectEqual(pane.tag_tail.len, pane.tag_tail_len);
for (pane.tag_tail) |byte| try std.testing.expectEqual(@as(u8, 'x'), byte);
+ p.update(.{ .key = .{ .cp = Key.escape } });
}
}
@@ -1735,7 +1729,7 @@ test "generated output refuses every pipe prompt before seeding the tag" {
}
}
-test "tag prompt owns the pipe behavior through cancellation and submission" {
+test "a prompt owns the pipe behavior through cancellation and submission" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
const pane = try p.setTestFile("one\ntwo\n");
@@ -1746,7 +1740,7 @@ test "tag prompt owns the pipe behavior through cancellation and submission" {
p.startPrompt(pane, .{ .pipe = .append });
p.update(.{ .key = .{ .cp = Key.escape } });
p.startPrompt(pane, .{ .pipe = how });
- try std.testing.expectEqual(how, pane.prompt.pipe.how);
+ try std.testing.expectEqual(how, pane.prompt.pipe);
p.update(.{ .key = .{ .cp = 'c', .text = "cat" } });
p.update(.{ .key = .{ .cp = Key.enter } });
const pending = p.pipe.wait orelse return error.MissingPipe;
@@ -1758,7 +1752,7 @@ test "tag prompt owns the pipe behavior through cancellation and submission" {
}
}
-test "tag prompt cancellation restores the selection before regex previews" {
+test "prompt cancellation restores the selection before regex previews" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
const pane = try p.setTestFile("one two one");
@@ -1786,133 +1780,57 @@ test "tag prompt cancellation restores the selection before regex previews" {
}
}
-test "tag prompt prefix allocation failure preserves an already seeded prompt" {
+test "a prompt that cannot be opened leaves the open one as it was" {
const Request = @typeInfo(@TypeOf(Pardes.startPrompt)).@"fn".params[2].type.?;
for ([_]Request{ .{ .search = config.search_marker }, .save, .{ .pipe = .replace }, .{ .pipe = .ignore }, .{ .pipe = .insert }, .{ .pipe = .append } }) |request| {
var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});
const p = try Pardes.init(failing.allocator(), .{ .tty_only = true });
defer p.deinit();
const pane = try p.setTestFile("one\n");
- const tail = " keep /prior";
- @memcpy(pane.tag_tail[0..tail.len], tail);
- pane.tag_tail_len = tail.len;
- pane.tag_init = true;
- pane.prompt = .{ .search = 5 };
- pane.tag_edit = true;
- pane.tag_sel = true;
- pane.tag_col = 14;
- pane.tag_anchor = 7;
+ p.startPrompt(pane, .{ .search = config.search_marker });
+ p.update(.{ .key = .{ .cp = 'p', .text = "prior" } });
pane.body.normal = .{ .count = 9, .prefix = .goto };
- const previous_prompt = pane.prompt;
const previous_normal = pane.body.normal;
_ = p.scratch.reset(.free_all);
failing.fail_index = failing.alloc_index;
p.startPrompt(pane, request);
try std.testing.expect(failing.has_induced_failure);
- try std.testing.expectEqualDeep(previous_prompt, pane.prompt);
+ try std.testing.expect(pane.prompt == .search);
+ try std.testing.expectEqualStrings(config.search_marker ++ "prior", pane.promptText().?);
try std.testing.expectEqualDeep(previous_normal, pane.body.normal);
- try std.testing.expectEqualStrings(tail, pane.tag_tail[0..pane.tag_tail_len]);
- try std.testing.expect(pane.tag_edit and pane.tag_sel);
- try std.testing.expectEqual(.normal, pane.body.mode);
- try std.testing.expectEqual(@as(u16, 14), pane.tag_col);
- try std.testing.expectEqual(@as(u16, 7), pane.tag_anchor);
+ try std.testing.expect(!pane.tag_init and !pane.tag_edit);
}
}
-test "tag prompt Save requires room for its complete directory prefix" {
+test "Save's prompt starts from the pane's directory" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
const pane = try p.setTestFile("one\n");
for ([_][]const u8{ "/dír space", "/dír space/", "" }) |dir| {
try pane.setOwnedCwd(dir);
- const slash: usize = @intFromBool(dir.len == 0 or dir[dir.len - 1] != '/');
- const needed = config.save_marker.len + dir.len + slash;
- for ([_]usize{ 1, 0 }) |shortfall| {
- @memset(&pane.tag_tail, 'x');
- pane.tag_tail_len = pane.tag_tail.len - needed + shortfall;
- pane.tag_init = true;
- pane.tag_edit = false;
- pane.tag_sel = true;
- pane.tag_col = 17;
- const start = pane.tag_tail_len;
- p.startPrompt(pane, .save);
- if (shortfall != 0) {
- try std.testing.expect(pane.prompt == .none);
- try std.testing.expectEqual(start, pane.tag_tail_len);
- try std.testing.expect(!pane.tag_edit and pane.tag_sel);
- try std.testing.expectEqual(@as(u16, 17), pane.tag_col);
- for (pane.tag_tail) |byte| try std.testing.expectEqual(@as(u8, 'x'), byte);
- } else {
- try std.testing.expect(pane.prompt == .save);
- try std.testing.expectEqual(pane.tag_tail.len, pane.tag_tail_len);
- try std.testing.expectEqualStrings(config.save_marker, pane.tag_tail[start..][0..config.save_marker.len]);
- try std.testing.expectEqualStrings(dir, pane.tag_tail[start + config.save_marker.len ..][0..dir.len]);
- try std.testing.expectEqual(@as(u8, '/'), pane.tag_tail[pane.tag_tail_len - 1]);
- p.update(.{ .key = .{ .cp = Key.escape } });
- }
- }
+ const slash = if (dir.len == 0 or dir[dir.len - 1] != '/') "/" else "";
+ p.startPrompt(pane, .save);
+ try std.testing.expect(pane.prompt == .save);
+ const answer = pane.promptText().?;
+ try std.testing.expect(std.mem.startsWith(u8, answer, config.save_marker));
+ try std.testing.expectEqualStrings(dir, answer[config.save_marker.len..][0..dir.len]);
+ try std.testing.expectEqualStrings(slash, answer[config.save_marker.len + dir.len ..]);
+ p.update(.{ .key = .{ .cp = Key.escape } });
}
}
-test "tag prompt successful seeded entry needs one scratch allocation" {
+test "a prompt opens with one allocation" {
const Request = @typeInfo(@TypeOf(Pardes.startPrompt)).@"fn".params[2].type.?;
for ([_]Request{ .{ .search = config.search_marker }, .save, .{ .pipe = .replace }, .{ .pipe = .ignore }, .{ .pipe = .insert }, .{ .pipe = .append } }) |request| {
var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});
const p = try Pardes.init(failing.allocator(), .{ .tty_only = true });
defer p.deinit();
const pane = try p.setTestFile("one\n");
- pane.tag_tail_len = 0;
- pane.tag_init = true;
_ = p.scratch.reset(.free_all);
const before = failing.alloc_index;
p.startPrompt(pane, request);
try std.testing.expectEqual(before + 1, failing.alloc_index);
- try std.testing.expect(pane.tag_edit and pane.body.mode == .insert);
- }
-}
-
-test "tag prompt failure may initialize only the default tail" {
- const Request = @typeInfo(@TypeOf(Pardes.startPrompt)).@"fn".params[2].type.?;
- for ([_]Request{ .{ .search = config.search_marker }, .save, .{ .pipe = .replace }, .{ .pipe = .ignore }, .{ .pipe = .insert }, .{ .pipe = .append } }) |request| {
- var saw_seeded_failure = false;
- var succeeded = false;
- for (0..16) |failure| {
- var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});
- const p = try Pardes.init(failing.allocator(), .{ .tty_only = true });
- defer p.deinit();
- const pane = try p.setTestFile("one\n");
- const path = try p.gpa.dupe(u8, "/" ++ "a" ** 2048 ++ ".zig");
- p.gpa.free(pane.file.?.path);
- pane.file.?.path = path;
- p.seedTail(pane);
- try std.testing.expect(pane.tag_init);
- const default_tail = pane.tag_tail;
- const default_len = pane.tag_tail_len;
- pane.tag_init = false;
- pane.tag_tail_len = 0;
- pane.tag_col = 17;
- pane.tag_sel = true;
- pane.body.normal = .{ .count = 9, .prefix = .goto };
- const previous_normal = pane.body.normal;
- _ = p.scratch.reset(.free_all);
- failing.fail_index = failing.alloc_index + failure;
- p.startPrompt(pane, request);
- if (!failing.has_induced_failure) {
- try std.testing.expect(pane.tag_edit and pane.body.mode == .insert);
- succeeded = true;
- break;
- }
- try std.testing.expect(pane.prompt == .none);
- try std.testing.expect(!pane.tag_edit and pane.tag_sel);
- try std.testing.expectEqual(.normal, pane.body.mode);
- try std.testing.expectEqualDeep(previous_normal, pane.body.normal);
- try std.testing.expectEqual(@as(u16, 17), pane.tag_col);
- if (pane.tag_init) {
- saw_seeded_failure = true;
- try std.testing.expectEqualStrings(default_tail[0..default_len], pane.tag_tail[0..pane.tag_tail_len]);
- } else try std.testing.expectEqual(@as(usize, 0), pane.tag_tail_len);
- }
- try std.testing.expect(succeeded and saw_seeded_failure);
+ try std.testing.expect(!pane.tag_edit and pane.input.mode == .insert);
}
}