summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 21:16:32 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commitf02c10bfa6d02d305ddb6d7a28b12c40035274c7 (patch)
tree1fe0e159cd3d24f0ed67d3358e3d6bcab9741cf7 /src
parenta2c719779c9ce0f676fb159135675df6275e77bf (diff)
downloadpardes-f02c10bfa6d02d305ddb6d7a28b12c40035274c7.tar.gz
pardes-f02c10bfa6d02d305ddb6d7a28b12c40035274c7.zip
A Restore brings back clean a pane whose file was never on disk, such as Config's new init; only a file that existed when dumped is said deleted
A restored pane whose path was not on disk was marked deleted on disk, so Config's not-yet-written init came back dirty and asked before Del, Exit or Restore. The dump now records whether the file was on disk (older dumps read as yes), and only one that was and is gone is said deleted. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/File.zig31
-rw-r--r--src/dump.zig4
-rw-r--r--src/ninep/ctl.zig10
3 files changed, 42 insertions, 3 deletions
diff --git a/src/File.zig b/src/File.zig
index a89db613..d16d4fc0 100644
--- a/src/File.zig
+++ b/src/File.zig
@@ -127,6 +127,7 @@ pub fn dumpPane(
.content = file.content,
.content_b64 = try dump.encodeBytes(arena, file.content),
.dirty = file.revision != file.saved_revision,
+ .on_disk = onDisk(file.path),
.origin = origin,
.origin_arg = origin_arg,
.mini_source = if (file.mini) |mini| mini.source else "",
@@ -220,6 +221,24 @@ fn restoreLocationRows(gpa: std.mem.Allocator, content: []const u8, saved: []con
return rows;
}
+test "a clean pane whose file was never on disk comes back clean from a Restore; one whose file has gone is said deleted" {
+ if (comptime !pardes.hosted) return;
+ for ([_]bool{ false, true }) |was_on_disk| {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ while (p.nextEffect()) |_| {}
+ const id = p.freeSlot().?;
+ const pane = try restore(p, id, .{
+ .kind = .file,
+ .tag = "",
+ .body = "",
+ .file = .{ .path = "/tmp/pardes-never-written/init", .content = "", .on_disk = was_on_disk },
+ });
+ try std.testing.expectEqual(was_on_disk, pane.file.?.disk_gone);
+ try std.testing.expectEqual(pane.file.?.saved_revision, pane.file.?.revision);
+ }
+}
+
test "location metadata restore rejects offsets and invalid syntax bytes" {
const gpa = std.testing.allocator;
var row: dump.LocationRow = .{
@@ -898,11 +917,19 @@ pub fn restore(p: *Pardes, id: usize, src: dump.Pane) !*Pane {
// Its file gone from disk since the dump: its text is nowhere else now,
// so it comes back as one deleted on disk, dirty, and Del, Exit and
// Restore ask before throwing it away.
- if (comptime pardes.hosted) if (output == null) if (filesystem.localPath(path)) |local|
- if (@import("exec.zig").kindOf(local) == null) deleted(p, @intCast(id));
+ // One never on disk (Config's new file, a name not yet saved) is
+ // restored as it was: clean, and nothing deleted.
+ if (output == null and saved.on_disk and !onDisk(path)) deleted(p, @intCast(id));
return pane;
}
+/// Whether `path` names a file on this host's disk now.
+fn onDisk(path: []const u8) bool {
+ if (comptime !pardes.hosted) return false;
+ const local = filesystem.localPath(path) orelse return false;
+ return @import("exec.zig").kindOf(local) != null;
+}
+
pub fn deinit(p: *Pardes, pane: *Pane, file: *State) void {
if (file.output == null) for (p.panes, 0..) |slot, id| {
if (slot == pane) p.emit(.{ .watch = .{ .pane = @intCast(id), .on = false } });
diff --git a/src/dump.zig b/src/dump.zig
index 4194f628..983d5894 100644
--- a/src/dump.zig
+++ b/src/dump.zig
@@ -209,6 +209,10 @@ pub const File = struct {
content: []const u8 = "",
content_b64: []const u8 = "",
dirty: bool = false,
+ /// Its file was on disk when dumped: one gone by the Restore is said
+ /// deleted, one never written (Config's new file) is not. True in
+ /// older dumps.
+ on_disk: bool = true,
tree_context: bool = false,
location_rows: []const LocationRow = &.{},
// Builtin names remain stable when enum ordinals change.
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index a14b1efb..63f89cdf 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -2691,8 +2691,16 @@ test "a restored pane whose file is gone from disk comes back dirty, and Del ask
if (comptime !pardes.hosted) return error.SkipZigTest;
const p = try withFile(testing.allocator, "kept text\n");
defer p.deinit();
- try pane_files.nameBuffer(p, 0, "/tmp/pardes-restore-gone-xyz/f.txt", false);
+ // On disk when dumped, gone by the Restore.
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "f.txt", .data = "kept text\n" });
+ var dir_buf: [4096]u8 = undefined;
+ const dir = dir_buf[0..try tmp.dir.realPath(testing.io, &dir_buf)];
+ var path_buf: [4200]u8 = undefined;
+ try pane_files.nameBuffer(p, 0, try std.fmt.bufPrint(&path_buf, "{s}/f.txt", .{dir}), false);
try pardes.dump.dumpState(p);
+ try tmp.dir.deleteFile(testing.io, "f.txt");
const restored = try pardes.dump.restore(p, p.dump_out.?, "/tmp/pardes.dump.zon");
defer restored.deinit();
const again = restored.panes[restored.paneBySerial(restored.panes[0].?.serial).?].?;