From f76085325a469058b8654db6e11c5737dab54677 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 21 Apr 2025 21:43:29 +0200 Subject: [PATCH] feat: query project files via git (part 1) --- build.zig | 1 + src/Project.zig | 29 +++++++++++++++++++++++ src/git.zig | 49 +++++++++++++++++++++++++++------------ src/project_manager.zig | 5 ++++ src/tui/status/branch.zig | 12 +++++----- 5 files changed, 75 insertions(+), 21 deletions(-) diff --git a/build.zig b/build.zig index cc54c43..7f1d998 100644 --- a/build.zig +++ b/build.zig @@ -461,6 +461,7 @@ pub fn build_exe( .{ .name = "syntax", .module = syntax_mod }, .{ .name = "dizzy", .module = dizzy_dep.module("dizzy") }, .{ .name = "fuzzig", .module = fuzzig_dep.module("fuzzig") }, + .{ .name = "git", .module = git_mod }, }, }); diff --git a/src/Project.zig b/src/Project.zig index 0aef063..1b5d07c 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -7,6 +7,7 @@ const dizzy = @import("dizzy"); const Buffer = @import("Buffer"); const fuzzig = @import("fuzzig"); const tracy = @import("tracy"); +const git = @import("git"); const builtin = @import("builtin"); const LSP = @import("LSP.zig"); @@ -24,6 +25,9 @@ persistent: bool = false, logger_lsp: log.Logger, logger_git: log.Logger, +workspace: ?[]const u8 = null, +branch: ?[]const u8 = null, + const Self = @This(); const OutOfMemoryError = error{OutOfMemory}; @@ -67,6 +71,8 @@ pub fn init(allocator: std.mem.Allocator, name: []const u8) OutOfMemoryError!Sel } pub fn deinit(self: *Self) void { + if (self.workspace) |p| self.allocator.free(p); + if (self.branch) |p| self.allocator.free(p); var i_ = self.file_language_server.iterator(); while (i_.next()) |p| { self.allocator.free(p.key_ptr.*); @@ -1850,3 +1856,26 @@ pub fn get_line(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 { } return allocator.dupe(u8, buf); } + +pub fn query_git(self: *Self) void { + git.workspace_path(@intFromPtr(self)) catch {}; + git.current_branch(@intFromPtr(self)) catch {}; +} + +pub fn process_git(self: *Self, m: tp.message) !void { + var value: []const u8 = undefined; + if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.null_ })) { + // 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); + 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); + } 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/git.zig b/src/git.zig index 80413ae..fb02212 100644 --- a/src/git.zig +++ b/src/git.zig @@ -7,37 +7,55 @@ pub const Error = error{ OutOfMemory, GitNotFound, GitCallFailed }; const log_execute = false; -pub fn workspace_path() Error!void { +pub fn workspace_path(context_: usize) Error!void { const fn_name = @src().fn_name; - try git(.{ "rev-parse", "--show-toplevel" }, struct { - fn result(parent: tp.pid_ref, output: []const u8) void { + try git(context_, .{ "rev-parse", "--show-toplevel" }, struct { + fn result(context: usize, parent: tp.pid_ref, output: []const u8) void { var it = std.mem.splitScalar(u8, output, '\n'); - while (it.next()) |branch| if (branch.len > 0) - parent.send(.{ module_name, fn_name, branch }) catch {}; + while (it.next()) |value| if (value.len > 0) + parent.send(.{ module_name, context, fn_name, value }) catch {}; } }.result, exit_null_on_error(fn_name)); } -pub fn current_branch() Error!void { +pub fn current_branch(context_: usize) Error!void { const fn_name = @src().fn_name; - try git(.{ "rev-parse", "--abbrev-ref", "HEAD" }, struct { - fn result(parent: tp.pid_ref, output: []const u8) void { + 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()) |branch| if (branch.len > 0) - parent.send(.{ module_name, fn_name, branch }) catch {}; + while (it.next()) |value| if (value.len > 0) + parent.send(.{ module_name, context, fn_name, value }) catch {}; + } + }.result, exit_null_on_error(fn_name)); +} + +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, 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_on_error(fn_name)); } fn git( + context: usize, cmd: anytype, out: OutputHandler, exit: ExitHandler, ) Error!void { - return git_err(cmd, out, noop, exit); + return git_err(context, cmd, out, noop, exit); } fn git_err( + context: usize, cmd: anytype, out: OutputHandler, err: OutputHandler, @@ -54,6 +72,7 @@ fn git_err( inline for (info.fields) |f| try cbor.writeValue(writer, @field(cmd, f.name)); return shell.execute(allocator, .{ .buf = buf.items }, .{ + .context = context, .out = to_shell_output_handler(out), .err = to_shell_output_handler(err), .exit = exit, @@ -74,18 +93,18 @@ fn exit_null_on_error(comptime tag: []const u8) shell.ExitHandler { }.exit; } -const OutputHandler = fn (parent: tp.pid_ref, output: []const u8) void; +const OutputHandler = fn (context: usize, parent: tp.pid_ref, output: []const u8) void; const ExitHandler = shell.ExitHandler; fn to_shell_output_handler(handler: anytype) shell.OutputHandler { return struct { - fn out(_: usize, parent: tp.pid_ref, _: []const u8, output: []const u8) void { - handler(parent, output); + fn out(context: usize, parent: tp.pid_ref, _: []const u8, output: []const u8) void { + handler(context, parent, output); } }.out; } -fn noop(_: tp.pid_ref, _: []const u8) void {} +fn noop(_: usize, _: tp.pid_ref, _: []const u8) void {} var git_path: ?struct { path: ?[:0]const u8 = null, diff --git a/src/project_manager.zig b/src/project_manager.zig index 705fedc..d389a37 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -325,6 +325,7 @@ const Process = struct { var text_len: usize = 0; var n: usize = 0; var task: []const u8 = undefined; + var context: usize = undefined; var root_dst: usize = 0; var root_src: usize = 0; @@ -341,6 +342,9 @@ const Process = struct { 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 {}; } else if (try cbor.match(m.buf, .{ "update_mru", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) { self.update_mru(project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{ "child", tp.extract(&project_directory), tp.extract(&language_server), "notify", tp.extract(&method), tp.extract_cbor(¶ms_cb) })) { @@ -423,6 +427,7 @@ const Process = struct { 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}); } diff --git a/src/tui/status/branch.zig b/src/tui/status/branch.zig index 6f1ffa5..f4bd492 100644 --- a/src/tui/status/branch.zig +++ b/src/tui/status/branch.zig @@ -29,7 +29,7 @@ pub fn create( .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), }; try tui.message_filters().add(MessageFilter.bind(self, receive_git)); - git.workspace_path() catch {}; + git.workspace_path(0) catch {}; return Widget.to(self); } @@ -51,11 +51,11 @@ fn process_git( m: tp.message, ) MessageFilter.Error!bool { var branch: []const u8 = undefined; - if (try match(m.buf, .{ any, "workspace_path", null_ })) { - self.branch = try self.allocator.dupe(u8, "null"); - } else if (try match(m.buf, .{ any, "workspace_path", string })) { - git.current_branch() catch {}; - } else if (try match(m.buf, .{ any, "current_branch", extract(&branch) })) { + if (try match(m.buf, .{ any, any, "workspace_path", null_ })) { + // do nothing, we do not have a git workspace + } else if (try match(m.buf, .{ any, any, "workspace_path", string })) { + git.current_branch(0) catch {}; + } else if (try match(m.buf, .{ any, any, "current_branch", extract(&branch) })) { if (self.branch) |p| self.allocator.free(p); self.branch = try self.allocator.dupe(u8, branch); } else {