diff --git a/src/Project.zig b/src/Project.zig index 80a4fd6..1b5d07c 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -11,35 +11,23 @@ const git = @import("git"); const builtin = @import("builtin"); const LSP = @import("LSP.zig"); -const walk_tree = @import("walk_tree.zig"); allocator: std.mem.Allocator, name: []const u8, -files: std.ArrayListUnmanaged(File) = .empty, -pending: std.ArrayListUnmanaged(File) = .empty, +files: std.ArrayList(File), +pending: std.ArrayList(File), longest_file_path: usize = 0, open_time: i64, language_servers: std.StringHashMap(LSP), file_language_server: std.StringHashMap(LSP), tasks: std.ArrayList(Task), persistent: bool = false, -logger: log.Logger, logger_lsp: log.Logger, logger_git: log.Logger, workspace: ?[]const u8 = null, branch: ?[]const u8 = null, -walker: ?tp.pid = null, - -// async task states -state: struct { - walk_tree: State = .none, - workspace_path: State = .none, - current_branch: State = .none, - workspace_files: State = .none, -} = .{}, - const Self = @This(); const OutOfMemoryError = error{OutOfMemory}; @@ -67,24 +55,22 @@ const Task = struct { mtime: i64, }; -const State = enum { none, running, done, failed }; - pub fn init(allocator: std.mem.Allocator, name: []const u8) OutOfMemoryError!Self { return .{ .allocator = allocator, .name = try allocator.dupe(u8, name), + .files = std.ArrayList(File).init(allocator), + .pending = std.ArrayList(File).init(allocator), .open_time = std.time.milliTimestamp(), .language_servers = std.StringHashMap(LSP).init(allocator), .file_language_server = std.StringHashMap(LSP).init(allocator), .tasks = std.ArrayList(Task).init(allocator), - .logger = log.logger("project"), .logger_lsp = log.logger("lsp"), .logger_git = log.logger("git"), }; } pub fn deinit(self: *Self) void { - if (self.walker) |pid| pid.send(.{"stop"}) catch {}; if (self.workspace) |p| self.allocator.free(p); if (self.branch) |p| self.allocator.free(p); var i_ = self.file_language_server.iterator(); @@ -97,13 +83,11 @@ pub fn deinit(self: *Self) void { p.value_ptr.*.term(); } for (self.files.items) |file| self.allocator.free(file.path); - self.files.deinit(self.allocator); - self.pending.deinit(self.allocator); + self.files.deinit(); for (self.tasks.items) |task| self.allocator.free(task.command); self.tasks.deinit(); self.logger_lsp.deinit(); self.logger_git.deinit(); - self.logger.deinit(); self.allocator.free(self.name); } @@ -310,11 +294,11 @@ fn make_URI(self: *Self, file_path: ?[]const u8) LspError![]const u8 { return buf.toOwnedSlice(); } -fn sort_files_by_mtime(self: *Self) void { +pub fn sort_files_by_mtime(self: *Self) void { sort_by_mtime(File, self.files.items); } -fn sort_tasks_by_mtime(self: *Self) void { +pub fn sort_tasks_by_mtime(self: *Self) void { sort_by_mtime(Task, self.tasks.items); } @@ -402,47 +386,21 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co return @min(max, matches.items.len); } -pub fn walk_tree_entry(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void { +pub fn add_pending_file(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void { self.longest_file_path = @max(self.longest_file_path, file_path.len); - (try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime }; + (try self.pending.addOne()).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime }; } -pub fn walk_tree_done(self: *Self) OutOfMemoryError!void { - self.state.walk_tree = .done; - if (self.walker) |pid| pid.deinit(); - self.walker = null; - return self.loaded(); -} - -fn merge_pending_files(self: *Self) OutOfMemoryError!void { +pub fn merge_pending_files(self: *Self) OutOfMemoryError!void { defer self.sort_files_by_mtime(); - const existing = try self.files.toOwnedSlice(self.allocator); - defer self.allocator.free(existing); + const existing = try self.files.toOwnedSlice(); self.files = self.pending; - self.pending = .empty; - + self.pending = std.ArrayList(File).init(self.allocator); for (existing) |*file| { self.update_mru_internal(file.path, file.mtime, file.pos.row, file.pos.col) catch {}; self.allocator.free(file.path); } -} - -fn loaded(self: *Self) OutOfMemoryError!void { - inline for (@typeInfo(@TypeOf(self.state)).@"struct".fields) |f| - if (@field(self.state, f.name) == .running) return; - - self.logger.print("project files: {d} restored, {d} {s}", .{ - self.files.items.len, - self.pending.items.len, - if (self.state.workspace_files == .done) "tracked" else "walked", - }); - - try self.merge_pending_files(); - self.logger.print("opened: {s} with {d} files in {d} ms", .{ - self.name, - self.files.items.len, - std.time.milliTimestamp() - self.open_time, - }); + self.allocator.free(existing); } pub fn update_mru(self: *Self, file_path: []const u8, row: usize, col: usize) OutOfMemoryError!void { @@ -462,14 +420,14 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi return; } if (row != 0) { - (try self.files.addOne(self.allocator)).* = .{ + (try self.files.addOne()).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime, .pos = .{ .row = row, .col = col }, .visited = true, }; } else { - (try self.files.addOne(self.allocator)).* = .{ + (try self.files.addOne()).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime, }; @@ -1900,55 +1858,23 @@ pub fn get_line(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 { } pub fn query_git(self: *Self) void { - self.state.workspace_path = .running; - git.workspace_path(@intFromPtr(self)) catch { - self.state.workspace_path = .failed; - self.start_walker(); - }; - self.state.current_branch = .running; - git.current_branch(@intFromPtr(self)) catch { - self.state.current_branch = .failed; - }; + git.workspace_path(@intFromPtr(self)) catch {}; + git.current_branch(@intFromPtr(self)) catch {}; } -fn start_walker(self: *Self) void { - self.state.walk_tree = .running; - self.walker = walk_tree.start(self.allocator, self.name) catch blk: { - self.state.walk_tree = .failed; - break :blk null; - }; -} - -pub fn process_git(self: *Self, m: tp.message) (OutOfMemoryError || error{Exit})!void { +pub fn process_git(self: *Self, m: tp.message) !void { var value: []const u8 = undefined; - var path: []const u8 = undefined; if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.null_ })) { - self.state.workspace_path = .done; - self.start_walker(); - try self.loaded(); + // no git workspace } else if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.extract(&value) })) { if (self.workspace) |p| self.allocator.free(p); self.workspace = try self.allocator.dupe(u8, value); - self.state.workspace_path = .done; - self.state.workspace_files = .running; - git.workspace_files(@intFromPtr(self)) catch { - self.state.workspace_files = .failed; - }; - } else if (try m.match(.{ tp.any, tp.any, "current_branch", tp.null_ })) { - self.state.current_branch = .done; - try self.loaded(); + git.workspace_files(@intFromPtr(self)) catch {}; } else if (try m.match(.{ tp.any, tp.any, "current_branch", tp.extract(&value) })) { if (self.branch) |p| self.allocator.free(p); self.branch = try self.allocator.dupe(u8, value); - self.state.current_branch = .done; - try self.loaded(); - } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.extract(&path) })) { - self.longest_file_path = @max(self.longest_file_path, path.len); - const stat = std.fs.cwd().statFile(path) catch return; - (try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, path), .mtime = stat.mtime }; - } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.null_ })) { - self.state.workspace_files = .done; - try self.loaded(); + } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.extract(&value) })) { + // TODO } else { self.logger_git.err("git", tp.unexpected(m)); } diff --git a/src/config.zig b/src/config.zig index ec0e51d..31f76fe 100644 --- a/src/config.zig +++ b/src/config.zig @@ -23,7 +23,7 @@ indent_size: usize = 4, tab_width: usize = 8, top_bar: []const u8 = "tabs", -bottom_bar: []const u8 = "mode file log selection diagnostics keybind branch linenumber clock spacer", +bottom_bar: []const u8 = "mode file log selection diagnostics keybind linenumber clock spacer", show_scrollbars: bool = true, show_fileicons: bool = true, diff --git a/src/git.zig b/src/git.zig index 1e76a40..fb02212 100644 --- a/src/git.zig +++ b/src/git.zig @@ -20,56 +20,29 @@ pub fn workspace_path(context_: usize) Error!void { pub fn current_branch(context_: usize) Error!void { const fn_name = @src().fn_name; - if (current_branch_cache) |p| { - tp.self_pid().send(.{ module_name, context_, fn_name, p.branch }) catch {}; - return; - } try git(context_, .{ "rev-parse", "--abbrev-ref", "HEAD" }, struct { fn result(context: usize, parent: tp.pid_ref, output: []const u8) void { var it = std.mem.splitScalar(u8, output, '\n'); - while (it.next()) |value| if (value.len > 0) { - blk: { - current_branch_cache = .{ .branch = allocator.dupeZ(u8, value) catch break :blk }; - } + while (it.next()) |value| if (value.len > 0) parent.send(.{ module_name, context, fn_name, value }) catch {}; - return; - }; } }.result, exit_null_on_error(fn_name)); } -var current_branch_cache: ?struct { - branch: ?[:0]const u8 = null, -} = null; -pub fn workspace_files(context: usize) Error!void { - return git_line_output( - context, - @src().fn_name, - .{ "ls-files", "--cached", "--others", "--exclude-standard" }, - ); -} - -pub fn workspace_ignored_files(context: usize) Error!void { - return git_line_output( - context, - @src().fn_name, - .{ "ls-files", "--cached", "--others", "--exclude-standard", "--ignored" }, - ); -} - -fn git_line_output(context_: usize, comptime tag: []const u8, cmd: anytype) Error!void { - try git_err(context_, cmd, struct { +pub fn workspace_files(context_: usize) Error!void { + const fn_name = @src().fn_name; + try git_err(context_, .{ "ls-files", "--cached", "--others", "--exclude-standard" }, struct { fn result(context: usize, parent: tp.pid_ref, output: []const u8) void { var it = std.mem.splitScalar(u8, output, '\n'); while (it.next()) |value| if (value.len > 0) - parent.send(.{ module_name, context, tag, value }) catch {}; + parent.send(.{ module_name, context, fn_name, value }) catch {}; } }.result, struct { fn result(_: usize, _: tp.pid_ref, output: []const u8) void { var it = std.mem.splitScalar(u8, output, '\n'); while (it.next()) |line| std.log.err("{s}: {s}", .{ module_name, line }); } - }.result, exit_null(tag)); + }.result, exit_null_on_error(fn_name)); } fn git( @@ -111,19 +84,11 @@ fn git_err( @compileError("git command should be a tuple: " ++ @typeName(@TypeOf(cmd))); } -fn exit_null(comptime tag: []const u8) shell.ExitHandler { - return struct { - fn exit(context: usize, parent: tp.pid_ref, _: []const u8, _: []const u8, _: i64) void { - parent.send(.{ module_name, context, tag, null }) catch {}; - } - }.exit; -} - fn exit_null_on_error(comptime tag: []const u8) shell.ExitHandler { return struct { - fn exit(context: usize, parent: tp.pid_ref, _: []const u8, _: []const u8, exit_code: i64) void { + fn exit(_: usize, parent: tp.pid_ref, _: []const u8, _: []const u8, exit_code: i64) void { if (exit_code > 0) - parent.send(.{ module_name, context, tag, null }) catch {}; + parent.send(.{ module_name, tag, null }) catch {}; } }.exit; } diff --git a/src/project_manager.zig b/src/project_manager.zig index 972e110..d389a37 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -247,12 +247,13 @@ const Process = struct { logger: log.Logger, receiver: Receiver, projects: ProjectsMap, + walker: ?tp.pid = null, const InvalidArgumentError = error{InvalidArgument}; const UnsupportedError = error{Unsupported}; const Receiver = tp.Receiver(*Process); - const ProjectsMap = std.StringHashMapUnmanaged(*Project); + const ProjectsMap = std.StringHashMap(*Project); const RecentProject = struct { name: []const u8, last_used: i128, @@ -266,7 +267,7 @@ const Process = struct { .parent = tp.self_pid().clone(), .logger = log.logger(module_name), .receiver = Receiver.init(Process.receive, self), - .projects = .empty, + .projects = ProjectsMap.init(allocator), }; return tp.spawn_link(self.allocator, self, Process.start, module_name); } @@ -278,7 +279,7 @@ const Process = struct { p.value_ptr.*.deinit(); self.allocator.destroy(p.value_ptr.*); } - self.projects.deinit(self.allocator); + self.projects.deinit(); self.parent.deinit(); self.logger.deinit(); self.allocator.destroy(self); @@ -333,10 +334,14 @@ const Process = struct { if (try cbor.match(m.buf, .{ "walk_tree_entry", tp.extract(&project_directory), tp.extract(&path), tp.extract(&high), tp.extract(&low) })) { const mtime = (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low)); if (self.projects.get(project_directory)) |project| - project.walk_tree_entry(path, mtime) catch |e| self.logger.err("walk_tree_entry", e); + project.add_pending_file( + path, + mtime, + ) catch |e| self.logger.err("walk_tree_entry", e); } else if (try cbor.match(m.buf, .{ "walk_tree_done", tp.extract(&project_directory) })) { - if (self.projects.get(project_directory)) |project| - project.walk_tree_done() catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; + if (self.walker) |pid| pid.deinit(); + self.walker = null; + self.loaded(project_directory) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), tp.more })) { const project: *Project = @ptrFromInt(context); project.process_git(m) catch {}; @@ -398,6 +403,7 @@ const Process = struct { } else if (try cbor.match(m.buf, .{ "get_mru_position", tp.extract(&project_directory), tp.extract(&path) })) { self.get_mru_position(from, project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{"shutdown"})) { + if (self.walker) |pid| pid.send(.{"stop"}) catch {}; self.persist_projects(); from.send(.{ "project_manager", "shutdown" }) catch return error.ClientFailed; return error.ExitNormal; @@ -417,8 +423,10 @@ const Process = struct { self.logger.print("opening: {s}", .{project_directory}); const project = try self.allocator.create(Project); project.* = try Project.init(self.allocator, project_directory); - try self.projects.put(self.allocator, try self.allocator.dupe(u8, project_directory), project); + try self.projects.put(try self.allocator.dupe(u8, project_directory), project); + self.walker = try walk_tree_async(self.allocator, project_directory); self.restore_project(project) catch |e| self.logger.err("restore_project", e); + project.sort_files_by_mtime(); project.query_git(); } else { self.logger.print("switched to: {s}", .{project_directory}); @@ -435,13 +443,25 @@ const Process = struct { } } + fn loaded(self: *Process, project_directory: []const u8) OutOfMemoryError!void { + const project = self.projects.get(project_directory) orelse return; + try project.merge_pending_files(); + self.logger.print("opened: {s} with {d} files in {d} ms", .{ + project_directory, + project.files.items.len, + std.time.milliTimestamp() - project.open_time, + }); + } + fn request_n_most_recent_file(self: *Process, from: tp.pid_ref, project_directory: []const u8, n: usize) (ProjectError || Project.ClientError)!void { const project = self.projects.get(project_directory) orelse return error.NoProject; + project.sort_files_by_mtime(); return project.request_n_most_recent_file(from, n); } fn request_recent_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize) (ProjectError || Project.ClientError)!void { const project = self.projects.get(project_directory) orelse return error.NoProject; + project.sort_files_by_mtime(); return project.request_recent_files(from, max); } @@ -795,6 +815,190 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_ }.spawn_link(a_, parent_, project_, max_, path_); } +fn walk_tree_async(a_: std.mem.Allocator, root_path_: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid { + return struct { + allocator: std.mem.Allocator, + root_path: []const u8, + parent: tp.pid, + receiver: Receiver, + dir: std.fs.Dir, + walker: FilteredWalker, + + const tree_walker = @This(); + const Receiver = tp.Receiver(*tree_walker); + + fn spawn_link(allocator: std.mem.Allocator, root_path: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid { + const self = try allocator.create(tree_walker); + self.* = .{ + .allocator = allocator, + .root_path = try allocator.dupe(u8, root_path), + .parent = tp.self_pid().clone(), + .receiver = Receiver.init(tree_walker.receive, self), + .dir = try std.fs.cwd().openDir(self.root_path, .{ .iterate = true }), + .walker = try walk_filtered(self.dir, self.allocator), + }; + return tp.spawn_link(allocator, self, tree_walker.start, module_name ++ ".tree_walker"); + } + + fn start(self: *tree_walker) tp.result { + errdefer self.deinit(); + const frame = tracy.initZone(@src(), .{ .name = "project scan" }); + defer frame.deinit(); + tp.receive(&self.receiver); + self.next() catch |e| return tp.exit_error(e, @errorReturnTrace()); + } + + fn deinit(self: *tree_walker) void { + self.walker.deinit(); + self.dir.close(); + self.allocator.free(self.root_path); + self.parent.deinit(); + } + + fn receive(self: *tree_walker, _: tp.pid_ref, m: tp.message) tp.result { + errdefer self.deinit(); + const frame = tracy.initZone(@src(), .{ .name = "project scan" }); + defer frame.deinit(); + + if (try m.match(.{"next"})) { + self.next() catch |e| return tp.exit_error(e, @errorReturnTrace()); + } else if (try m.match(.{"stop"})) { + return tp.exit_normal(); + } else { + return tp.unexpected(m); + } + } + + fn next(self: *tree_walker) !void { + if (try self.walker.next()) |path| { + const stat = self.dir.statFile(path) catch return tp.self_pid().send(.{"next"}); + const mtime = stat.mtime; + const high: i64 = @intCast(mtime >> 64); + const low: i64 = @truncate(mtime); + std.debug.assert(mtime == (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low))); + try self.parent.send(.{ "walk_tree_entry", self.root_path, path, high, low }); + return tp.self_pid().send(.{"next"}); + } else { + self.parent.send(.{ "walk_tree_done", self.root_path }) catch {}; + return tp.exit_normal(); + } + } + }.spawn_link(a_, root_path_); +} + +const filtered_dirs = [_][]const u8{ + "build", + ".cache", + ".cargo", + ".git", + ".jj", + "node_modules", + ".npm", + ".rustup", + ".var", + ".zig-cache", + "zig-cache", + "zig-out", +}; + +fn is_filtered_dir(dirname: []const u8) bool { + for (filtered_dirs) |filter| + if (std.mem.eql(u8, filter, dirname)) + return true; + return false; +} + +const FilteredWalker = struct { + allocator: std.mem.Allocator, + stack: std.ArrayListUnmanaged(StackItem), + name_buffer: std.ArrayListUnmanaged(u8), + + const Path = []const u8; + + const StackItem = struct { + iter: std.fs.Dir.Iterator, + dirname_len: usize, + }; + + pub fn next(self: *FilteredWalker) OutOfMemoryError!?Path { + while (self.stack.items.len != 0) { + var top = &self.stack.items[self.stack.items.len - 1]; + var containing = top; + var dirname_len = top.dirname_len; + if (top.iter.next() catch { + var item_ = self.stack.pop(); + if (item_) |*item| + if (self.stack.items.len != 0) { + item.iter.dir.close(); + }; + continue; + }) |base| { + self.name_buffer.shrinkRetainingCapacity(dirname_len); + if (self.name_buffer.items.len != 0) { + try self.name_buffer.append(self.allocator, std.fs.path.sep); + dirname_len += 1; + } + try self.name_buffer.appendSlice(self.allocator, base.name); + switch (base.kind) { + .directory => { + if (is_filtered_dir(base.name)) + continue; + var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) { + error.NameTooLong => @panic("unexpected error.NameTooLong"), // no path sep in base.name + else => continue, + }; + { + errdefer new_dir.close(); + try self.stack.append(self.allocator, .{ + .iter = new_dir.iterateAssumeFirstIteration(), + .dirname_len = self.name_buffer.items.len, + }); + top = &self.stack.items[self.stack.items.len - 1]; + containing = &self.stack.items[self.stack.items.len - 2]; + } + }, + .file => return self.name_buffer.items, + else => continue, + } + } else { + var item_ = self.stack.pop(); + if (item_) |*item| + if (self.stack.items.len != 0) { + item.iter.dir.close(); + }; + } + } + return null; + } + + pub fn deinit(self: *FilteredWalker) void { + // Close any remaining directories except the initial one (which is always at index 0) + if (self.stack.items.len > 1) { + for (self.stack.items[1..]) |*item| { + item.iter.dir.close(); + } + } + self.stack.deinit(self.allocator); + self.name_buffer.deinit(self.allocator); + } +}; + +fn walk_filtered(dir: std.fs.Dir, allocator: std.mem.Allocator) !FilteredWalker { + var stack: std.ArrayListUnmanaged(FilteredWalker.StackItem) = .{}; + errdefer stack.deinit(allocator); + + try stack.append(allocator, .{ + .iter = dir.iterate(), + .dirname_len = 0, + }); + + return .{ + .allocator = allocator, + .stack = stack, + .name_buffer = .{}, + }; +} + pub fn normalize_file_path(file_path: []const u8) []const u8 { const project = tp.env.get().str("project"); if (project.len == 0) return file_path; diff --git a/src/tui/logview.zig b/src/tui/logview.zig index 02776e0..f5ed903 100644 --- a/src/tui/logview.zig +++ b/src/tui/logview.zig @@ -74,7 +74,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool { fn output_tdiff(self: *Self, tdiff: i64) !void { const msi = @divFloor(tdiff, time.us_per_ms); - if (msi < 10) { + if (msi == 0) { const d: f64 = @floatFromInt(tdiff); const ms = d / time.us_per_ms; _ = try self.plane.print("{d:6.2} ▏", .{ms}); diff --git a/src/tui/status/branch.zig b/src/tui/status/branch.zig index a505d9f..f4bd492 100644 --- a/src/tui/status/branch.zig +++ b/src/tui/status/branch.zig @@ -64,14 +64,12 @@ fn process_git( return true; } -const format = " {s} {s} "; - pub fn layout(self: *Self) Widget.Layout { const branch = self.branch orelse return .{ .static = 0 }; var buf: [256]u8 = undefined; var fbs = std.io.fixedBufferStream(&buf); const writer = fbs.writer(); - writer.print(format, .{ branch_symbol, branch }) catch {}; + writer.print("{s} {s}", .{ branch_symbol, branch }) catch {}; const len = self.plane.egc_chunk_width(fbs.getWritten(), 0, 1); return .{ .static = len }; } @@ -84,7 +82,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool { self.plane.set_style(theme.statusbar); self.plane.fill(" "); self.plane.home(); - _ = self.plane.print(format, .{ branch_symbol, branch }) catch {}; + _ = self.plane.print("{s} {s}", .{ branch_symbol, branch }) catch {}; return false; } diff --git a/src/walk_tree.zig b/src/walk_tree.zig deleted file mode 100644 index bae5a68..0000000 --- a/src/walk_tree.zig +++ /dev/null @@ -1,189 +0,0 @@ -const std = @import("std"); -const tp = @import("thespian"); -const tracy = @import("tracy"); - -const module_name = @typeName(@This()); - -const SpawnError = (OutOfMemoryError || error{ThespianSpawnFailed}); -const OutOfMemoryError = error{OutOfMemory}; - -pub fn start(a_: std.mem.Allocator, root_path_: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid { - return struct { - allocator: std.mem.Allocator, - root_path: []const u8, - parent: tp.pid, - receiver: Receiver, - dir: std.fs.Dir, - walker: FilteredWalker, - - const tree_walker = @This(); - const Receiver = tp.Receiver(*tree_walker); - - fn spawn_link(allocator: std.mem.Allocator, root_path: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid { - const self = try allocator.create(tree_walker); - self.* = .{ - .allocator = allocator, - .root_path = try allocator.dupe(u8, root_path), - .parent = tp.self_pid().clone(), - .receiver = .init(tree_walker.receive, self), - .dir = try std.fs.cwd().openDir(self.root_path, .{ .iterate = true }), - .walker = try .init(self.dir, self.allocator), - }; - return tp.spawn_link(allocator, self, tree_walker.start, module_name ++ ".tree_walker"); - } - - fn start(self: *tree_walker) tp.result { - errdefer self.deinit(); - const frame = tracy.initZone(@src(), .{ .name = "project scan" }); - defer frame.deinit(); - tp.receive(&self.receiver); - self.next() catch |e| return tp.exit_error(e, @errorReturnTrace()); - } - - fn deinit(self: *tree_walker) void { - self.walker.deinit(); - self.dir.close(); - self.allocator.free(self.root_path); - self.parent.deinit(); - } - - fn receive(self: *tree_walker, _: tp.pid_ref, m: tp.message) tp.result { - errdefer self.deinit(); - const frame = tracy.initZone(@src(), .{ .name = "project scan" }); - defer frame.deinit(); - - if (try m.match(.{"next"})) { - self.next() catch |e| return tp.exit_error(e, @errorReturnTrace()); - } else if (try m.match(.{"stop"})) { - return tp.exit_normal(); - } else { - return tp.unexpected(m); - } - } - - fn next(self: *tree_walker) !void { - if (try self.walker.next()) |path| { - const stat = self.dir.statFile(path) catch return tp.self_pid().send(.{"next"}); - const mtime = stat.mtime; - const high: i64 = @intCast(mtime >> 64); - const low: i64 = @truncate(mtime); - std.debug.assert(mtime == (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low))); - try self.parent.send(.{ "walk_tree_entry", self.root_path, path, high, low }); - return tp.self_pid().send(.{"next"}); - } else { - self.parent.send(.{ "walk_tree_done", self.root_path }) catch {}; - return tp.exit_normal(); - } - } - }.spawn_link(a_, root_path_); -} - -const filtered_dirs = [_][]const u8{ - ".cache", - ".cargo", - ".git", - ".jj", - "node_modules", - ".npm", - ".rustup", - ".var", - ".zig-cache", -}; - -fn is_filtered_dir(dirname: []const u8) bool { - for (filtered_dirs) |filter| - if (std.mem.eql(u8, filter, dirname)) - return true; - return false; -} - -const FilteredWalker = struct { - allocator: std.mem.Allocator, - stack: std.ArrayListUnmanaged(StackItem), - name_buffer: std.ArrayListUnmanaged(u8), - - const Path = []const u8; - - const StackItem = struct { - iter: std.fs.Dir.Iterator, - dirname_len: usize, - }; - - pub fn init(dir: std.fs.Dir, allocator: std.mem.Allocator) !FilteredWalker { - var stack: std.ArrayListUnmanaged(FilteredWalker.StackItem) = .{}; - errdefer stack.deinit(allocator); - - try stack.append(allocator, .{ - .iter = dir.iterate(), - .dirname_len = 0, - }); - - return .{ - .allocator = allocator, - .stack = stack, - .name_buffer = .{}, - }; - } - - pub fn deinit(self: *FilteredWalker) void { - // Close any remaining directories except the initial one (which is always at index 0) - if (self.stack.items.len > 1) { - for (self.stack.items[1..]) |*item| { - item.iter.dir.close(); - } - } - self.stack.deinit(self.allocator); - self.name_buffer.deinit(self.allocator); - } - - pub fn next(self: *FilteredWalker) OutOfMemoryError!?Path { - while (self.stack.items.len != 0) { - var top = &self.stack.items[self.stack.items.len - 1]; - var containing = top; - var dirname_len = top.dirname_len; - if (top.iter.next() catch { - var item_ = self.stack.pop(); - if (item_) |*item| - if (self.stack.items.len != 0) { - item.iter.dir.close(); - }; - continue; - }) |base| { - self.name_buffer.shrinkRetainingCapacity(dirname_len); - if (self.name_buffer.items.len != 0) { - try self.name_buffer.append(self.allocator, std.fs.path.sep); - dirname_len += 1; - } - try self.name_buffer.appendSlice(self.allocator, base.name); - switch (base.kind) { - .directory => { - if (is_filtered_dir(base.name)) - continue; - var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) { - error.NameTooLong => @panic("unexpected error.NameTooLong"), // no path sep in base.name - else => continue, - }; - { - errdefer new_dir.close(); - try self.stack.append(self.allocator, .{ - .iter = new_dir.iterateAssumeFirstIteration(), - .dirname_len = self.name_buffer.items.len, - }); - top = &self.stack.items[self.stack.items.len - 1]; - containing = &self.stack.items[self.stack.items.len - 2]; - } - }, - .file => return self.name_buffer.items, - else => continue, - } - } else { - var item_ = self.stack.pop(); - if (item_) |*item| - if (self.stack.items.len != 0) { - item.iter.dir.close(); - }; - } - } - return null; - } -};