diff options
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 4 | ||||
| -rw-r--r-- | src/builtins.zig | 33 |
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; } |
