diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 13:41:07 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | 066f25a8cee7f5c63e991a9857667c346c6b75df (patch) | |
| tree | d629528aee40b993f989693ca456005bac71aef3 /src | |
| parent | 164da9fc0a7ab3c7ed39ea0a2dd6bbff3c36cdaf (diff) | |
| download | pardes-066f25a8cee7f5c63e991a9857667c346c6b75df.tar.gz pardes-066f25a8cee7f5c63e991a9857667c346c6b75df.zip | |
A buffer whose file was deleted on disk is dirty, and Del asks first
A file deleted under an open buffer left dirty 0 and let Del, Exit and the rest throw the only copy of its text away unasked. It now counts as dirty, as acme's does, and the refusals ask about it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/builtins.zig | 19 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 14 | ||||
| -rw-r--r-- | src/ninep/pane.zig | 3 | ||||
| -rw-r--r-- | src/tagline.zig | 2 |
4 files changed, 29 insertions, 9 deletions
diff --git a/src/builtins.zig b/src/builtins.zig index b0499dc0..ca01e0fe 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -536,13 +536,18 @@ fn warnModifiedIn(c: Ctx, asking: Pane.Discarding, which: Asked) bool { // under 100 bytes it is not asked about, as acme's winclean does not // whine about a small unnamed window, nor about a command's output. const scratch = if (f.output) |o| o.from == .cmd and o.from.cmd == .New else false; - if (f.revision == f.saved_revision or !(panes.Output.fileTraits(f.output).saves or scratch)) continue; - if (f.output != null and f.content.len < 100) continue; - // Dirty by a rename alone is no text to lose, as get says (ctl.zig). - // Nor is a buffer never edited at all: its first edit is what keeps - // the text it had (File.zig), so with none there is nothing to lose. - const saved = f.saved_hash orelse continue; - if (std.hash.Wyhash.hash(0, f.content) == saved) continue; + // A file deleted on disk is asked about, edited or not: its text is + // nowhere else now (acme's winclean asks the same). + const gone = f.disk_gone and f.output == null; + if (!gone) { + if (f.revision == f.saved_revision or !(panes.Output.fileTraits(f.output).saves or scratch)) continue; + if (f.output != null and f.content.len < 100) continue; + // Dirty by a rename alone is no text to lose, as get says (ctl.zig). + // Nor is a buffer never edited at all: its first edit is what keeps + // the text it had (File.zig), so with none there is nothing to lose. + const saved = f.saved_hash orelse continue; + if (std.hash.Wyhash.hash(0, f.content) == saved) continue; + } if (pane.discard_warned) |w| if (w.revision == f.revision and w.by == asking) continue; pane.discard_warned = .{ .revision = f.revision, .by = asking }; // Scratches share a name: each is named with its pane's serial too, diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index b93d83c3..ebb85609 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -2201,6 +2201,20 @@ test "+Unsaved drops a pane once it has closed" { try testing.expect(std.mem.indexOf(u8, list.file.?.content, "/tmp/pardes-unsaved-a.txt: Modified") == null); } +test "a buffer whose file was deleted on disk is dirty, and Del asks first" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const serial = serialOf(p); + try testing.expectEqualStrings("0", std.mem.trimEnd(u8, rd(p, Node.of(serial, .dirty), 0, 8).bytes, "\n")); + pardes.panes.File.deleted(p, @intCast(p.paneBySerial(serial).?)); + try testing.expectEqualStrings("1", std.mem.trimEnd(u8, rd(p, Node.of(serial, .dirty), 0, 8).bytes, "\n")); + try testing.expectEqual(Status.err, wr(p, Node.of(serial, .ctl), "Del\n").reply.status); + try testing.expect(p.paneBySerial(serial) != null); + _ = wr(p, Node.of(serial, .ctl), "Del\n"); + p.sync(); + try testing.expect(p.paneBySerial(serial) == null); +} + test "every EINVAL a write gets says why, in its err record too; DEL is a control character in a line" { const p = try withFile(testing.allocator, "x\n"); defer p.deinit(); diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index f90a83d8..d48c8e71 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -125,7 +125,8 @@ pub fn tagOf(p: *Pardes, pane: *Pane) []const u8 { pub fn dirtyOf(pane: *const Pane) bool { const f = if (pane.file) |*x| x else return false; if (!panes.Output.fileTraits(f.output).saves) return false; - return f.revision != f.saved_revision; + // Its file deleted on disk: the text is only here now, as acme counts it. + return f.revision != f.saved_revision or f.disk_gone; } /// The pane's name: its file, its document, or a terminal's directory. diff --git a/src/tagline.zig b/src/tagline.zig index 5f95b2a0..df093925 100644 --- a/src/tagline.zig +++ b/src/tagline.zig @@ -30,7 +30,7 @@ const dirty_marker = " *"; /// A file with changes it has not saved. pub fn dirty(pane: *const panes.Pane) bool { const f = pane.file orelse return false; - return panes.Output.fileTraits(f.output).saves and f.revision != f.saved_revision; + return panes.Output.fileTraits(f.output).saves and (f.revision != f.saved_revision or f.disk_gone); } pub const legacy_pane_tail = " Del"; pub const legacy_file_pane_tail = " Save Del"; |
