summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 17:44:42 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit9f6de14934df03c2e3d521af78ce2e5d7362cc88 (patch)
tree6c05f115b5e6a21c1ad81d9d0236c5b969332ae8
parent5d29c3151e40c05e764a9112d407082df89a19e1 (diff)
downloadpardes-9f6de14934df03c2e3d521af78ce2e5d7362cc88.tar.gz
pardes-9f6de14934df03c2e3d521af78ce2e5d7362cc88.zip
An Exit or Restore refused over a ctl write names every unsaved pane, not just the first
Each pane got its own message, but a ctl write fails with its first error only, so a script was told about one pane, and the next Exit discarded both. The refusal now lists them all, as acme's rowclean does, and says the next one discards them all. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--.agents/skills/pardes-9p/SKILL.md4
-rw-r--r--src/builtins.zig33
2 files changed, 35 insertions, 2 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md
index 58009df5..87cbbe96 100644
--- a/.agents/skills/pardes-9p/SKILL.md
+++ b/.agents/skills/pardes-9p/SKILL.md
@@ -68,8 +68,8 @@ $m/focus the serial of the pane with the keyboard (empty while a column/wor
$m/ctl the settings, one a line as a write takes them; write a setting or a session builtin
(Newcol, Dump, Theme x; Exit QUITS the editor, Kill [word...] stops the
commands pardes started (command panes, lines it typed into shells), a word
- matching a command line's first word; Exit and Restore refuse once per
- unsaved pane, `<name>: Modified (Exit again to discard)`, and the same
+ matching a command line's first word; Exit and Restore refuse once,
+ naming every unsaved pane, `<a>, <b>: Modified (Exit again to discard them all)`, and the same
word again DISCARDS that text -- not a retry, unlike lock's `file in use`;
Kill signals only the foreground job, so of `sleep 30; echo done` the
echo still runs; Joincol needs a column to the right of the keyboard's);
diff --git a/src/builtins.zig b/src/builtins.zig
index 97f7817f..ef425c91 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -240,6 +240,28 @@ test "Exit asks once about unsaved text, and quits when asked again" {
try std.testing.expect(p.quit);
}
+test "Exit over a ctl write names every pane it asks about, and the next Exit discards them all" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const first = try p.setTestFile("saved\n");
+ first.file.?.saved_revision = first.file.?.revision -% 1;
+ p.newScratchBelow(p.active);
+ const second = p.panes[p.active].?;
+ panes.File.setContent(p, &second.file.?, try std.testing.allocator.dupe(u8, "x" ** 120));
+ try std.testing.expect(second.file.?.revision != second.file.?.saved_revision);
+ p.fs.no_prompt = true;
+ defer p.fs.no_prompt = false;
+ try std.testing.expect(p.executeBuiltinLine(p.active, "Exit"));
+ try std.testing.expect(!p.quit);
+ const said = p.fs.failure[0..p.fs.failure_len];
+ try std.testing.expect(std.mem.indexOf(u8, said, first.file.?.path) != null);
+ try std.testing.expect(std.mem.indexOf(u8, said, second.file.?.path) != null);
+ try std.testing.expect(std.mem.endsWith(u8, said, ": Modified (Exit again to discard them all)"));
+ p.fs.failure_len = 0;
+ try std.testing.expect(p.executeBuiltinLine(p.active, "Exit"));
+ try std.testing.expect(p.quit);
+}
+
/// A dump file that exists, for a Restore to get past reading it.
fn testDump(buf: []u8) ![]const u8 {
var tmp = std.testing.tmpDir(.{});
@@ -419,6 +441,10 @@ pub const Exec = struct {
/// worth the question, as winclean does not ask for one.
fn warnModified(c: Ctx, asking: Pane.Discarding) bool {
var modified = false;
+ // Every pane asked about is named in the one refusal a ctl write gets,
+ // as acme's rowclean names them all (wind.c:511).
+ var all_buf: [256]u8 = undefined;
+ var all = std.Io.Writer.fixed(&all_buf);
for (c.p.panes, 0..) |slot, id| {
const pane = slot orelse continue;
const f = pane.file orelse continue;
@@ -428,8 +454,15 @@ fn warnModified(c: Ctx, asking: Pane.Discarding) bool {
pane.discard_warned = .{ .revision = f.revision, .by = asking };
var buf: [limits.host_path_cap + 48]u8 = undefined;
c.p.reportFailure(id, std.fmt.bufPrint(&buf, "{s}: Modified ({s} again to discard)", .{ f.path, @tagName(asking) }) catch f.path);
+ all.print("{s}{s}", .{ if (modified) ", " else "", f.path }) catch {};
modified = true;
}
+ if (modified and c.p.fs.no_prompt) {
+ all.print(": Modified ({s} again to discard them all)", .{@tagName(asking)}) catch {};
+ const said = @import("Messages.zig").clip(all.buffered(), c.p.fs.failure.len);
+ @memcpy(c.p.fs.failure[0..said.len], said);
+ c.p.fs.failure_len = @intCast(said.len);
+ }
return modified;
}