fix: watch all directories within the project tree

This commit is contained in:
CJ van den Berg 2026-02-20 10:23:15 +01:00
parent 583344d413
commit 348c2055da
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
3 changed files with 40 additions and 10 deletions

View file

@ -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;

View file

@ -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 {};

View file

@ -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)