refactor: add watches for newly created directories

This commit is contained in:
CJ van den Berg 2026-02-20 20:36:17 +01:00
parent 8526838230
commit 5d5e1f77e8
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
2 changed files with 43 additions and 3 deletions

View file

@ -13,6 +13,9 @@ pub const EventType = enum {
created,
modified,
deleted,
/// A new directory was created inside a watched directory. The receiver
/// should call watch() on the path to get events for files created in it.
dir_created,
/// Only produced on macOS and Windows where the OS gives no pairing info.
/// On Linux, paired renames are emitted as a { "FW", "rename", from, to } message instead.
renamed,
@ -205,7 +208,7 @@ const INotifyBackend = struct {
try parent.send(.{ "FW", "change", full_path, EventType.deleted });
} else {
const event_type: EventType = if (ev.mask & IN.CREATE != 0)
.created
if (ev.mask & IN.ISDIR != 0) .dir_created else .created
else if (ev.mask & (IN.DELETE | IN.DELETE_SELF) != 0)
.deleted
else if (ev.mask & (IN.MODIFY | IN.CLOSE_WRITE) != 0)
@ -233,6 +236,7 @@ const FSEventsBackend = struct {
const kFSEventStreamEventFlagItemRemoved: u32 = 0x00000200;
const kFSEventStreamEventFlagItemRenamed: u32 = 0x00000800;
const kFSEventStreamEventFlagItemModified: u32 = 0x00001000;
const kFSEventStreamEventFlagItemIsDir: u32 = 0x00020000;
const kFSEventStreamEventIdSinceNow: u64 = 0xFFFFFFFFFFFFFFFF;
const kCFStringEncodingUTF8: u32 = 0x08000100;
@ -372,7 +376,7 @@ const FSEventsBackend = struct {
const event_type: EventType = if (flags & kFSEventStreamEventFlagItemRemoved != 0)
.deleted
else if (flags & kFSEventStreamEventFlagItemCreated != 0)
.created
if (flags & kFSEventStreamEventFlagItemIsDir != 0) .dir_created else .created
else if (flags & kFSEventStreamEventFlagItemRenamed != 0)
.renamed
else if (flags & kFSEventStreamEventFlagItemModified != 0)
@ -530,6 +534,19 @@ const KQueueBackend = struct {
try current.put(allocator, name, {});
}
// Emit dir_created for new subdirectories outside the lock (no snapshot involvement).
var dir2 = std.fs.openDirAbsolute(dir_path, .{ .iterate = true }) catch return;
defer dir2.close();
var dir_iter = dir2.iterate();
while (try dir_iter.next()) |entry| {
if (entry.kind != .directory) continue;
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
const full_path = std.fmt.bufPrint(&path_buf, "{s}/{s}", .{ dir_path, entry.name }) catch continue;
// Only emit if not already watched.
if (!snapshots.contains(full_path))
try parent.send(.{ "FW", "change", full_path, EventType.dir_created });
}
snapshots_mutex.lock();
defer snapshots_mutex.unlock();
@ -654,6 +671,7 @@ const WindowsBackend = struct {
dwCompletionKey: windows.ULONG_PTR,
lpOverlapped: ?*windows.OVERLAPPED,
) callconv(.winapi) windows.BOOL;
pub extern "kernel32" fn GetFileAttributesW(lpFileName: [*:0]const windows.WCHAR) callconv(.winapi) windows.DWORD;
};
iocp: windows.HANDLE,
@ -764,8 +782,25 @@ const WindowsBackend = struct {
offset += info.NextEntryOffset;
continue;
};
// Distinguish files from directories.
const is_dir = blk: {
var full_path_w: [std.fs.max_path_bytes]windows.WCHAR = undefined;
const len = std.unicode.utf8ToUtf16Le(&full_path_w, full_path) catch break :blk false;
full_path_w[len] = 0;
const attrs = win32.GetFileAttributesW(full_path_w[0..len :0]);
const INVALID: windows.DWORD = 0xFFFFFFFF;
const FILE_ATTRIBUTE_DIRECTORY: windows.DWORD = 0x10;
break :blk attrs != INVALID and (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0;
};
const adjusted_event_type: EventType = if (is_dir and event_type == .created)
.dir_created
else if (is_dir) { // Other directory events (modified, deleted, renamed), skip.
if (info.NextEntryOffset == 0) break;
offset += info.NextEntryOffset;
continue;
} else event_type;
watches_mutex.unlock();
parent.send(.{ "FW", "change", full_path, event_type }) catch {
parent.send(.{ "FW", "change", full_path, adjusted_event_type }) catch {
watches_mutex.lock();
break;
};

View file

@ -594,12 +594,17 @@ const Process = struct {
fn handle_file_watch_event(self: *Process, abs_path: []const u8, event_type: file_watcher.EventType) void {
std.log.debug("file_watch_event: {s} {s}", .{ @tagName(event_type), abs_path });
if (event_type == .dir_created) {
file_watcher.watch(abs_path) catch |e| self.logger.err("file_watcher.watch(dir_created)", e);
return;
}
if (self.project_for_path(abs_path)) |match| {
switch (event_type) {
.created => match.project.file_added(match.rel_path) catch |e| self.logger.err("file_watcher.file_added", e),
.modified => match.project.file_modified(match.rel_path),
.deleted => match.project.file_deleted(match.rel_path),
.renamed => match.project.file_deleted(match.rel_path),
.dir_created => unreachable,
}
} else {
self.parent.send(.{ "FW", "change", abs_path, event_type }) catch {};