mirror of
https://mzte.de/git/LordMZTE/dotfiles.git
synced 2024-05-17 06:23:50 +02:00
380 lines
12 KiB
Zig
380 lines
12 KiB
Zig
const std = @import("std");
|
||
const at = @import("ansi-term");
|
||
const known_folders = @import("known-folders");
|
||
const ffi = @import("ffi.zig");
|
||
const checkGitError = ffi.checkGitError;
|
||
const c = ffi.c;
|
||
const FishMode = @import("FishMode.zig");
|
||
|
||
const Style = at.style.Style;
|
||
const Color = at.style.Color;
|
||
|
||
const symbols = struct {
|
||
const left_separator = "";
|
||
const right_separator = "";
|
||
const top_left = "";
|
||
const bottom_left = "";
|
||
const path_separator = "❯";
|
||
const top_end = "";
|
||
const staged = "";
|
||
const unstaged = "";
|
||
const home = "";
|
||
const root = "";
|
||
const watch = "";
|
||
const jobs = "";
|
||
const nix = "";
|
||
};
|
||
|
||
pub const Options = struct {
|
||
status: i16,
|
||
mode: FishMode,
|
||
duration: u32,
|
||
jobs: u32,
|
||
nix_name: ?[]const u8,
|
||
};
|
||
|
||
pub fn render(writer: anytype, options: Options) !void {
|
||
var renderer = Renderer(@TypeOf(writer)){
|
||
.last_style = null,
|
||
.writer = writer,
|
||
.options = options,
|
||
};
|
||
try renderer.render();
|
||
}
|
||
|
||
fn Renderer(comptime Writer: type) type {
|
||
return struct {
|
||
last_style: ?Style,
|
||
writer: Writer,
|
||
options: Options,
|
||
|
||
const Self = @This();
|
||
|
||
pub fn render(self: *Self) !void {
|
||
const left_color: Color = if (self.options.status == 0) .Green else .Red;
|
||
|
||
try self.setStyle(.{ .foreground = left_color });
|
||
try self.writer.writeAll(symbols.top_left);
|
||
try self.setStyle(.{ .background = left_color });
|
||
try self.renderDuration();
|
||
try self.renderJobs();
|
||
try self.renderNix();
|
||
try self.renderCwd();
|
||
self.renderGit() catch |err| {
|
||
switch (err) {
|
||
error.GitError => {}, // git error will be printed
|
||
else => return err,
|
||
}
|
||
};
|
||
|
||
try self.writer.writeAll(" ");
|
||
try self.setStyle(.{ .foreground = self.last_style.?.background });
|
||
try self.writer.writeAll(symbols.top_end ++ "\n");
|
||
|
||
try self.setStyle(.{ .foreground = left_color });
|
||
try self.writer.writeAll(symbols.bottom_left);
|
||
try self.setStyle(.{ .foreground = left_color, .background = left_color });
|
||
try self.writer.writeAll(" ");
|
||
|
||
const mode_color = self.options.mode.getColor();
|
||
|
||
try self.setStyle(.{
|
||
.foreground = left_color,
|
||
.background = mode_color,
|
||
.font_style = .{ .bold = true },
|
||
});
|
||
try self.writer.writeAll(symbols.right_separator ++ " ");
|
||
try self.setStyle(.{
|
||
.foreground = .Black,
|
||
.background = mode_color,
|
||
.font_style = .{ .bold = true },
|
||
});
|
||
try self.writer.writeAll(self.options.mode.getText());
|
||
try self.writer.writeAll(" ");
|
||
try self.setStyle(.{ .foreground = mode_color });
|
||
try self.writer.writeAll(symbols.right_separator ++ " ");
|
||
try self.setStyle(.{});
|
||
}
|
||
|
||
fn renderDuration(self: *Self) !void {
|
||
if (self.options.duration < 2 * std.time.ms_per_s)
|
||
return;
|
||
|
||
try self.drawLeftSep(.Blue);
|
||
try self.setStyle(.{
|
||
.background = .Blue,
|
||
.foreground = .Black,
|
||
.font_style = .{ .bold = true },
|
||
});
|
||
try self.writer.writeAll(" ");
|
||
try self.writer.writeAll(symbols.watch);
|
||
try self.writer.writeAll(" ");
|
||
|
||
var total = self.options.duration;
|
||
|
||
const hours = total / std.time.ms_per_hour;
|
||
total -= hours * std.time.ms_per_hour;
|
||
|
||
const minutes = total / std.time.ms_per_min;
|
||
total -= minutes * std.time.ms_per_min;
|
||
|
||
const seconds = total / std.time.ms_per_s;
|
||
total -= seconds * std.time.ms_per_s;
|
||
|
||
const millis = total;
|
||
|
||
if (hours > 0) {
|
||
try self.writer.print("{}h ", .{hours});
|
||
}
|
||
|
||
if (minutes > 0 or hours > 0) {
|
||
try self.writer.print("{}min ", .{minutes});
|
||
}
|
||
|
||
if (seconds > 0 or minutes > 0 or hours > 0) {
|
||
try self.writer.print("{}s ", .{seconds});
|
||
}
|
||
|
||
if (millis > 0 or seconds > 0 or minutes > 0 or hours > 0) {
|
||
try self.writer.print("{}ms", .{millis});
|
||
}
|
||
}
|
||
|
||
fn renderJobs(self: *Self) !void {
|
||
if (self.options.jobs <= 0)
|
||
return;
|
||
|
||
try self.drawLeftSep(.Cyan);
|
||
try self.setStyle(.{ .background = .Cyan, .foreground = .Black });
|
||
|
||
try self.writer.print(" {s} {}", .{ symbols.jobs, self.options.jobs });
|
||
}
|
||
|
||
fn renderNix(self: *Self) !void {
|
||
if (self.options.nix_name) |name| {
|
||
try self.drawLeftSep(.Blue);
|
||
try self.setStyle(.{ .background = .Blue, .foreground = .Black});
|
||
|
||
try self.writer.print(" {s} {s}", .{symbols.nix, name });
|
||
}
|
||
}
|
||
|
||
fn renderCwd(self: *Self) !void {
|
||
var cwd_buf: [512]u8 = undefined;
|
||
const cwd = try std.posix.getcwd(&cwd_buf);
|
||
|
||
const home_path = (try known_folders.getPath(std.heap.c_allocator, .home));
|
||
|
||
try self.drawLeftSep(.{ .Yellow = {} });
|
||
var written_path = false;
|
||
if (home_path) |home| {
|
||
defer std.heap.c_allocator.free(home);
|
||
if (std.mem.startsWith(u8, cwd, home)) {
|
||
try self.setStyle(.{
|
||
.background = .Yellow,
|
||
.foreground = .Red,
|
||
});
|
||
try self.writer.writeAll(" " ++ symbols.home);
|
||
if (home.len != cwd.len) {
|
||
try self.renderPathSep();
|
||
try self.renderPath(cwd[(home.len + 1)..]);
|
||
}
|
||
written_path = true;
|
||
}
|
||
}
|
||
|
||
// write root-relative path
|
||
if (!written_path) {
|
||
try self.setStyle(.{
|
||
.background = .Yellow,
|
||
.foreground = .Red,
|
||
});
|
||
try self.writer.writeAll(" " ++ symbols.root);
|
||
|
||
// don't render separators when we're in /
|
||
if (cwd.len > 1) {
|
||
try self.renderPathSep();
|
||
try self.renderPath(cwd[1..]);
|
||
}
|
||
}
|
||
}
|
||
|
||
fn renderPath(self: *Self, path: []const u8) !void {
|
||
for (path) |byte|
|
||
if (byte == '/')
|
||
try self.renderPathSep()
|
||
else
|
||
try self.writer.writeByte(byte);
|
||
}
|
||
|
||
fn renderPathSep(self: *Self) !void {
|
||
try self.setStyle(.{
|
||
.background = self.last_style.?.background,
|
||
.foreground = .Blue,
|
||
});
|
||
|
||
try self.writer.writeAll(" " ++ symbols.path_separator ++ " ");
|
||
|
||
try self.setStyle(.{
|
||
.background = self.last_style.?.background,
|
||
.foreground = .Black,
|
||
});
|
||
}
|
||
|
||
fn renderGit(self: *Self) !void {
|
||
try checkGitError(c.git_libgit2_init());
|
||
defer _ = c.git_libgit2_shutdown();
|
||
|
||
var path_buf = std.mem.zeroes(c.git_buf);
|
||
defer c.git_buf_dispose(&path_buf);
|
||
if (c.git_repository_discover(&path_buf, ".", 1, null) < 0)
|
||
// no repo found
|
||
return;
|
||
|
||
var repo: ?*c.git_repository = null;
|
||
try checkGitError(c.git_repository_open(&repo, path_buf.ptr));
|
||
defer c.git_repository_free(repo);
|
||
|
||
var head: ?*c.git_reference = null;
|
||
const head_err = c.git_repository_head(&head, repo);
|
||
|
||
// branch with no commits
|
||
if (head_err == c.GIT_EUNBORNBRANCH) {
|
||
const bg = Color{ .Grey = 200 };
|
||
try self.drawLeftSep(bg);
|
||
try self.setStyle(.{
|
||
.background = bg,
|
||
.foreground = .Black,
|
||
.font_style = .{ .bold = true },
|
||
});
|
||
try self.writer.writeAll(" <new branch>");
|
||
|
||
return;
|
||
}
|
||
|
||
defer c.git_reference_free(head);
|
||
const name = c.git_reference_shorthand(head);
|
||
|
||
var status_options: c.git_status_options = undefined;
|
||
try checkGitError(c.git_status_options_init(
|
||
&status_options,
|
||
c.GIT_STATUS_OPTIONS_VERSION,
|
||
));
|
||
|
||
status_options.flags =
|
||
c.GIT_STATUS_OPT_INCLUDE_UNTRACKED |
|
||
c.GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
|
||
|
||
var status_list: ?*c.git_status_list = null;
|
||
try checkGitError(c.git_status_list_new(
|
||
&status_list,
|
||
repo,
|
||
&status_options,
|
||
));
|
||
|
||
var counts = GitStatusCounts{};
|
||
try checkGitError(c.git_status_foreach_ext(
|
||
repo,
|
||
&status_options,
|
||
gitStatusCb,
|
||
&counts,
|
||
));
|
||
|
||
// now render all that info!
|
||
const ref_bg = counts.getColor();
|
||
try self.drawLeftSep(ref_bg);
|
||
|
||
try self.setStyle(.{
|
||
.background = ref_bg,
|
||
.foreground = .Black,
|
||
.font_style = .{ .bold = true },
|
||
});
|
||
// using print here because name is a cstring
|
||
try self.writer.print(" {s}", .{name});
|
||
|
||
if (counts.staged > 0) {
|
||
try self.drawLeftSep(.Green);
|
||
try self.setStyle(.{
|
||
.background = .Green,
|
||
.foreground = .Black,
|
||
});
|
||
|
||
try self.writer.print(" {}{s}", .{ counts.staged, symbols.staged });
|
||
}
|
||
|
||
if (counts.unstaged > 0) {
|
||
try self.drawLeftSep(.Magenta);
|
||
try self.setStyle(.{
|
||
.background = .Magenta,
|
||
.foreground = .Black,
|
||
});
|
||
|
||
try self.writer.print(" {}{s}", .{ counts.unstaged, symbols.unstaged });
|
||
}
|
||
}
|
||
|
||
fn setStyle(self: *Self, style: Style) !void {
|
||
try at.format.updateStyle(self.writer, style, self.*.last_style);
|
||
self.last_style = style;
|
||
}
|
||
|
||
fn drawLeftSep(self: *Self, new_bg: Color) !void {
|
||
try self.writer.writeAll(" ");
|
||
try self.setStyle(.{
|
||
.background = self.last_style.?.background,
|
||
.foreground = new_bg,
|
||
});
|
||
try self.writer.writeAll(symbols.left_separator);
|
||
}
|
||
};
|
||
}
|
||
|
||
fn gitStatusCb(
|
||
_: [*c]const u8,
|
||
flags: c_uint,
|
||
counts_: ?*anyopaque,
|
||
) callconv(.C) c_int {
|
||
const staged_flags =
|
||
c.GIT_STATUS_INDEX_NEW |
|
||
c.GIT_STATUS_INDEX_MODIFIED |
|
||
c.GIT_STATUS_INDEX_DELETED |
|
||
c.GIT_STATUS_INDEX_RENAMED |
|
||
c.GIT_STATUS_INDEX_TYPECHANGE;
|
||
|
||
const unstaged_flags =
|
||
c.GIT_STATUS_WT_NEW |
|
||
c.GIT_STATUS_WT_MODIFIED |
|
||
c.GIT_STATUS_WT_DELETED |
|
||
c.GIT_STATUS_WT_RENAMED |
|
||
c.GIT_STATUS_WT_TYPECHANGE;
|
||
|
||
const counts: *GitStatusCounts = @ptrCast(@alignCast(counts_));
|
||
|
||
if (flags & staged_flags > 0)
|
||
counts.staged += 1;
|
||
|
||
if (flags & unstaged_flags > 0)
|
||
counts.unstaged += 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
const GitStatusCounts = struct {
|
||
staged: u32 = 0,
|
||
unstaged: u32 = 0,
|
||
|
||
pub fn getColor(self: *GitStatusCounts) Color {
|
||
const has_staged = self.staged > 0;
|
||
const has_unstaged = self.unstaged > 0;
|
||
|
||
return if (!has_staged and !has_unstaged)
|
||
.Blue
|
||
else if (has_staged and has_unstaged)
|
||
.Magenta
|
||
else if (has_staged)
|
||
.Green
|
||
else
|
||
.{ .Grey = 200 };
|
||
}
|
||
};
|