From cb93a76c114a36ff7693501b33de7b9441c5689d Mon Sep 17 00:00:00 2001 From: julia Date: Wed, 8 Jan 2025 13:34:53 +1100 Subject: [PATCH 1/6] bug fixes syntax/compilation errors in `removeFile`, and remove `offset` since its information is stored in `self.paths.items.len` --- README.md | 4 ++-- examples/basic.zig | 3 +++ src/watchers/linux.zig | 17 +++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1625113..e9bf085 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # fzwatch A lightweight and cross-platform file watcher for your Zig projects. -> [!NOTE] +> [!NOTE] > This project exists to support [fancy-cat](https://github.com/freref/fancy-cat) and has limited features. ## Instructions @@ -15,7 +15,7 @@ A basic example can be found under [examples](./examples/basic.zig). The API is pub const Event = enum { modified }; pub const Callback = fn (context: *anyopaque, event: Event) void; pub const Opts = struct { latency: f16 = 1.0 }; - + pub fn init(allocator: std.mem.Allocator) !Watcher; pub fn deinit(self: *Watcher) void; pub fn addFile(self: *Watcher, path: []const u8) !void; diff --git a/examples/basic.zig b/examples/basic.zig index 575fac8..3c0eece 100644 --- a/examples/basic.zig +++ b/examples/basic.zig @@ -19,6 +19,9 @@ pub fn main() !void { defer watcher.deinit(); try watcher.addFile("README.md"); + try watcher.addFile("build.zig"); + try watcher.addFile("build.zig.zon"); + try watcher.removeFile("build.zig.zon"); watcher.setCallback(callback, null); const thread = try std.Thread.spawn(.{}, watcherThread, .{&watcher}); diff --git a/src/watchers/linux.zig b/src/watchers/linux.zig index b38e028..bc5a69b 100644 --- a/src/watchers/linux.zig +++ b/src/watchers/linux.zig @@ -5,7 +5,6 @@ pub const LinuxWatcher = struct { allocator: std.mem.Allocator, inotify_fd: i32, paths: std.ArrayList([]const u8), - offset: usize, callback: ?*const interfaces.Callback, running: bool, context: ?*anyopaque, @@ -18,7 +17,6 @@ pub const LinuxWatcher = struct { .allocator = allocator, .inotify_fd = @intCast(fd), .paths = std.ArrayList([]const u8).init(allocator), - .offset = 1, .callback = null, .running = false, .context = null, @@ -42,11 +40,10 @@ pub const LinuxWatcher = struct { } pub fn removeFile(self: *LinuxWatcher, path: []const u8) !void { - for (0.., self.paths) |idx, mem_path| { - if (mem_path == path) { - _ = std.posix.inotify_rm_watch(self.inotify_fd, idx - self.offset); - try self.paths.items().remove(idx); - self.offset += 1; + for (0.., self.paths.items) |idx, mem_path| { + if (std.mem.eql(u8, mem_path, path)) { + std.posix.inotify_rm_watch(self.inotify_fd, @intCast(idx + @intFromBool(idx == self.paths.items.len))); + _ = self.paths.swapRemove(idx); return; } } @@ -84,11 +81,11 @@ pub const LinuxWatcher = struct { // So we have to re-add the file to the watcher if (ev.mask & std.os.linux.IN.IGNORED != 0) { const wd_usize = @as(usize, @intCast(@max(0, ev.wd))); - if (wd_usize < self.offset) { + if (wd_usize > self.paths.items.len) { return error.InvalidWatchDescriptor; } - const index = wd_usize - self.offset; - try self.addFile(self.paths.items[index]); + // TODO: remove previous buffer + try self.addFile(self.paths.items[wd_usize - 1]); if (self.callback) |callback| { callback(self.context, interfaces.Event.modified); } From 14335084a52b7d841bb2c51d70e9be1b491f5b1e Mon Sep 17 00:00:00 2001 From: julia Date: Wed, 8 Jan 2025 16:06:33 +1100 Subject: [PATCH 2/6] idea to update wd for prev files doesnt compile but the idea is that it would be more safe to update the watch descriptors of the previous files so that we can check valid descriptors in the event loop --- examples/basic.zig | 6 ++-- src/watchers/interfaces.zig | 6 +++- src/watchers/linux.zig | 64 ++++++++++++++++++++++++++----------- src/watchers/macos.zig | 5 ++- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/examples/basic.zig b/examples/basic.zig index 3c0eece..c1e3ad0 100644 --- a/examples/basic.zig +++ b/examples/basic.zig @@ -3,8 +3,8 @@ const fzwatch = @import("fzwatch"); fn callback(context: ?*anyopaque, event: fzwatch.Event) void { _ = context; - switch (event) { - .modified => std.debug.print("File was modified!\n", .{}), + switch (event.kind) { + .modified => std.debug.print("File {d} was modified!\n", .{event.item}), } } @@ -19,7 +19,9 @@ pub fn main() !void { defer watcher.deinit(); try watcher.addFile("README.md"); + // try watcher.removeFile("README.md"); try watcher.addFile("build.zig"); + try watcher.removeFile("build.zig"); try watcher.addFile("build.zig.zon"); try watcher.removeFile("build.zig.zon"); watcher.setCallback(callback, null); diff --git a/src/watchers/interfaces.zig b/src/watchers/interfaces.zig index 403016e..30c57db 100644 --- a/src/watchers/interfaces.zig +++ b/src/watchers/interfaces.zig @@ -1,3 +1,7 @@ -pub const Event = enum { modified }; +pub const Event = struct { + kind: enum { modified }, + /// the index into `Watcher.paths.items` which this event came from + item: usize +}; pub const Callback = fn (context: ?*anyopaque, event: Event) void; pub const Opts = struct { latency: f16 = 1.0 }; diff --git a/src/watchers/linux.zig b/src/watchers/linux.zig index bc5a69b..7cff6be 100644 --- a/src/watchers/linux.zig +++ b/src/watchers/linux.zig @@ -5,6 +5,8 @@ pub const LinuxWatcher = struct { allocator: std.mem.Allocator, inotify_fd: i32, paths: std.ArrayList([]const u8), + /// inotify wd offset from removing files + offset: usize, callback: ?*const interfaces.Callback, running: bool, context: ?*anyopaque, @@ -17,6 +19,7 @@ pub const LinuxWatcher = struct { .allocator = allocator, .inotify_fd = @intCast(fd), .paths = std.ArrayList([]const u8).init(allocator), + .offset = 1, .callback = null, .running = false, .context = null, @@ -41,11 +44,30 @@ pub const LinuxWatcher = struct { pub fn removeFile(self: *LinuxWatcher, path: []const u8) !void { for (0.., self.paths.items) |idx, mem_path| { - if (std.mem.eql(u8, mem_path, path)) { - std.posix.inotify_rm_watch(self.inotify_fd, @intCast(idx + @intFromBool(idx == self.paths.items.len))); - _ = self.paths.swapRemove(idx); - return; + if (!std.mem.eql(u8, mem_path, path)) + continue; + + // need to update wd of all previous files so they are above the offset + // so we just remove and add them back from inotify watch + // TODO: 100% better way to do this + for(0..idx) |i| { + std.posix.inotify_rm_watch(self.inotify_fd, @intCast(idx + self.offset - i)); + + const t = try std.posix.inotify_add_watch( + self.inotify_fd, + self.paths.items[i], + std.os.linux.IN.MODIFY, + ); + + std.log.debug("removed: {d} added {d}", .{idx + self.offset - i, t}); } + + self.offset += idx; + std.posix.inotify_rm_watch(self.inotify_fd, @intCast(idx + self.offset)); + self.offset += 1; + _ = self.paths.orderedRemove(idx); + + return; } } @@ -59,10 +81,10 @@ pub const LinuxWatcher = struct { if (self.paths.items.len == 0) return error.NoFilesToWatch; self.running = true; - var buffer: [4096]u8 align(@alignOf(std.os.linux.inotify_event)) = undefined; + var buffer: [4096]std.os.linux.inotify_event = undefined; while (self.running) { - const length = std.posix.read(self.inotify_fd, &buffer) catch |err| switch (err) { + const length = std.posix.read(self.inotify_fd, std.mem.sliceAsBytes(&buffer)) catch |err| switch (err) { error.WouldBlock => { std.time.sleep(@as(u64, @intFromFloat(@as(f64, opts.latency) * @as(f64, @floatFromInt(std.time.ns_per_s))))); continue; @@ -72,30 +94,34 @@ pub const LinuxWatcher = struct { }, }; - var ptr: [*]u8 = &buffer; - const end_ptr = ptr + @as(usize, @intCast(length)); - - while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) { - const ev = @as(*const std.os.linux.inotify_event, @ptrCast(@alignCast(ptr))); + var i: usize = 0; + while (i < length) : (i += buffer[i].len + @sizeOf(std.os.linux.inotify_event)) { + const ev = buffer[i]; // Editors like vim create temporary files when saving // So we have to re-add the file to the watcher if (ev.mask & std.os.linux.IN.IGNORED != 0) { const wd_usize = @as(usize, @intCast(@max(0, ev.wd))); - if (wd_usize > self.paths.items.len) { + std.log.info("w {d}, l {d}, o {d}", .{wd_usize, self.paths.items.len, self.offset}); + std.log.info("{d}", .{self.paths.items.len}); + if(wd_usize == 0) continue; + if (wd_usize > self.paths.items.len + self.offset or wd_usize < self.offset) return error.InvalidWatchDescriptor; - } - // TODO: remove previous buffer - try self.addFile(self.paths.items[wd_usize - 1]); + + try self.addFile(self.paths.items[wd_usize - self.offset]); if (self.callback) |callback| { - callback(self.context, interfaces.Event.modified); + callback(self.context, .{ + .kind = .modified, + .item = wd_usize - self.offset + }); } } else if (ev.mask & std.os.linux.IN.MODIFY != 0) { if (self.callback) |callback| { - callback(self.context, interfaces.Event.modified); + callback(self.context, .{ + .kind = .modified, + .item = @as(usize, @intCast(ev.wd)) - self.offset + }); } } - - ptr = @alignCast(ptr + @sizeOf(std.os.linux.inotify_event) + ev.len); } } } diff --git a/src/watchers/macos.zig b/src/watchers/macos.zig index de600cd..c190820 100644 --- a/src/watchers/macos.zig +++ b/src/watchers/macos.zig @@ -87,7 +87,10 @@ pub const MacosWatcher = struct { while (i < numEvents) : (i += 1) { const flags = eventFlags[i]; if (flags & c.kFSEventStreamEventFlagItemModified != 0) { - self.callback.?(self.context, interfaces.Event.modified); + self.callback.?(self.context, .{ + .kind = .modified, + .item = i + }); } } } From f077375f20f0bbf7830752e1490140f213943806 Mon Sep 17 00:00:00 2001 From: julia Date: Wed, 8 Jan 2025 18:41:29 +1100 Subject: [PATCH 3/6] using an `old` hashmap i think its safe not to even check if the wd is below offset, it would be much faster and if inotify is sending it than it must already be valid. ill leave this commit as the basis for an idea though --- examples/basic.zig | 4 +- src/watchers/linux.zig | 98 +++++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/examples/basic.zig b/examples/basic.zig index c1e3ad0..3e8c3e3 100644 --- a/examples/basic.zig +++ b/examples/basic.zig @@ -21,9 +21,9 @@ pub fn main() !void { try watcher.addFile("README.md"); // try watcher.removeFile("README.md"); try watcher.addFile("build.zig"); - try watcher.removeFile("build.zig"); + // try watcher.removeFile("build.zig"); try watcher.addFile("build.zig.zon"); - try watcher.removeFile("build.zig.zon"); + // try watcher.removeFile("build.zig.zon"); watcher.setCallback(callback, null); const thread = try std.Thread.spawn(.{}, watcherThread, .{&watcher}); diff --git a/src/watchers/linux.zig b/src/watchers/linux.zig index 7cff6be..601e38f 100644 --- a/src/watchers/linux.zig +++ b/src/watchers/linux.zig @@ -3,10 +3,17 @@ const interfaces = @import("interfaces.zig"); pub const LinuxWatcher = struct { allocator: std.mem.Allocator, - inotify_fd: i32, paths: std.ArrayList([]const u8), - /// inotify wd offset from removing files - offset: usize, + inotify: struct { + fd: i32, + /// inotify wd offset from removing files + offset: usize, + /// the old watch descriptors that would have a lower wd than `offset` + /// making them unsafe to normally check. by keeping them here we know that + /// these specific low wd's are safe + /// maps wd to offset at the time of population + old: std.AutoHashMap(i32, i32) + }, callback: ?*const interfaces.Callback, running: bool, context: ?*anyopaque, @@ -17,9 +24,12 @@ pub const LinuxWatcher = struct { return LinuxWatcher{ .allocator = allocator, - .inotify_fd = @intCast(fd), .paths = std.ArrayList([]const u8).init(allocator), - .offset = 1, + .inotify = .{ + .fd = @intCast(fd), + .offset = 1, + .old = std.AutoHashMap(i32, i32).init(allocator) + }, .callback = null, .running = false, .context = null, @@ -29,16 +39,19 @@ pub const LinuxWatcher = struct { pub fn deinit(self: *LinuxWatcher) void { self.stop(); self.paths.deinit(); - std.posix.close(self.inotify_fd); + self.inotify.old.deinit(); + std.posix.close(self.inotify.fd); } pub fn addFile(self: *LinuxWatcher, path: []const u8) !void { - _ = try std.posix.inotify_add_watch( - self.inotify_fd, + const wd = try std.posix.inotify_add_watch( + self.inotify.fd, path, std.os.linux.IN.MODIFY, ); + try self.inotify.old.putNoClobber(wd, @intCast(self.inotify.offset)); + try self.paths.append(path); } @@ -47,26 +60,13 @@ pub const LinuxWatcher = struct { if (!std.mem.eql(u8, mem_path, path)) continue; - // need to update wd of all previous files so they are above the offset - // so we just remove and add them back from inotify watch - // TODO: 100% better way to do this - for(0..idx) |i| { - std.posix.inotify_rm_watch(self.inotify_fd, @intCast(idx + self.offset - i)); - - const t = try std.posix.inotify_add_watch( - self.inotify_fd, - self.paths.items[i], - std.os.linux.IN.MODIFY, - ); - - std.log.debug("removed: {d} added {d}", .{idx + self.offset - i, t}); - } - - self.offset += idx; - std.posix.inotify_rm_watch(self.inotify_fd, @intCast(idx + self.offset)); - self.offset += 1; + _ = std.posix.inotify_rm_watch(self.inotify.fd, @intCast(idx + self.inotify.offset)); + self.inotify.offset += 1; _ = self.paths.orderedRemove(idx); + // self.inotify.old.clearRetainingCapacity(); + // for(0..idx) |i| try self.inotify.old.putNoClobber(@intCast(i), 0); + return; } } @@ -84,7 +84,7 @@ pub const LinuxWatcher = struct { var buffer: [4096]std.os.linux.inotify_event = undefined; while (self.running) { - const length = std.posix.read(self.inotify_fd, std.mem.sliceAsBytes(&buffer)) catch |err| switch (err) { + const length = std.posix.read(self.inotify.fd, std.mem.sliceAsBytes(&buffer)) catch |err| switch (err) { error.WouldBlock => { std.time.sleep(@as(u64, @intFromFloat(@as(f64, opts.latency) * @as(f64, @floatFromInt(std.time.ns_per_s))))); continue; @@ -94,34 +94,32 @@ pub const LinuxWatcher = struct { }, }; + // in bytes var i: usize = 0; while (i < length) : (i += buffer[i].len + @sizeOf(std.os.linux.inotify_event)) { const ev = buffer[i]; + if(ev.wd < self.inotify.offset) { + try if(self.inotify.old.get(ev.wd)) |offset| { + std.log.info("{d}, {d}", .{offset, ev.wd}); + try self.addFile(self.paths.items[@intCast(ev.wd - offset)]); + } + else if(ev.wd == 0) {continue;} + else error.InvalidWatchDescriptor; + } else if (ev.wd > self.paths.items.len + self.inotify.offset) + return error.InvalidWatchDescriptor; + + const index = @as(usize, @intCast(@max(0, ev.wd))) - self.inotify.offset; // Editors like vim create temporary files when saving // So we have to re-add the file to the watcher - if (ev.mask & std.os.linux.IN.IGNORED != 0) { - const wd_usize = @as(usize, @intCast(@max(0, ev.wd))); - std.log.info("w {d}, l {d}, o {d}", .{wd_usize, self.paths.items.len, self.offset}); - std.log.info("{d}", .{self.paths.items.len}); - if(wd_usize == 0) continue; - if (wd_usize > self.paths.items.len + self.offset or wd_usize < self.offset) - return error.InvalidWatchDescriptor; - - try self.addFile(self.paths.items[wd_usize - self.offset]); - if (self.callback) |callback| { - callback(self.context, .{ - .kind = .modified, - .item = wd_usize - self.offset - }); - } - } else if (ev.mask & std.os.linux.IN.MODIFY != 0) { - if (self.callback) |callback| { - callback(self.context, .{ - .kind = .modified, - .item = @as(usize, @intCast(ev.wd)) - self.offset - }); - } - } + if (ev.mask & std.os.linux.IN.IGNORED == 0 and ev.mask & std.os.linux.IN.MODIFY == 0) + continue; + + if(ev.mask & std.os.linux.IN.IGNORED != 0) + try self.addFile(self.paths.items[index]); + if (self.callback) |callback| callback(self.context, .{ + .kind = .modified, + .item = index + }); } } } From 3070b7a682bc58272bf688ba6a28740fb8b8800b Mon Sep 17 00:00:00 2001 From: julia Date: Wed, 8 Jan 2025 18:46:40 +1100 Subject: [PATCH 4/6] ignore old watch descriptors creates a bit of a bug where the wd will only be updated once another file is modified --- examples/basic.zig | 2 +- src/watchers/linux.zig | 26 +++++--------------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/examples/basic.zig b/examples/basic.zig index 3e8c3e3..e68dbcf 100644 --- a/examples/basic.zig +++ b/examples/basic.zig @@ -23,7 +23,7 @@ pub fn main() !void { try watcher.addFile("build.zig"); // try watcher.removeFile("build.zig"); try watcher.addFile("build.zig.zon"); - // try watcher.removeFile("build.zig.zon"); + try watcher.removeFile("build.zig.zon"); watcher.setCallback(callback, null); const thread = try std.Thread.spawn(.{}, watcherThread, .{&watcher}); diff --git a/src/watchers/linux.zig b/src/watchers/linux.zig index 601e38f..fe3302f 100644 --- a/src/watchers/linux.zig +++ b/src/watchers/linux.zig @@ -8,11 +8,6 @@ pub const LinuxWatcher = struct { fd: i32, /// inotify wd offset from removing files offset: usize, - /// the old watch descriptors that would have a lower wd than `offset` - /// making them unsafe to normally check. by keeping them here we know that - /// these specific low wd's are safe - /// maps wd to offset at the time of population - old: std.AutoHashMap(i32, i32) }, callback: ?*const interfaces.Callback, running: bool, @@ -28,7 +23,6 @@ pub const LinuxWatcher = struct { .inotify = .{ .fd = @intCast(fd), .offset = 1, - .old = std.AutoHashMap(i32, i32).init(allocator) }, .callback = null, .running = false, @@ -39,19 +33,16 @@ pub const LinuxWatcher = struct { pub fn deinit(self: *LinuxWatcher) void { self.stop(); self.paths.deinit(); - self.inotify.old.deinit(); std.posix.close(self.inotify.fd); } pub fn addFile(self: *LinuxWatcher, path: []const u8) !void { - const wd = try std.posix.inotify_add_watch( + _ = try std.posix.inotify_add_watch( self.inotify.fd, path, std.os.linux.IN.MODIFY, ); - try self.inotify.old.putNoClobber(wd, @intCast(self.inotify.offset)); - try self.paths.append(path); } @@ -64,9 +55,6 @@ pub const LinuxWatcher = struct { self.inotify.offset += 1; _ = self.paths.orderedRemove(idx); - // self.inotify.old.clearRetainingCapacity(); - // for(0..idx) |i| try self.inotify.old.putNoClobber(@intCast(i), 0); - return; } } @@ -98,14 +86,10 @@ pub const LinuxWatcher = struct { var i: usize = 0; while (i < length) : (i += buffer[i].len + @sizeOf(std.os.linux.inotify_event)) { const ev = buffer[i]; - if(ev.wd < self.inotify.offset) { - try if(self.inotify.old.get(ev.wd)) |offset| { - std.log.info("{d}, {d}", .{offset, ev.wd}); - try self.addFile(self.paths.items[@intCast(ev.wd - offset)]); - } - else if(ev.wd == 0) {continue;} - else error.InvalidWatchDescriptor; - } else if (ev.wd > self.paths.items.len + self.inotify.offset) + + if(ev.wd == 0) {continue;} + else if(ev.wd < self.inotify.offset) {continue;} + else if (ev.wd > self.paths.items.len + self.inotify.offset) return error.InvalidWatchDescriptor; const index = @as(usize, @intCast(@max(0, ev.wd))) - self.inotify.offset; From cebc0d30a7cd1bdecf4a94eb86a8c1b73f016fd6 Mon Sep 17 00:00:00 2001 From: julia Date: Fri, 10 Jan 2025 14:13:39 +1100 Subject: [PATCH 5/6] cleanup --- README.md | 2 +- examples/context.zig | 2 +- src/watchers/interfaces.zig | 2 +- src/watchers/linux.zig | 15 +++++++-------- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e9bf085..f981c22 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ zig build run- ### Usage A basic example can be found under [examples](./examples/basic.zig). The API is defined as follows: ```zig -pub const Event = enum { modified }; +pub const Event = struct { kind: enum { modified }, item: usize }; pub const Callback = fn (context: *anyopaque, event: Event) void; pub const Opts = struct { latency: f16 = 1.0 }; diff --git a/examples/context.zig b/examples/context.zig index 049a764..4787dd0 100644 --- a/examples/context.zig +++ b/examples/context.zig @@ -8,7 +8,7 @@ const Object = struct { thread: ?std.Thread, fn callback(context: ?*anyopaque, event: fzwatch.Event) void { - switch (event) { + switch (event.kind) { .modified => { const to_increment: *usize = @as(*usize, @ptrCast(@alignCast(context.?))); to_increment.* += 1; diff --git a/src/watchers/interfaces.zig b/src/watchers/interfaces.zig index 30c57db..45ad688 100644 --- a/src/watchers/interfaces.zig +++ b/src/watchers/interfaces.zig @@ -4,4 +4,4 @@ pub const Event = struct { item: usize }; pub const Callback = fn (context: ?*anyopaque, event: Event) void; -pub const Opts = struct { latency: f16 = 1.0 }; +pub const Opts = struct { latency: f16 = 1.0, notify: bool = false}; diff --git a/src/watchers/linux.zig b/src/watchers/linux.zig index fe3302f..54cbc72 100644 --- a/src/watchers/linux.zig +++ b/src/watchers/linux.zig @@ -10,8 +10,8 @@ pub const LinuxWatcher = struct { offset: usize, }, callback: ?*const interfaces.Callback, - running: bool, context: ?*anyopaque, + running: bool, pub fn init(allocator: std.mem.Allocator) !LinuxWatcher { const fd = try std.posix.inotify_init1(std.os.linux.IN.NONBLOCK); @@ -22,11 +22,11 @@ pub const LinuxWatcher = struct { .paths = std.ArrayList([]const u8).init(allocator), .inotify = .{ .fd = @intCast(fd), - .offset = 1, + .offset = 1, }, .callback = null, - .running = false, .context = null, + .running = false, }; } @@ -87,17 +87,16 @@ pub const LinuxWatcher = struct { while (i < length) : (i += buffer[i].len + @sizeOf(std.os.linux.inotify_event)) { const ev = buffer[i]; - if(ev.wd == 0) {continue;} - else if(ev.wd < self.inotify.offset) {continue;} + if(ev.wd < self.inotify.offset) {continue;} else if (ev.wd > self.paths.items.len + self.inotify.offset) return error.InvalidWatchDescriptor; - const index = @as(usize, @intCast(@max(0, ev.wd))) - self.inotify.offset; - // Editors like vim create temporary files when saving - // So we have to re-add the file to the watcher if (ev.mask & std.os.linux.IN.IGNORED == 0 and ev.mask & std.os.linux.IN.MODIFY == 0) continue; + const index = @as(usize, @intCast(@max(0, ev.wd))) - self.inotify.offset; + // Editors like vim create temporary files when saving + // So we have to re-add the file to the watcher if(ev.mask & std.os.linux.IN.IGNORED != 0) try self.addFile(self.paths.items[index]); if (self.callback) |callback| callback(self.context, .{ From acb1b516f625542cd80289697dd4fcdc29369dd9 Mon Sep 17 00:00:00 2001 From: julia Date: Sat, 11 Jan 2025 13:29:58 +1100 Subject: [PATCH 6/6] oops remove artifact from poll testing --- src/watchers/interfaces.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/watchers/interfaces.zig b/src/watchers/interfaces.zig index 45ad688..30c57db 100644 --- a/src/watchers/interfaces.zig +++ b/src/watchers/interfaces.zig @@ -4,4 +4,4 @@ pub const Event = struct { item: usize }; pub const Callback = fn (context: ?*anyopaque, event: Event) void; -pub const Opts = struct { latency: f16 = 1.0, notify: bool = false}; +pub const Opts = struct { latency: f16 = 1.0 };