From 348c2055da23e9741b2e506a621398e076318c43 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 20 Feb 2026 10:23:15 +0100 Subject: [PATCH] fix: watch all directories within the project tree --- src/Project.zig | 5 +++++ src/project_manager.zig | 13 +++++++++++++ src/walk_tree.zig | 32 ++++++++++++++++++++++---------- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/Project.zig b/src/Project.zig index 6103d11..7ea611c 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -587,6 +587,10 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query_: []c return @min(max, matches.items.len); } +fn walk_tree_dir_callback(parent: tp.pid_ref, root_path: []const u8, dir_path: []const u8) error{Exit}!void { + try parent.send(.{ "walk_tree_dir", root_path, dir_path }); +} + fn walk_tree_entry_callback(parent: tp.pid_ref, root_path: []const u8, file_path: []const u8, mtime_high: i64, mtime_low: i64) error{Exit}!void { const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path); try parent.send(.{ "walk_tree_entry", root_path, file_path, mtime_high, mtime_low, file_type, file_icon, file_color }); @@ -2830,6 +2834,7 @@ fn start_walker(self: *Self) void { .follow_directory_symlinks = tp.env.get().is("follow_directory_symlinks"), .maximum_symlink_depth = @intCast(tp.env.get().num("maximum_symlink_depth")), .log_ignored_links = tp.env.get().is("log_ignored_links"), + .dir_callback = walk_tree_dir_callback, }) catch blk: { self.state.walk_tree = .failed; break :blk null; diff --git a/src/project_manager.zig b/src/project_manager.zig index bd56f5e..68a018c 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -425,6 +425,10 @@ const Process = struct { if (try cbor.match(m.buf, .{ "FW", "change", tp.extract(&path), tp.extract(&event_type) })) { self.handle_file_watch_event(path, event_type); + } else if (try cbor.match(m.buf, .{ "walk_tree_dir", tp.extract(&project_directory), tp.extract(&path) })) { + var abs_buf: [std.fs.max_path_bytes]u8 = undefined; + const abs_path = std.fmt.bufPrint(&abs_buf, "{s}{c}{s}", .{ project_directory, std.fs.path.sep, path }) catch return; + file_watcher.watch(abs_path) catch |e| self.logger.err("file_watcher.watch_dir", e); } else if (try cbor.match(m.buf, .{ "walk_tree_entry", tp.extract(&project_directory), tp.more })) { if (self.projects.get(project_directory)) |project| project.walk_tree_entry(m) catch |e| self.logger.err("walk_tree_entry", e); @@ -440,6 +444,15 @@ const Process = struct { } else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), "blame", tp.more })) { const request: *Project.GitBlameRequest = @ptrFromInt(context); request.project.process_git_response(self.parent.ref(), m) catch |e| self.logger.err("git-blame", e); + } else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), "workspace_files", tp.extract(&path) })) { + const project: *Project = @ptrFromInt(context); + const dir_path = std.fs.path.dirname(path) orelse ""; + if (dir_path.len > 0) blk: { + var abs_buf: [std.fs.max_path_bytes]u8 = undefined; + const abs_path = std.fmt.bufPrint(&abs_buf, "{s}{c}{s}", .{ project.name, std.fs.path.sep, dir_path }) catch break :blk; + file_watcher.watch(abs_path) catch |e| self.logger.err("file_watcher.watch_dir", e); + } + project.process_git(self.parent.ref(), m) catch {}; } else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), tp.more })) { const project: *Project = @ptrFromInt(context); project.process_git(self.parent.ref(), m) catch {}; diff --git a/src/walk_tree.zig b/src/walk_tree.zig index 3565cee..40e716b 100644 --- a/src/walk_tree.zig +++ b/src/walk_tree.zig @@ -9,11 +9,13 @@ const OutOfMemoryError = error{OutOfMemory}; pub const EntryCallBack = *const fn (parent: tp.pid_ref, root_path: []const u8, path: []const u8, mtime_high: i64, mtime_low: i64) error{Exit}!void; pub const DoneCallBack = *const fn (parent: tp.pid_ref, root_path: []const u8) error{Exit}!void; +pub const DirCallBack = *const fn (parent: tp.pid_ref, root_path: []const u8, path: []const u8) error{Exit}!void; pub const Options = struct { follow_directory_symlinks: bool = false, maximum_symlink_depth: usize = 1, log_ignored_links: bool = false, + dir_callback: ?DirCallBack = null, }; pub fn start(a_: std.mem.Allocator, root_path_: []const u8, entry_handler: EntryCallBack, done_handler: DoneCallBack, options: Options) (SpawnError || std.fs.Dir.OpenError)!tp.pid { @@ -78,7 +80,13 @@ pub fn start(a_: std.mem.Allocator, root_path_: []const u8, entry_handler: Entry } fn next(self: *tree_walker) !void { - if (try self.walker.next()) |path| { + if (try self.walker.next()) |entry| { + if (entry.kind == .dir) { + if (self.options.dir_callback) |cb| + cb(self.parent.ref(), self.root_path, entry.path) catch {}; + return tp.self_pid().send(.{"next"}); + } + const path = entry.path; const stat = self.dir.statFile(path) catch { try self.entry_handler(self.parent.ref(), self.root_path, path, 0, 0); return tp.self_pid().send(.{"next"}); @@ -123,6 +131,8 @@ const FilteredWalker = struct { name_buffer: std.ArrayListUnmanaged(u8), options: Options, + const Kind = enum { file, dir }; + const Entry = struct { path: []const u8, kind: Kind }; const Path = []const u8; const StackItem = struct { @@ -160,7 +170,7 @@ const FilteredWalker = struct { self.name_buffer.deinit(self.allocator); } - fn next(self: *FilteredWalker) OutOfMemoryError!?Path { + fn next(self: *FilteredWalker) OutOfMemoryError!?Entry { while (self.stack.items.len != 0) { var top = &self.stack.items[self.stack.items.len - 1]; var containing = top; @@ -182,17 +192,17 @@ const FilteredWalker = struct { switch (base.kind) { .directory => { _ = try self.next_directory(&base, &top, &containing, top.symlink_depth); - continue; + return .{ .path = self.name_buffer.items, .kind = .dir }; }, - .file => return self.name_buffer.items, + .file => return .{ .path = self.name_buffer.items, .kind = .file }, .sym_link => { if (top.symlink_depth == 0) { if (self.options.log_ignored_links) std.log.warn("TOO MANY LINKS! ignoring symlink: {s}", .{base.name}); continue; } - if (try self.next_sym_link(&base, &top, &containing, top.symlink_depth -| 1)) |file| - return file + if (try self.next_sym_link(&base, &top, &containing, top.symlink_depth -| 1)) |entry| + return entry else continue; }, @@ -229,15 +239,17 @@ const FilteredWalker = struct { return; } - fn next_sym_link(self: *FilteredWalker, base: *const std.fs.Dir.Entry, top: **StackItem, containing: **StackItem, symlink_depth: usize) !?[]const u8 { + fn next_sym_link(self: *FilteredWalker, base: *const std.fs.Dir.Entry, top: **StackItem, containing: **StackItem, symlink_depth: usize) !?Entry { const st = top.*.iter.dir.statFile(base.name) catch return null; switch (st.kind) { .directory => { - if (self.options.follow_directory_symlinks) - _ = try self.next_directory(base, top, containing, symlink_depth); + if (self.options.follow_directory_symlinks) { + try self.next_directory(base, top, containing, symlink_depth); + return .{ .path = self.name_buffer.items, .kind = .dir }; + } return null; }, - .file => return self.name_buffer.items, + .file => return .{ .path = self.name_buffer.items, .kind = .file }, .sym_link => { if (symlink_depth == 0) { if (self.options.log_ignored_links)