summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 06:01:01 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit81ec44ca3b103bdc755e9f5d73a270c05c88617b (patch)
tree397dd624f7168e5d66c36509fb6f9603c1ce9d5f /src
parent6dacd7778b846967fb945297420148e783f48f95 (diff)
downloadpardes-81ec44ca3b103bdc755e9f5d73a270c05c88617b.tar.gz
pardes-81ec44ca3b103bdc755e9f5d73a270c05c88617b.zip
A watched file's deletion wakes the watch at once
The directory mark took CLOSE_WRITE, MOVED_TO and CREATE only, so a file deleted or moved away was noticed only when something else in the directory next changed. The mark takes DELETE and MOVED_FROM too, so changed <serial> deleted comes at once; a test deletes a watched file and finds the watch readable within a second and the pane told. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/file_watch.zig39
1 files changed, 37 insertions, 2 deletions
diff --git a/src/file_watch.zig b/src/file_watch.zig
index 71a4680d..d028488e 100644
--- a/src/file_watch.zig
+++ b/src/file_watch.zig
@@ -225,8 +225,10 @@ fn markDir(fd: c_int, dir_z: [:0]const u8, watches: *const Table) c_int {
switch (builtin.os.tag) {
.linux => {
// CLOSE_WRITE coalesces one writer's writes; MOVED_TO and CREATE
- // cover rename-over and delete-then-recreate saves.
- const mask = linux.IN.CLOSE_WRITE | linux.IN.MOVED_TO | linux.IN.CREATE | linux.IN.ONLYDIR;
+ // cover rename-over and delete-then-recreate saves; DELETE and
+ // MOVED_FROM a file going away, said at once (File.deleted), not
+ // when something else next changes in the directory.
+ const mask = linux.IN.CLOSE_WRITE | linux.IN.MOVED_TO | linux.IN.CREATE | linux.IN.DELETE | linux.IN.MOVED_FROM | linux.IN.ONLYDIR;
return libc.inotify_add_watch(fd, dir_z, mask);
},
.macos => {
@@ -838,3 +840,36 @@ test "theme watch reloads valid ZON and keeps the last theme across a bad save"
try std.testing.expect(!reloadTheme(core, gpa, &watches, false));
try std.testing.expectEqual(@as(?[3]u8, .{ 9, 8, 7 }), core.theme().bg);
}
+
+test "a watched file deleted wakes the watch at once, and its pane says so" {
+ if (comptime !supported or !filesystem.platform_has_fs or builtin.os.tag != .linux) return;
+ const io = std.testing.io;
+ const gpa = std.testing.allocator;
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(io, .{ .sub_path = "watched", .data = "here\n" });
+ var path_buf: [256]u8 = undefined;
+ const path = try std.fmt.bufPrint(&path_buf, ".zig-cache/tmp/{s}/watched", .{tmp.sub_path});
+ const core = try pardes.Pardes.init(gpa, .{ .tty_only = true });
+ defer core.deinit();
+ while (core.nextEffect()) |_| {}
+ const id: u8 = @intCast(core.freeSlot().?);
+ const pane = try pardes.panes.File.open(core, id, path, 0);
+ const fd = init(true);
+ if (fd < 0) return error.NoWatcher;
+ defer _ = libc.close(fd);
+ var watches: Table = @splat(null);
+ defer watchPane(fd, &watches, id, null, pane.serial, .{ .text = 0 });
+ while (core.nextEffect()) |effect| switch (effect) {
+ .watch => |watch| if (watch.pane == id and watch.on) {
+ _ = applyEffect(core, io, fd, &watches, id, watch.on, watch.mode);
+ },
+ else => {},
+ };
+ _ = drain(fd);
+ try tmp.dir.deleteFile(io, "watched");
+ var pfd = [_]libc.pollfd{.{ .fd = fd, .events = libc.POLL.IN, .revents = 0 }};
+ try std.testing.expect(libc.poll(&pfd, 1, 1000) > 0);
+ _ = reloadChanged(core, io, gpa, &watches);
+ try std.testing.expect(pane.file.?.disk_gone);
+}