Compare commits
12 commits
master
...
file-watch
| Author | SHA1 | Date | |
|---|---|---|---|
| 15f471f390 | |||
| c56e713510 | |||
| 7dce0b0572 | |||
| 311209649f | |||
| 7614eea30e | |||
| bc06118995 | |||
| b88e98f4e2 | |||
| c46d910c87 | |||
| 682188f704 | |||
| 863c9aade5 | |||
| b4d2998425 | |||
| 9cb2e7bc35 |
7 changed files with 367 additions and 11 deletions
23
build.zig
23
build.zig
|
|
@ -349,6 +349,12 @@ pub fn build_exe(
|
|||
});
|
||||
const syntax_mod = syntax_dep.module("syntax");
|
||||
|
||||
const nightwatch_dep = b.dependency("nightwatch", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
const nightwatch_mod = nightwatch_dep.module("nightwatch");
|
||||
|
||||
const help_mod = b.createModule(.{
|
||||
.root_source_file = b.path("help.md"),
|
||||
});
|
||||
|
|
@ -604,6 +610,15 @@ pub fn build_exe(
|
|||
},
|
||||
});
|
||||
|
||||
const file_watcher_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/file_watcher.zig"),
|
||||
.imports = &.{
|
||||
.{ .name = "nightwatch", .module = nightwatch_mod },
|
||||
.{ .name = "cbor", .module = cbor_mod },
|
||||
.{ .name = "thespian", .module = thespian_mod },
|
||||
},
|
||||
});
|
||||
|
||||
const project_manager_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/project_manager.zig"),
|
||||
.imports = &.{
|
||||
|
|
@ -619,6 +634,7 @@ pub fn build_exe(
|
|||
.{ .name = "fuzzig", .module = fuzzig_dep.module("fuzzig") },
|
||||
.{ .name = "git", .module = git_mod },
|
||||
.{ .name = "VcsStatus", .module = VcsStatus_mod },
|
||||
.{ .name = "file_watcher", .module = file_watcher_mod },
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -723,6 +739,13 @@ pub fn build_exe(
|
|||
exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file }));
|
||||
exe.root_module.addImport("version_info", b.createModule(.{ .root_source_file = version_info_file }));
|
||||
|
||||
if (target.result.os.tag == .macos) {
|
||||
exe.addFrameworkPath(b.dependency("xcode-frameworks", .{}).path("Frameworks"));
|
||||
exe.addLibraryPath(b.dependency("xcode-frameworks", .{}).path("lib"));
|
||||
exe.linkFramework("CoreServices");
|
||||
exe.linkFramework("CoreFoundation");
|
||||
}
|
||||
|
||||
if (target.result.os.tag == .windows) {
|
||||
exe.addWin32ResourceFile(.{
|
||||
.file = b.path("src/win32/flow.rc"),
|
||||
|
|
|
|||
|
|
@ -46,6 +46,14 @@
|
|||
.url = "git+https://github.com/ziglibs/diffz.git#fbdf690b87db6b1142bbce6d4906f90b09ce60bb",
|
||||
.hash = "diffz-0.0.1-G2tlIezMAQBwGNGDs7Hn_N25dWSjEzgR_FAx9GFAvCuZ",
|
||||
},
|
||||
.@"xcode-frameworks" = .{
|
||||
.url = "git+https://github.com/hexops/xcode-frameworks?ref=main#8a1cfb373587ea4c9bb1468b7c986462d8d4e10e",
|
||||
.hash = "N-V-__8AALShqgXkvqYU6f__FrA22SMWmi2TXCJjNTO1m8XJ",
|
||||
},
|
||||
.nightwatch = .{
|
||||
.url = "git+https://git.flow-control.dev/neurocyte/nightwatch?ref=master#a1e5e3e9a5126d1ff7ce4959a823ea12f20ef0ae",
|
||||
.hash = "nightwatch-0.1.0-uXzeH8WuAAC95apH6JQZQDzCGrXK2PRPq0rDPPxwUI3Z",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"include",
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
@ -777,6 +781,61 @@ fn loaded(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void {
|
|||
parent.send(.{ "PRJ", "open_done", self.name, self.longest_file_path, self.files.items.len }) catch {};
|
||||
}
|
||||
|
||||
pub fn file_added(self: *Self, file_path: []const u8) OutOfMemoryError!void {
|
||||
for (self.files.items) |file|
|
||||
if (std.mem.eql(u8, file.path, file_path)) return;
|
||||
for (self.pending.items) |file|
|
||||
if (std.mem.eql(u8, file.path, file_path)) return;
|
||||
const file_type, const file_icon, const file_color = guess_file_type(file_path);
|
||||
(try self.files.addOne(self.allocator)).* = .{
|
||||
.path = try self.allocator.dupe(u8, file_path),
|
||||
.type = file_type,
|
||||
.icon = file_icon,
|
||||
.color = file_color,
|
||||
.mtime = std.time.nanoTimestamp(),
|
||||
};
|
||||
self.longest_file_path = @max(self.longest_file_path, file_path.len);
|
||||
self.sort_files_by_mtime();
|
||||
}
|
||||
|
||||
pub fn file_modified(self: *Self, file_path: []const u8) void {
|
||||
for (self.files.items) |*file| {
|
||||
if (!std.mem.eql(u8, file.path, file_path)) continue;
|
||||
file.mtime = std.time.nanoTimestamp();
|
||||
self.sort_files_by_mtime();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_renamed(self: *Self, from_path: []const u8, to_path: []const u8) OutOfMemoryError!void {
|
||||
for (self.files.items) |*file| {
|
||||
if (!std.mem.eql(u8, file.path, to_path)) continue;
|
||||
self.file_deleted(from_path);
|
||||
return;
|
||||
}
|
||||
for (self.files.items) |*file| {
|
||||
if (!std.mem.eql(u8, file.path, from_path)) continue;
|
||||
const new_path = try self.allocator.dupe(u8, to_path);
|
||||
self.allocator.free(file.path);
|
||||
file.path = new_path;
|
||||
file.mtime = std.time.nanoTimestamp();
|
||||
self.longest_file_path = @max(self.longest_file_path, to_path.len);
|
||||
self.sort_files_by_mtime();
|
||||
return;
|
||||
}
|
||||
return self.file_added(to_path);
|
||||
}
|
||||
|
||||
pub fn file_deleted(self: *Self, file_path: []const u8) void {
|
||||
for (self.files.items, 0..) |file, i| {
|
||||
if (!std.mem.eql(u8, file.path, file_path)) continue;
|
||||
self.allocator.free(file.path);
|
||||
_ = self.files.swapRemove(i);
|
||||
self.sort_files_by_mtime();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_mru(self: *Self, file_path: []const u8, row: usize, col: usize) OutOfMemoryError!void {
|
||||
defer self.sort_files_by_mtime();
|
||||
try self.update_mru_internal(file_path, std.time.nanoTimestamp(), row, col);
|
||||
|
|
@ -2796,6 +2855,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;
|
||||
|
|
|
|||
171
src/file_watcher.zig
Normal file
171
src/file_watcher.zig
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
const std = @import("std");
|
||||
const tp = @import("thespian");
|
||||
const cbor = @import("cbor");
|
||||
const nightwatch = @import("nightwatch");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pid: tp.pid_ref,
|
||||
|
||||
const Self = @This();
|
||||
const module_name = @typeName(Self);
|
||||
|
||||
pub const EventType = nightwatch.EventType;
|
||||
|
||||
pub const Error = error{
|
||||
FileWatcherSendFailed,
|
||||
ThespianSpawnFailed,
|
||||
OutOfMemory,
|
||||
};
|
||||
const SpawnError = error{ OutOfMemory, ThespianSpawnFailed };
|
||||
|
||||
pub fn watch(path: []const u8) Error!void {
|
||||
return send(.{ "watch", path });
|
||||
}
|
||||
|
||||
pub fn unwatch(path: []const u8) Error!void {
|
||||
return send(.{ "unwatch", path });
|
||||
}
|
||||
|
||||
pub fn start() SpawnError!void {
|
||||
_ = try get();
|
||||
}
|
||||
|
||||
pub fn shutdown() void {
|
||||
const pid = tp.env.get().proc(module_name);
|
||||
if (pid.expired()) return;
|
||||
pid.send(.{"shutdown"}) catch {};
|
||||
}
|
||||
|
||||
fn get() SpawnError!Self {
|
||||
const pid = tp.env.get().proc(module_name);
|
||||
return if (pid.expired()) create() else .{ .pid = pid };
|
||||
}
|
||||
|
||||
fn send(message: anytype) Error!void {
|
||||
return (try get()).pid.send(message) catch error.FileWatcherSendFailed;
|
||||
}
|
||||
|
||||
fn create() SpawnError!Self {
|
||||
const pid = try Process.create();
|
||||
defer pid.deinit();
|
||||
tp.env.get().proc_set(module_name, pid.ref());
|
||||
return .{ .pid = tp.env.get().proc(module_name) };
|
||||
}
|
||||
|
||||
const Process = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
parent: tp.pid,
|
||||
receiver: Receiver,
|
||||
nw: nightwatch,
|
||||
fd_watcher: if (builtin.os.tag == .linux) tp.file_descriptor else void,
|
||||
handler: nightwatch.Handler,
|
||||
|
||||
const Receiver = tp.Receiver(*@This());
|
||||
|
||||
fn create() SpawnError!tp.pid {
|
||||
const allocator = std.heap.c_allocator;
|
||||
const self = try allocator.create(@This());
|
||||
errdefer allocator.destroy(self);
|
||||
self.* = .{
|
||||
.allocator = allocator,
|
||||
.parent = tp.self_pid().clone(),
|
||||
.receiver = Receiver.init(@This().receive, self),
|
||||
.nw = undefined,
|
||||
.fd_watcher = if (builtin.os.tag == .linux) undefined else {},
|
||||
.handler = init_handler(),
|
||||
};
|
||||
return tp.spawn_link(self.allocator, self, @This().start, module_name);
|
||||
}
|
||||
|
||||
fn deinit(self: *@This()) void {
|
||||
if (builtin.os.tag == .linux) self.fd_watcher.deinit();
|
||||
self.nw.deinit();
|
||||
self.parent.deinit();
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn init_handler() nightwatch.Handler {
|
||||
return .{
|
||||
.vtable = &.{
|
||||
.change = handle_change,
|
||||
.rename = handle_rename,
|
||||
.wait_readable = if (builtin.os.tag == .linux) wait_readable else {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn start(self: *@This()) tp.result {
|
||||
errdefer self.deinit();
|
||||
_ = tp.set_trap(true);
|
||||
self.nw = nightwatch.init(self.allocator, &self.handler) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||
if (builtin.os.tag == .linux)
|
||||
self.fd_watcher = tp.file_descriptor.init(module_name, self.nw.backend.inotify_fd) catch |e| {
|
||||
std.log.err("file_watcher.start: {}", .{e});
|
||||
return tp.exit_error(e, @errorReturnTrace());
|
||||
};
|
||||
tp.receive(&self.receiver);
|
||||
}
|
||||
|
||||
fn receive(self: *@This(), from: tp.pid_ref, m: tp.message) tp.result {
|
||||
errdefer self.deinit();
|
||||
return self.receive_safe(from, m) catch |e| switch (e) {
|
||||
error.ExitNormal => tp.exit_normal(),
|
||||
else => {
|
||||
const err = tp.exit_error(e, @errorReturnTrace());
|
||||
std.log.err("file_watcher.receive: {}", .{err});
|
||||
return err;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn receive_safe(self: *@This(), _: tp.pid_ref, m: tp.message) (error{ExitNormal} || cbor.Error)!void {
|
||||
var path: []const u8 = undefined;
|
||||
var tag: []const u8 = undefined;
|
||||
var err_code: i64 = 0;
|
||||
var err_msg: []const u8 = undefined;
|
||||
|
||||
if (try cbor.match(m.buf, .{ "fd", tp.extract(&tag), "read_ready" })) {
|
||||
// re-arm the file_discriptor
|
||||
if (builtin.os.tag == .linux) {
|
||||
self.fd_watcher.wait_read() catch |e| std.log.err("file_watcher wait_read: {}", .{e});
|
||||
self.nw.handle_read_ready() catch |e| std.log.err("file_watcher handle_read_ready: {}", .{e});
|
||||
}
|
||||
} else if (try cbor.match(m.buf, .{ "fd", tp.extract(&tag), "read_error", tp.extract(&err_code), tp.extract(&err_msg) })) {
|
||||
std.log.err("fd read error on {s}: ({d}) {s}", .{ tag, err_code, err_msg });
|
||||
} else if (try cbor.match(m.buf, .{ "watch", tp.extract(&path) })) {
|
||||
self.nw.watch(path) catch |e| std.log.err("file_watcher watch: {s} -> {}", .{ path, e });
|
||||
} else if (try cbor.match(m.buf, .{ "unwatch", tp.extract(&path) })) {
|
||||
self.nw.unwatch(path) catch |e| std.log.err("file_watcher unwatch: {s} -> {}", .{ path, e });
|
||||
} else if (try cbor.match(m.buf, .{"shutdown"})) {
|
||||
return error.ExitNormal;
|
||||
} else if (try cbor.match(m.buf, .{ "exit", tp.more })) {
|
||||
return error.ExitNormal;
|
||||
} else {
|
||||
std.log.err("file_watcher.receive: {}", .{tp.unexpected(m)});
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_change(handler: *nightwatch.Handler, path: []const u8, event_type: EventType) error{HandlerFailed}!void {
|
||||
const self: *@This() = @alignCast(@fieldParentPtr("handler", handler));
|
||||
_ = self;
|
||||
_ = path;
|
||||
_ = event_type;
|
||||
}
|
||||
|
||||
fn handle_rename(handler: *nightwatch.Handler, src_path: []const u8, dst_path: []const u8) error{HandlerFailed}!void {
|
||||
const self: *@This() = @alignCast(@fieldParentPtr("handler", handler));
|
||||
_ = self;
|
||||
_ = src_path;
|
||||
_ = dst_path;
|
||||
}
|
||||
|
||||
fn wait_readable(handler: *nightwatch.Handler) error{HandlerFailed}!nightwatch.ReadableStatus {
|
||||
const self: *@This() = @alignCast(@fieldParentPtr("handler", handler));
|
||||
if (builtin.os.tag == .linux)
|
||||
self.fd_watcher.wait_read() catch |e| {
|
||||
std.log.err("file_watcher.wait_readable: {}", .{e});
|
||||
return error.HandlerFailed;
|
||||
};
|
||||
return .will_notify;
|
||||
}
|
||||
};
|
||||
|
|
@ -3,6 +3,7 @@ const tp = @import("thespian");
|
|||
const cbor = @import("cbor");
|
||||
const log = @import("log");
|
||||
const tracy = @import("tracy");
|
||||
const file_watcher = @import("file_watcher");
|
||||
const file_type_config = @import("file_type_config");
|
||||
const lsp_config = @import("lsp_config");
|
||||
const root = @import("soft_root").root;
|
||||
|
|
@ -420,8 +421,18 @@ const Process = struct {
|
|||
var vcs_id: []const u8 = undefined;
|
||||
|
||||
var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf);
|
||||
var event_type: file_watcher.EventType = undefined;
|
||||
var from_path: []const u8 = undefined;
|
||||
|
||||
if (try cbor.match(m.buf, .{ "walk_tree_entry", tp.extract(&project_directory), tp.more })) {
|
||||
if (try cbor.match(m.buf, .{ "FW", "rename", tp.extract(&from_path), tp.extract(&path) })) {
|
||||
self.handle_file_watch_rename(from_path, path);
|
||||
} else 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 project_directory;
|
||||
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);
|
||||
} else if (try cbor.match(m.buf, .{ "walk_tree_done", tp.extract(&project_directory) })) {
|
||||
|
|
@ -436,6 +447,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 {};
|
||||
|
|
@ -520,6 +540,7 @@ const Process = struct {
|
|||
self.logger.print("{s} error: {s}", .{ tag, message });
|
||||
} else if (try cbor.match(m.buf, .{"shutdown"})) {
|
||||
self.persist_projects();
|
||||
file_watcher.shutdown();
|
||||
from.send(.{ "project_manager", "shutdown" }) catch return error.ClientFailed;
|
||||
return error.ExitNormal;
|
||||
} else if (try cbor.match(m.buf, .{ "exit", "normal" })) {
|
||||
|
|
@ -537,6 +558,59 @@ const Process = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn project_for_path(self: *Process, abs_path: []const u8) ?struct { project: *Project, rel_path: []const u8 } {
|
||||
var it = self.projects.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const dir = entry.key_ptr.*;
|
||||
if (!std.mem.startsWith(u8, abs_path, dir)) continue;
|
||||
if (abs_path.len <= dir.len or abs_path[dir.len] != std.fs.path.sep) continue;
|
||||
return .{ .project = entry.value_ptr.*, .rel_path = abs_path[dir.len + 1 ..] };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn handle_file_watch_rename(self: *Process, abs_from: []const u8, abs_to: []const u8) void {
|
||||
std.log.debug("file_watch_event: rename {s} -> {s}", .{ abs_from, abs_to });
|
||||
const src = self.project_for_path(abs_from);
|
||||
const dst = self.project_for_path(abs_to);
|
||||
|
||||
if (src) |s| {
|
||||
if (dst) |d| {
|
||||
if (s.project == d.project) {
|
||||
s.project.file_renamed(s.rel_path, d.rel_path) catch |e| self.logger.err("file_watcher.file_renamed", e);
|
||||
} else {
|
||||
s.project.file_deleted(s.rel_path);
|
||||
d.project.file_added(d.rel_path) catch |e| self.logger.err("file_watcher.file_added", e);
|
||||
}
|
||||
} else {
|
||||
s.project.file_deleted(s.rel_path);
|
||||
}
|
||||
} else if (dst) |d| {
|
||||
d.project.file_added(d.rel_path) catch |e| self.logger.err("file_watcher.file_added", e);
|
||||
} else {
|
||||
self.parent.send(.{ "FW", "rename", abs_from, abs_to }) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
}
|
||||
|
||||
fn open(self: *Process, project_directory: []const u8) (SpawnError || std.fs.Dir.OpenError)!void {
|
||||
if (self.projects.get(project_directory)) |project| {
|
||||
project.last_used = std.time.nanoTimestamp();
|
||||
|
|
@ -548,6 +622,7 @@ const Process = struct {
|
|||
try self.projects.put(self.allocator, try self.allocator.dupe(u8, project_directory), project);
|
||||
self.restore_project(project) catch |e| self.logger.err("restore_project", e);
|
||||
project.query_git();
|
||||
file_watcher.watch(project_directory) catch |e| self.logger.err("file_watcher.watch", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -558,6 +633,7 @@ const Process = struct {
|
|||
kv.value.deinit();
|
||||
self.allocator.destroy(kv.value);
|
||||
self.logger.print("closed: {s}", .{project_directory});
|
||||
file_watcher.unwatch(project_directory) catch |e| self.logger.err("file_watcher.unwatch", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -622,6 +622,12 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
|
|||
if (try m.match(.{ "line_number_mode", tp.more })) // drop broadcast messages
|
||||
return;
|
||||
|
||||
if (try m.match(.{ "FW", "change", tp.more })) // file watcher events
|
||||
return;
|
||||
|
||||
if (try m.match(.{ "FW", "rename", tp.more })) // file watcher rename events
|
||||
return;
|
||||
|
||||
return tp.unexpected(m);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue