diff --git a/build.zig b/build.zig index 453ee81..cc54c43 100644 --- a/build.zig +++ b/build.zig @@ -587,6 +587,7 @@ pub fn build_exe( check_exe.root_module.addImport("input", input_mod); check_exe.root_module.addImport("syntax", syntax_mod); check_exe.root_module.addImport("color", color_mod); + check_exe.root_module.addImport("bin_path", bin_path_mod); check_exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file })); check_exe.root_module.addImport("version_info", b.createModule(.{ .root_source_file = version_info_file })); check_step.dependOn(&check_exe.step); diff --git a/src/git.zig b/src/git.zig index b2b4626..80413ae 100644 --- a/src/git.zig +++ b/src/git.zig @@ -3,30 +3,101 @@ const tp = @import("thespian"); const shell = @import("shell"); const bin_path = @import("bin_path"); +pub const Error = error{ OutOfMemory, GitNotFound, GitCallFailed }; + +const log_execute = false; + +pub fn workspace_path() 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 { + var it = std.mem.splitScalar(u8, output, '\n'); + while (it.next()) |branch| if (branch.len > 0) + parent.send(.{ module_name, fn_name, branch }) catch {}; + } + }.result, exit_null_on_error(fn_name)); +} + +pub fn current_branch() 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 { + var it = std.mem.splitScalar(u8, output, '\n'); + while (it.next()) |branch| if (branch.len > 0) + parent.send(.{ module_name, fn_name, branch }) catch {}; + } + }.result, exit_null_on_error(fn_name)); +} + +fn git( + cmd: anytype, + out: OutputHandler, + exit: ExitHandler, +) Error!void { + return git_err(cmd, out, noop, exit); +} + +fn git_err( + cmd: anytype, + out: OutputHandler, + err: OutputHandler, + exit: ExitHandler, +) Error!void { + const cbor = @import("cbor"); + const git_binary = get_git() orelse return error.GitNotFound; + var buf: std.ArrayListUnmanaged(u8) = .empty; + const writer = buf.writer(allocator); + switch (@typeInfo(@TypeOf(cmd))) { + .@"struct" => |info| if (info.is_tuple) { + try cbor.writeArrayHeader(writer, info.fields.len + 1); + try cbor.writeValue(writer, git_binary); + inline for (info.fields) |f| + try cbor.writeValue(writer, @field(cmd, f.name)); + return shell.execute(allocator, .{ .buf = buf.items }, .{ + .out = to_shell_output_handler(out), + .err = to_shell_output_handler(err), + .exit = exit, + .log_execute = log_execute, + }) catch error.GitCallFailed; + }, + else => {}, + } + @compileError("git command should be a tuple: " ++ @typeName(@TypeOf(cmd))); +} + +fn exit_null_on_error(comptime tag: []const u8) shell.ExitHandler { + return struct { + fn exit(_: usize, parent: tp.pid_ref, _: []const u8, _: []const u8, exit_code: i64) void { + if (exit_code > 0) + parent.send(.{ module_name, tag, null }) catch {}; + } + }.exit; +} + +const OutputHandler = fn (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); + } + }.out; +} + +fn noop(_: tp.pid_ref, _: []const u8) void {} + var git_path: ?struct { path: ?[:0]const u8 = null, } = null; +const allocator = std.heap.c_allocator; + fn get_git() ?[]const u8 { if (git_path) |p| return p.path; - const path = bin_path.find_binary_in_path(std.heap.c_allocator, "git") catch null; + const path = bin_path.find_binary_in_path(allocator, module_name) catch null; git_path = .{ .path = path }; return path; } -pub fn get_current_branch(allocator: std.mem.Allocator) !void { - const git_binary = get_git() orelse return error.GitBinaryNotFound; - const git_current_branch_cmd = tp.message.fmt(.{ git_binary, "rev-parse", "--abbrev-ref", "HEAD" }); - const handlers = struct { - fn out(_: usize, parent: tp.pid_ref, _: []const u8, output: []const u8) void { - var it = std.mem.splitScalar(u8, output, '\n'); - while (it.next()) |branch| if (branch.len > 0) - parent.send(.{ "git", "current_branch", branch }) catch {}; - } - }; - try shell.execute(allocator, git_current_branch_cmd, .{ - .out = handlers.out, - .err = shell.log_err_handler, - .exit = shell.log_exit_err_handler, - }); -} +const module_name = @typeName(@This()); diff --git a/src/shell.zig b/src/shell.zig index c4ee5ca..5a8424d 100644 --- a/src/shell.zig +++ b/src/shell.zig @@ -36,6 +36,7 @@ pub const Handlers = struct { out: *const OutputHandler, err: ?*const OutputHandler = null, exit: *const ExitHandler = log_exit_handler, + log_execute: bool = true, }; pub fn execute(allocator: std.mem.Allocator, argv: tp.message, handlers: Handlers) Error!void { @@ -185,7 +186,8 @@ const Process = struct { _ = tp.set_trap(true); var buf: [1024]u8 = undefined; const json = self.argv.to_json(&buf) catch |e| return tp.exit_error(e, @errorReturnTrace()); - self.logger.print("shell: execute {s}", .{json}); + if (self.handlers.log_execute) + self.logger.print("shell: execute {s}", .{json}); self.sp = tp.subprocess.init(self.allocator, self.argv, module_name, self.stdin_behavior) catch |e| return tp.exit_error(e, @errorReturnTrace()); tp.receive(&self.receiver); } diff --git a/src/tui/mode/mini/goto.zig b/src/tui/mode/mini/goto.zig index d5023c7..ec18e48 100644 --- a/src/tui/mode/mini/goto.zig +++ b/src/tui/mode/mini/goto.zig @@ -131,4 +131,9 @@ const cmds = struct { self.goto(); } pub const mini_mode_insert_bytes_meta: Meta = .{ .arguments = &.{.string} }; + + pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result { + return mini_mode_insert_bytes(self, ctx); + } + pub const mini_mode_paste_meta: Meta = .{ .arguments = &.{.string} }; }; diff --git a/src/tui/status/branch.zig b/src/tui/status/branch.zig index 86b7295..6f1ffa5 100644 --- a/src/tui/status/branch.zig +++ b/src/tui/status/branch.zig @@ -1,6 +1,5 @@ const std = @import("std"); const tp = @import("thespian"); -const cbor = @import("cbor"); const EventHandler = @import("EventHandler"); const Plane = @import("renderer").Plane; @@ -18,14 +17,19 @@ branch: ?[]const u8 = null, const Self = @This(); -pub fn create(allocator: std.mem.Allocator, parent: Plane, _: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget { +pub fn create( + allocator: std.mem.Allocator, + parent: Plane, + _: ?EventHandler, + _: ?[]const u8, +) @import("widget.zig").CreateError!Widget { const self: *Self = try allocator.create(Self); self.* = .{ .allocator = allocator, .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), }; try tui.message_filters().add(MessageFilter.bind(self, receive_git)); - git.get_current_branch(self.allocator) catch {}; + git.workspace_path() catch {}; return Widget.to(self); } @@ -36,13 +40,28 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { } fn receive_git(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool { + return if (try match(m.buf, .{ "git", more })) + self.process_git(m) + else + false; +} + +fn process_git( + self: *Self, + m: tp.message, +) MessageFilter.Error!bool { var branch: []const u8 = undefined; - if (try cbor.match(m.buf, .{ "git", "current_branch", tp.extract(&branch) })) { + 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 (self.branch) |p| self.allocator.free(p); self.branch = try self.allocator.dupe(u8, branch); - return true; + } else { + return false; } - return false; + return true; } pub fn layout(self: *Self) Widget.Layout { @@ -66,3 +85,12 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool { _ = self.plane.print("{s} {s}", .{ branch_symbol, branch }) catch {}; return false; } + +const match = cbor.match; +const more = cbor.more; +const null_ = cbor.null_; +const string = cbor.string; +const extract = cbor.extract; +const any = cbor.any; + +const cbor = @import("cbor");