summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 13:41:07 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit066f25a8cee7f5c63e991a9857667c346c6b75df (patch)
treed629528aee40b993f989693ca456005bac71aef3
parent164da9fc0a7ab3c7ed39ea0a2dd6bbff3c36cdaf (diff)
downloadpardes-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]>
-rw-r--r--docs/fs.md4
-rw-r--r--src/builtins.zig19
-rw-r--r--src/ninep/ctl.zig14
-rw-r--r--src/ninep/pane.zig3
-rw-r--r--src/tagline.zig2
-rw-r--r--test/snapshots/lookloaded.golden2
6 files changed, 33 insertions, 11 deletions
diff --git a/docs/fs.md b/docs/fs.md
index c72c0131..088d1c92 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -747,7 +747,9 @@ written to `addr` that goes from the current one (`.`, `+1`, `-/re/`), which
there is none of, until an address that stands alone (`2`, `#0`, `/re/`) is.
The three flag files `dirty`, `mark` and `scroll` read `0` or `1` and take
-`0` or `1`: whether the buffer differs from its file, whether a write pushes
+`0` or `1`: whether the buffer differs from its file (a file deleted on
+disk counts, as in acme: its text is only here now, and Del, Exit and the
+rest ask first), whether a write pushes
an undo point (writing `1` pushes one now), and whether a write scrolls the
pane. The writes of one open of `data`, `xdata` or `body` are one undo
step while `mark` is 1 (so `printf 'a\nb\n' > data` is one, though a
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";
diff --git a/test/snapshots/lookloaded.golden b/test/snapshots/lookloaded.golden
index 6644a430..70b42102 100644
--- a/test/snapshots/lookloaded.golden
+++ b/test/snapshots/lookloaded.golden
@@ -22,7 +22,7 @@
| big.txt
== snap removed grid=100x31 cursor=54,8
|6: rm big.txt; echo GO''NE
-|7: /tmp/pardes-snap/lookloaded/cwd/big.txt Save Tt GONE
+|7: * /tmp/pardes-snap/lookloaded/cwd/big.txt Save Tt GONE
== snap look-noFS grid=100x31 cursor=14,13
|8: y Collapse Del big.txt:10
|9: 6 line 6