diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-27 23:42:53 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 9584b0682b45f17aaa42157540947904d867777a (patch) | |
| tree | faac80b84c02e9611e9df9ff0e210c1d1c895a84 /test | |
| parent | 95a3a1bb822e583988bfe0f312d0ef219b27a454 (diff) | |
| download | pardes-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')
| -rw-r--r-- | test/panes.zig | 154 | ||||
| -rw-r--r-- | test/pdf.zig | 4 | ||||
| -rw-r--r-- | test/snapshots/find.golden | 1 | ||||
| -rw-r--r-- | test/snapshots/find.snap | 3 | ||||
| -rw-r--r-- | test/snapshots/tagbottomshort.golden | 2 | ||||
| -rw-r--r-- | test/snapshots/tagbottomshort.snap | 9 |
6 files changed, 45 insertions, 128 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); } } diff --git a/test/pdf.zig b/test/pdf.zig index 52fb62aa..5bc271d4 100644 --- a/test/pdf.zig +++ b/test/pdf.zig @@ -1113,9 +1113,9 @@ test "PDF normal adapter consumes unsupported actions and navigates page fallbac p.update(.{ .key = .{ .cp = '!' } }); try std.testing.expect(!p.leader_on); p.update(.{ .key = .{ .cp = '/' } }); - try std.testing.expect(pane.tag_edit); + try std.testing.expect(pane.prompt == .search and !pane.tag_edit); p.update(.{ .key = .{ .cp = Key.escape } }); - try std.testing.expect(!pane.tag_edit); + try std.testing.expect(pane.prompt == .none); } test "Escape cancels PDF chrome in place and Shift-Escape leaves the pane" { diff --git a/test/snapshots/find.golden b/test/snapshots/find.golden index 12dac242..b6b7b322 100644 --- a/test/snapshots/find.golden +++ b/test/snapshots/find.golden @@ -81,7 +81,6 @@ |4: |30: 1 gamma body Last == snap nomatch grid=120x41 cursor=7,39 -|2: /tmp/pardes-snap/find/cwd/here.txt Save Tty Collapse Del Find / |3: 1 Find |18: /tmp/pardes-snap/find/cwd/+Search Save Tty Collapse Del |19: 1 diff --git a/test/snapshots/find.snap b/test/snapshots/find.snap index fbd585f7..fe1ecad3 100644 --- a/test/snapshots/find.snap +++ b/test/snapshots/find.snap @@ -64,7 +64,8 @@ key enter stable 700 15000 snap back # executing the WORD Find (middle-click in a body) arms the same input — the -# enum IS the dispatch — and Esc abandons it, restoring the tag tail +# enum IS the dispatch — and Esc abandons it. The input is its own line, so an +# abandoned one leaves nothing behind in the tag press middle 8 4 release middle 8 4 stable 700 10000 diff --git a/test/snapshots/tagbottomshort.golden b/test/snapshots/tagbottomshort.golden index a5d53fe2..c11fe4ac 100644 --- a/test/snapshots/tagbottomshort.golden +++ b/test/snapshots/tagbottomshort.golden @@ -21,7 +21,7 @@ | New Tty Find Grep Joincol Delcol | 1 one /ZZ | /tmp/pardes-snap/tagbottomshort/cwd/s.txt Save Tty Collapse Del -== snap tag-only grid=80x2 cursor=70,1 +== snap tag-only grid=80x2 cursor=79,1 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Kill | /tmp/pardes-snap/tagbottomshort/cwd/s.txt Save Tty Collapse Del == snap regrown grid=80x11 cursor=79,2 diff --git a/test/snapshots/tagbottomshort.snap b/test/snapshots/tagbottomshort.snap index 2574df36..58da6a87 100644 --- a/test/snapshots/tagbottomshort.snap +++ b/test/snapshots/tagbottomshort.snap @@ -10,8 +10,8 @@ wait 8000 s.txt stable 700 20000 key space t b stable 700 15000 -# `/` with room to spare: the prompt is cut off the tagline (grid 9) and drawn -# on grid 8, the last body row, with the cursor. +# `/` with room to spare: the prompt is its own line, drawn on grid 8, the last +# body row, with the cursor; the tagline (grid 9) never holds any of it. key / send ZZ stable 600 8000 @@ -27,9 +27,8 @@ resize 4 80 stable 700 10000 snap one-body # the tagline alone. No body row, so nothing of the prompt is drawn anywhere — -# not over the tag, not on the row above the pane. The tagline itself is -# untouched: the marker sits after the builtins, so cutting the tail there -# leaves every column of it exactly as `tag-only` would look unarmed. +# not over the tag, not on the row above the pane, and its caret with it. The +# tagline itself is untouched, exactly as `tag-only` would look unarmed. resize 2 80 stable 700 10000 snap tag-only |
