Compare commits
7 commits
6dfa87ce58
...
22b29b15b9
Author | SHA1 | Date | |
---|---|---|---|
22b29b15b9 | |||
86b19501c4 | |||
b82c582a83 | |||
ba5dc35913 | |||
1a0d4ca7b2 | |||
5ebd6353b3 | |||
2c68c2a00c |
7 changed files with 341 additions and 245 deletions
118
src/Project.zig
118
src/Project.zig
|
@ -11,23 +11,35 @@ const git = @import("git");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const LSP = @import("LSP.zig");
|
const LSP = @import("LSP.zig");
|
||||||
|
const walk_tree = @import("walk_tree.zig");
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
files: std.ArrayList(File),
|
files: std.ArrayListUnmanaged(File) = .empty,
|
||||||
pending: std.ArrayList(File),
|
pending: std.ArrayListUnmanaged(File) = .empty,
|
||||||
longest_file_path: usize = 0,
|
longest_file_path: usize = 0,
|
||||||
open_time: i64,
|
open_time: i64,
|
||||||
language_servers: std.StringHashMap(LSP),
|
language_servers: std.StringHashMap(LSP),
|
||||||
file_language_server: std.StringHashMap(LSP),
|
file_language_server: std.StringHashMap(LSP),
|
||||||
tasks: std.ArrayList(Task),
|
tasks: std.ArrayList(Task),
|
||||||
persistent: bool = false,
|
persistent: bool = false,
|
||||||
|
logger: log.Logger,
|
||||||
logger_lsp: log.Logger,
|
logger_lsp: log.Logger,
|
||||||
logger_git: log.Logger,
|
logger_git: log.Logger,
|
||||||
|
|
||||||
workspace: ?[]const u8 = null,
|
workspace: ?[]const u8 = null,
|
||||||
branch: ?[]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 Self = @This();
|
||||||
|
|
||||||
const OutOfMemoryError = error{OutOfMemory};
|
const OutOfMemoryError = error{OutOfMemory};
|
||||||
|
@ -55,22 +67,24 @@ const Task = struct {
|
||||||
mtime: i64,
|
mtime: i64,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const State = enum { none, running, done, failed };
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, name: []const u8) OutOfMemoryError!Self {
|
pub fn init(allocator: std.mem.Allocator, name: []const u8) OutOfMemoryError!Self {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.name = try allocator.dupe(u8, name),
|
.name = try allocator.dupe(u8, name),
|
||||||
.files = std.ArrayList(File).init(allocator),
|
|
||||||
.pending = std.ArrayList(File).init(allocator),
|
|
||||||
.open_time = std.time.milliTimestamp(),
|
.open_time = std.time.milliTimestamp(),
|
||||||
.language_servers = std.StringHashMap(LSP).init(allocator),
|
.language_servers = std.StringHashMap(LSP).init(allocator),
|
||||||
.file_language_server = std.StringHashMap(LSP).init(allocator),
|
.file_language_server = std.StringHashMap(LSP).init(allocator),
|
||||||
.tasks = std.ArrayList(Task).init(allocator),
|
.tasks = std.ArrayList(Task).init(allocator),
|
||||||
|
.logger = log.logger("project"),
|
||||||
.logger_lsp = log.logger("lsp"),
|
.logger_lsp = log.logger("lsp"),
|
||||||
.logger_git = log.logger("git"),
|
.logger_git = log.logger("git"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
|
if (self.walker) |pid| pid.send(.{"stop"}) catch {};
|
||||||
if (self.workspace) |p| self.allocator.free(p);
|
if (self.workspace) |p| self.allocator.free(p);
|
||||||
if (self.branch) |p| self.allocator.free(p);
|
if (self.branch) |p| self.allocator.free(p);
|
||||||
var i_ = self.file_language_server.iterator();
|
var i_ = self.file_language_server.iterator();
|
||||||
|
@ -83,11 +97,13 @@ pub fn deinit(self: *Self) void {
|
||||||
p.value_ptr.*.term();
|
p.value_ptr.*.term();
|
||||||
}
|
}
|
||||||
for (self.files.items) |file| self.allocator.free(file.path);
|
for (self.files.items) |file| self.allocator.free(file.path);
|
||||||
self.files.deinit();
|
self.files.deinit(self.allocator);
|
||||||
|
self.pending.deinit(self.allocator);
|
||||||
for (self.tasks.items) |task| self.allocator.free(task.command);
|
for (self.tasks.items) |task| self.allocator.free(task.command);
|
||||||
self.tasks.deinit();
|
self.tasks.deinit();
|
||||||
self.logger_lsp.deinit();
|
self.logger_lsp.deinit();
|
||||||
self.logger_git.deinit();
|
self.logger_git.deinit();
|
||||||
|
self.logger.deinit();
|
||||||
self.allocator.free(self.name);
|
self.allocator.free(self.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,11 +310,11 @@ fn make_URI(self: *Self, file_path: ?[]const u8) LspError![]const u8 {
|
||||||
return buf.toOwnedSlice();
|
return buf.toOwnedSlice();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sort_files_by_mtime(self: *Self) void {
|
fn sort_files_by_mtime(self: *Self) void {
|
||||||
sort_by_mtime(File, self.files.items);
|
sort_by_mtime(File, self.files.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sort_tasks_by_mtime(self: *Self) void {
|
fn sort_tasks_by_mtime(self: *Self) void {
|
||||||
sort_by_mtime(Task, self.tasks.items);
|
sort_by_mtime(Task, self.tasks.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,21 +402,47 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
|
||||||
return @min(max, matches.items.len);
|
return @min(max, matches.items.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_pending_file(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void {
|
pub fn walk_tree_entry(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void {
|
||||||
self.longest_file_path = @max(self.longest_file_path, file_path.len);
|
self.longest_file_path = @max(self.longest_file_path, file_path.len);
|
||||||
(try self.pending.addOne()).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime };
|
(try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge_pending_files(self: *Self) OutOfMemoryError!void {
|
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 {
|
||||||
defer self.sort_files_by_mtime();
|
defer self.sort_files_by_mtime();
|
||||||
const existing = try self.files.toOwnedSlice();
|
const existing = try self.files.toOwnedSlice(self.allocator);
|
||||||
|
defer self.allocator.free(existing);
|
||||||
self.files = self.pending;
|
self.files = self.pending;
|
||||||
self.pending = std.ArrayList(File).init(self.allocator);
|
self.pending = .empty;
|
||||||
|
|
||||||
for (existing) |*file| {
|
for (existing) |*file| {
|
||||||
self.update_mru_internal(file.path, file.mtime, file.pos.row, file.pos.col) catch {};
|
self.update_mru_internal(file.path, file.mtime, file.pos.row, file.pos.col) catch {};
|
||||||
self.allocator.free(file.path);
|
self.allocator.free(file.path);
|
||||||
}
|
}
|
||||||
self.allocator.free(existing);
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_mru(self: *Self, file_path: []const u8, row: usize, col: usize) OutOfMemoryError!void {
|
pub fn update_mru(self: *Self, file_path: []const u8, row: usize, col: usize) OutOfMemoryError!void {
|
||||||
|
@ -420,14 +462,14 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (row != 0) {
|
if (row != 0) {
|
||||||
(try self.files.addOne()).* = .{
|
(try self.files.addOne(self.allocator)).* = .{
|
||||||
.path = try self.allocator.dupe(u8, file_path),
|
.path = try self.allocator.dupe(u8, file_path),
|
||||||
.mtime = mtime,
|
.mtime = mtime,
|
||||||
.pos = .{ .row = row, .col = col },
|
.pos = .{ .row = row, .col = col },
|
||||||
.visited = true,
|
.visited = true,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
(try self.files.addOne()).* = .{
|
(try self.files.addOne(self.allocator)).* = .{
|
||||||
.path = try self.allocator.dupe(u8, file_path),
|
.path = try self.allocator.dupe(u8, file_path),
|
||||||
.mtime = mtime,
|
.mtime = mtime,
|
||||||
};
|
};
|
||||||
|
@ -1858,23 +1900,55 @@ pub fn get_line(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_git(self: *Self) void {
|
pub fn query_git(self: *Self) void {
|
||||||
git.workspace_path(@intFromPtr(self)) catch {};
|
self.state.workspace_path = .running;
|
||||||
git.current_branch(@intFromPtr(self)) catch {};
|
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;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_git(self: *Self, m: tp.message) !void {
|
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 {
|
||||||
var value: []const u8 = undefined;
|
var value: []const u8 = undefined;
|
||||||
|
var path: []const u8 = undefined;
|
||||||
if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.null_ })) {
|
if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.null_ })) {
|
||||||
// no git workspace
|
self.state.workspace_path = .done;
|
||||||
|
self.start_walker();
|
||||||
|
try self.loaded();
|
||||||
} else if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.extract(&value) })) {
|
} else if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.extract(&value) })) {
|
||||||
if (self.workspace) |p| self.allocator.free(p);
|
if (self.workspace) |p| self.allocator.free(p);
|
||||||
self.workspace = try self.allocator.dupe(u8, value);
|
self.workspace = try self.allocator.dupe(u8, value);
|
||||||
git.workspace_files(@intFromPtr(self)) catch {};
|
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();
|
||||||
} else if (try m.match(.{ tp.any, tp.any, "current_branch", tp.extract(&value) })) {
|
} else if (try m.match(.{ tp.any, tp.any, "current_branch", tp.extract(&value) })) {
|
||||||
if (self.branch) |p| self.allocator.free(p);
|
if (self.branch) |p| self.allocator.free(p);
|
||||||
self.branch = try self.allocator.dupe(u8, value);
|
self.branch = try self.allocator.dupe(u8, value);
|
||||||
} else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.extract(&value) })) {
|
self.state.current_branch = .done;
|
||||||
// TODO
|
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 {
|
} else {
|
||||||
self.logger_git.err("git", tp.unexpected(m));
|
self.logger_git.err("git", tp.unexpected(m));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ indent_size: usize = 4,
|
||||||
tab_width: usize = 8,
|
tab_width: usize = 8,
|
||||||
|
|
||||||
top_bar: []const u8 = "tabs",
|
top_bar: []const u8 = "tabs",
|
||||||
bottom_bar: []const u8 = "mode file log selection diagnostics keybind linenumber clock spacer",
|
bottom_bar: []const u8 = "mode file log selection diagnostics keybind branch linenumber clock spacer",
|
||||||
show_scrollbars: bool = true,
|
show_scrollbars: bool = true,
|
||||||
show_fileicons: bool = true,
|
show_fileicons: bool = true,
|
||||||
|
|
||||||
|
|
51
src/git.zig
51
src/git.zig
|
@ -20,29 +20,56 @@ pub fn workspace_path(context_: usize) Error!void {
|
||||||
|
|
||||||
pub fn current_branch(context_: usize) Error!void {
|
pub fn current_branch(context_: usize) Error!void {
|
||||||
const fn_name = @src().fn_name;
|
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 {
|
try git(context_, .{ "rev-parse", "--abbrev-ref", "HEAD" }, struct {
|
||||||
fn result(context: usize, parent: tp.pid_ref, output: []const u8) void {
|
fn result(context: usize, parent: tp.pid_ref, output: []const u8) void {
|
||||||
var it = std.mem.splitScalar(u8, output, '\n');
|
var it = std.mem.splitScalar(u8, output, '\n');
|
||||||
while (it.next()) |value| if (value.len > 0)
|
while (it.next()) |value| if (value.len > 0) {
|
||||||
|
blk: {
|
||||||
|
current_branch_cache = .{ .branch = allocator.dupeZ(u8, value) catch break :blk };
|
||||||
|
}
|
||||||
parent.send(.{ module_name, context, fn_name, value }) catch {};
|
parent.send(.{ module_name, context, fn_name, value }) catch {};
|
||||||
|
return;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}.result, exit_null_on_error(fn_name));
|
}.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 {
|
pub fn workspace_files(context: usize) Error!void {
|
||||||
const fn_name = @src().fn_name;
|
return git_line_output(
|
||||||
try git_err(context_, .{ "ls-files", "--cached", "--others", "--exclude-standard" }, struct {
|
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 {
|
||||||
fn result(context: usize, parent: tp.pid_ref, output: []const u8) void {
|
fn result(context: usize, parent: tp.pid_ref, output: []const u8) void {
|
||||||
var it = std.mem.splitScalar(u8, output, '\n');
|
var it = std.mem.splitScalar(u8, output, '\n');
|
||||||
while (it.next()) |value| if (value.len > 0)
|
while (it.next()) |value| if (value.len > 0)
|
||||||
parent.send(.{ module_name, context, fn_name, value }) catch {};
|
parent.send(.{ module_name, context, tag, value }) catch {};
|
||||||
}
|
}
|
||||||
}.result, struct {
|
}.result, struct {
|
||||||
fn result(_: usize, _: tp.pid_ref, output: []const u8) void {
|
fn result(_: usize, _: tp.pid_ref, output: []const u8) void {
|
||||||
var it = std.mem.splitScalar(u8, output, '\n');
|
var it = std.mem.splitScalar(u8, output, '\n');
|
||||||
while (it.next()) |line| std.log.err("{s}: {s}", .{ module_name, line });
|
while (it.next()) |line| std.log.err("{s}: {s}", .{ module_name, line });
|
||||||
}
|
}
|
||||||
}.result, exit_null_on_error(fn_name));
|
}.result, exit_null(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn git(
|
fn git(
|
||||||
|
@ -84,11 +111,19 @@ fn git_err(
|
||||||
@compileError("git command should be a tuple: " ++ @typeName(@TypeOf(cmd)));
|
@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 {
|
fn exit_null_on_error(comptime tag: []const u8) shell.ExitHandler {
|
||||||
return struct {
|
return struct {
|
||||||
fn exit(_: usize, parent: tp.pid_ref, _: []const u8, _: []const u8, exit_code: i64) void {
|
fn exit(context: usize, parent: tp.pid_ref, _: []const u8, _: []const u8, exit_code: i64) void {
|
||||||
if (exit_code > 0)
|
if (exit_code > 0)
|
||||||
parent.send(.{ module_name, tag, null }) catch {};
|
parent.send(.{ module_name, context, tag, null }) catch {};
|
||||||
}
|
}
|
||||||
}.exit;
|
}.exit;
|
||||||
}
|
}
|
||||||
|
|
|
@ -247,13 +247,12 @@ const Process = struct {
|
||||||
logger: log.Logger,
|
logger: log.Logger,
|
||||||
receiver: Receiver,
|
receiver: Receiver,
|
||||||
projects: ProjectsMap,
|
projects: ProjectsMap,
|
||||||
walker: ?tp.pid = null,
|
|
||||||
|
|
||||||
const InvalidArgumentError = error{InvalidArgument};
|
const InvalidArgumentError = error{InvalidArgument};
|
||||||
const UnsupportedError = error{Unsupported};
|
const UnsupportedError = error{Unsupported};
|
||||||
|
|
||||||
const Receiver = tp.Receiver(*Process);
|
const Receiver = tp.Receiver(*Process);
|
||||||
const ProjectsMap = std.StringHashMap(*Project);
|
const ProjectsMap = std.StringHashMapUnmanaged(*Project);
|
||||||
const RecentProject = struct {
|
const RecentProject = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
last_used: i128,
|
last_used: i128,
|
||||||
|
@ -267,7 +266,7 @@ const Process = struct {
|
||||||
.parent = tp.self_pid().clone(),
|
.parent = tp.self_pid().clone(),
|
||||||
.logger = log.logger(module_name),
|
.logger = log.logger(module_name),
|
||||||
.receiver = Receiver.init(Process.receive, self),
|
.receiver = Receiver.init(Process.receive, self),
|
||||||
.projects = ProjectsMap.init(allocator),
|
.projects = .empty,
|
||||||
};
|
};
|
||||||
return tp.spawn_link(self.allocator, self, Process.start, module_name);
|
return tp.spawn_link(self.allocator, self, Process.start, module_name);
|
||||||
}
|
}
|
||||||
|
@ -279,7 +278,7 @@ const Process = struct {
|
||||||
p.value_ptr.*.deinit();
|
p.value_ptr.*.deinit();
|
||||||
self.allocator.destroy(p.value_ptr.*);
|
self.allocator.destroy(p.value_ptr.*);
|
||||||
}
|
}
|
||||||
self.projects.deinit();
|
self.projects.deinit(self.allocator);
|
||||||
self.parent.deinit();
|
self.parent.deinit();
|
||||||
self.logger.deinit();
|
self.logger.deinit();
|
||||||
self.allocator.destroy(self);
|
self.allocator.destroy(self);
|
||||||
|
@ -334,14 +333,10 @@ 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) })) {
|
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));
|
const mtime = (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low));
|
||||||
if (self.projects.get(project_directory)) |project|
|
if (self.projects.get(project_directory)) |project|
|
||||||
project.add_pending_file(
|
project.walk_tree_entry(path, mtime) catch |e| self.logger.err("walk_tree_entry", e);
|
||||||
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) })) {
|
} else if (try cbor.match(m.buf, .{ "walk_tree_done", tp.extract(&project_directory) })) {
|
||||||
if (self.walker) |pid| pid.deinit();
|
if (self.projects.get(project_directory)) |project|
|
||||||
self.walker = null;
|
project.walk_tree_done() catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed;
|
||||||
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 })) {
|
} else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), tp.more })) {
|
||||||
const project: *Project = @ptrFromInt(context);
|
const project: *Project = @ptrFromInt(context);
|
||||||
project.process_git(m) catch {};
|
project.process_git(m) catch {};
|
||||||
|
@ -403,7 +398,6 @@ const Process = struct {
|
||||||
} else if (try cbor.match(m.buf, .{ "get_mru_position", tp.extract(&project_directory), tp.extract(&path) })) {
|
} 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;
|
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"})) {
|
} else if (try cbor.match(m.buf, .{"shutdown"})) {
|
||||||
if (self.walker) |pid| pid.send(.{"stop"}) catch {};
|
|
||||||
self.persist_projects();
|
self.persist_projects();
|
||||||
from.send(.{ "project_manager", "shutdown" }) catch return error.ClientFailed;
|
from.send(.{ "project_manager", "shutdown" }) catch return error.ClientFailed;
|
||||||
return error.ExitNormal;
|
return error.ExitNormal;
|
||||||
|
@ -423,10 +417,8 @@ const Process = struct {
|
||||||
self.logger.print("opening: {s}", .{project_directory});
|
self.logger.print("opening: {s}", .{project_directory});
|
||||||
const project = try self.allocator.create(Project);
|
const project = try self.allocator.create(Project);
|
||||||
project.* = try Project.init(self.allocator, project_directory);
|
project.* = try Project.init(self.allocator, project_directory);
|
||||||
try self.projects.put(try self.allocator.dupe(u8, project_directory), project);
|
try self.projects.put(self.allocator, 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);
|
self.restore_project(project) catch |e| self.logger.err("restore_project", e);
|
||||||
project.sort_files_by_mtime();
|
|
||||||
project.query_git();
|
project.query_git();
|
||||||
} else {
|
} else {
|
||||||
self.logger.print("switched to: {s}", .{project_directory});
|
self.logger.print("switched to: {s}", .{project_directory});
|
||||||
|
@ -443,25 +435,13 @@ 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 {
|
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;
|
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);
|
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 {
|
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;
|
const project = self.projects.get(project_directory) orelse return error.NoProject;
|
||||||
project.sort_files_by_mtime();
|
|
||||||
return project.request_recent_files(from, max);
|
return project.request_recent_files(from, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -815,190 +795,6 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_
|
||||||
}.spawn_link(a_, parent_, project_, max_, path_);
|
}.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 {
|
pub fn normalize_file_path(file_path: []const u8) []const u8 {
|
||||||
const project = tp.env.get().str("project");
|
const project = tp.env.get().str("project");
|
||||||
if (project.len == 0) return file_path;
|
if (project.len == 0) return file_path;
|
||||||
|
|
|
@ -74,7 +74,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||||
|
|
||||||
fn output_tdiff(self: *Self, tdiff: i64) !void {
|
fn output_tdiff(self: *Self, tdiff: i64) !void {
|
||||||
const msi = @divFloor(tdiff, time.us_per_ms);
|
const msi = @divFloor(tdiff, time.us_per_ms);
|
||||||
if (msi == 0) {
|
if (msi < 10) {
|
||||||
const d: f64 = @floatFromInt(tdiff);
|
const d: f64 = @floatFromInt(tdiff);
|
||||||
const ms = d / time.us_per_ms;
|
const ms = d / time.us_per_ms;
|
||||||
_ = try self.plane.print("{d:6.2} ▏", .{ms});
|
_ = try self.plane.print("{d:6.2} ▏", .{ms});
|
||||||
|
|
|
@ -64,12 +64,14 @@ fn process_git(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const format = " {s} {s} ";
|
||||||
|
|
||||||
pub fn layout(self: *Self) Widget.Layout {
|
pub fn layout(self: *Self) Widget.Layout {
|
||||||
const branch = self.branch orelse return .{ .static = 0 };
|
const branch = self.branch orelse return .{ .static = 0 };
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
var fbs = std.io.fixedBufferStream(&buf);
|
var fbs = std.io.fixedBufferStream(&buf);
|
||||||
const writer = fbs.writer();
|
const writer = fbs.writer();
|
||||||
writer.print("{s} {s}", .{ branch_symbol, branch }) catch {};
|
writer.print(format, .{ branch_symbol, branch }) catch {};
|
||||||
const len = self.plane.egc_chunk_width(fbs.getWritten(), 0, 1);
|
const len = self.plane.egc_chunk_width(fbs.getWritten(), 0, 1);
|
||||||
return .{ .static = len };
|
return .{ .static = len };
|
||||||
}
|
}
|
||||||
|
@ -82,7 +84,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||||
self.plane.set_style(theme.statusbar);
|
self.plane.set_style(theme.statusbar);
|
||||||
self.plane.fill(" ");
|
self.plane.fill(" ");
|
||||||
self.plane.home();
|
self.plane.home();
|
||||||
_ = self.plane.print("{s} {s}", .{ branch_symbol, branch }) catch {};
|
_ = self.plane.print(format, .{ branch_symbol, branch }) catch {};
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
189
src/walk_tree.zig
Normal file
189
src/walk_tree.zig
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue