dotfiles/scripts/prompt/src/prompt.zig

380 lines
12 KiB
Zig
Raw Normal View History

2022-06-18 17:08:41 +02:00
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;
2022-06-18 17:08:41 +02:00
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 = "";
2023-06-08 19:27:16 +02:00
const home = "";
2022-06-18 18:16:41 +02:00
const root = "";
const watch = "";
2023-04-20 07:54:30 +02:00
const jobs = "";
2024-04-14 22:06:02 +02:00
const nix = "󱄅";
2022-06-18 17:08:41 +02:00
};
2023-04-20 00:05:06 +02:00
pub const Options = struct {
status: i16,
mode: FishMode,
duration: u32,
2023-04-20 07:54:30 +02:00
jobs: u32,
2024-04-14 22:06:02 +02:00
nix_name: ?[]const u8,
2023-04-20 00:05:06 +02:00
};
pub fn render(writer: anytype, options: Options) !void {
2023-05-15 11:35:54 +02:00
var renderer = Renderer(@TypeOf(writer)){
2022-06-18 17:08:41 +02:00
.last_style = null,
.writer = writer,
2023-04-20 00:05:06 +02:00
.options = options,
2023-05-15 11:35:54 +02:00
};
try renderer.render();
2022-06-18 17:08:41 +02:00
}
fn Renderer(comptime Writer: type) type {
return struct {
last_style: ?Style,
2022-06-18 17:08:41 +02:00
writer: Writer,
2023-04-20 00:05:06 +02:00
options: Options,
2022-06-18 17:08:41 +02:00
const Self = @This();
pub fn render(self: *Self) !void {
2023-07-01 19:20:02 +02:00
const left_color: Color = if (self.options.status == 0) .Green else .Red;
2022-06-18 17:08:41 +02:00
try self.setStyle(.{ .foreground = left_color });
try self.writer.writeAll(symbols.top_left);
try self.setStyle(.{ .background = left_color });
2023-04-20 00:05:06 +02:00
try self.renderDuration();
2023-04-20 07:54:30 +02:00
try self.renderJobs();
2024-04-14 22:06:02 +02:00
try self.renderNix();
2022-06-18 17:08:41 +02:00
try self.renderCwd();
self.renderGit() catch |err| {
switch (err) {
2022-06-18 18:16:41 +02:00
error.GitError => {}, // git error will be printed
2022-06-18 17:08:41 +02:00
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(" ");
2023-04-20 00:05:06 +02:00
const mode_color = self.options.mode.getColor();
2022-06-18 17:08:41 +02:00
try self.setStyle(.{
.foreground = left_color,
.background = mode_color,
.font_style = .{ .bold = true },
});
try self.writer.writeAll(symbols.right_separator ++ " ");
try self.setStyle(.{
2023-07-01 19:20:02 +02:00
.foreground = .Black,
2022-06-18 17:08:41 +02:00
.background = mode_color,
.font_style = .{ .bold = true },
});
2023-04-20 00:05:06 +02:00
try self.writer.writeAll(self.options.mode.getText());
2022-06-18 17:08:41 +02:00
try self.writer.writeAll(" ");
try self.setStyle(.{ .foreground = mode_color });
try self.writer.writeAll(symbols.right_separator ++ " ");
try self.setStyle(.{});
}
2023-04-20 00:05:06 +02:00
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,
2023-04-20 00:05:06 +02:00
.font_style = .{ .bold = true },
});
try self.writer.writeAll(" ");
try self.writer.writeAll(symbols.watch);
try self.writer.writeAll(" ");
2023-04-20 07:58:03 +02:00
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;
2023-04-20 00:05:06 +02:00
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) {
2023-04-20 07:58:03 +02:00
try self.writer.print("{}s ", .{seconds});
}
if (millis > 0 or seconds > 0 or minutes > 0 or hours > 0) {
try self.writer.print("{}ms", .{millis});
2023-04-20 00:05:06 +02:00
}
}
2023-04-20 07:54:30 +02:00
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 });
}
2024-04-14 22:06:02 +02:00
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 });
}
}
2022-06-18 17:08:41 +02:00
fn renderCwd(self: *Self) !void {
2022-10-24 21:50:05 +02:00
var cwd_buf: [512]u8 = undefined;
const cwd = try std.posix.getcwd(&cwd_buf);
2022-06-18 17:08:41 +02:00
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);
2022-10-24 21:50:05 +02:00
if (std.mem.startsWith(u8, cwd, home)) {
2022-06-18 17:08:41 +02:00
try self.setStyle(.{
.background = .Yellow,
.foreground = .Red,
2022-06-18 17:08:41 +02:00
});
2022-06-18 18:16:41 +02:00
try self.writer.writeAll(" " ++ symbols.home);
2022-10-24 21:50:05 +02:00
if (home.len != cwd.len) {
2022-06-18 18:16:41 +02:00
try self.renderPathSep();
2022-10-24 21:50:05 +02:00
try self.renderPath(cwd[(home.len + 1)..]);
2022-06-18 17:08:41 +02:00
}
written_path = true;
}
}
2022-06-18 18:16:41 +02:00
// write root-relative path
2022-06-18 17:08:41 +02:00
if (!written_path) {
try self.setStyle(.{
2023-07-01 19:20:02 +02:00
.background = .Yellow,
.foreground = .Red,
2022-06-18 17:08:41 +02:00
});
2022-06-18 18:16:41 +02:00
try self.writer.writeAll(" " ++ symbols.root);
// don't render separators when we're in /
2022-10-24 21:50:05 +02:00
if (cwd.len > 1) {
2022-06-18 18:16:41 +02:00
try self.renderPathSep();
2022-10-24 21:50:05 +02:00
try self.renderPath(cwd[1..]);
2022-06-18 18:16:41 +02:00
}
2022-06-18 17:08:41 +02:00
}
}
2022-06-18 18:16:41 +02:00
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,
2023-07-01 19:20:02 +02:00
.foreground = .Blue,
2022-06-18 18:16:41 +02:00
});
try self.writer.writeAll(" " ++ symbols.path_separator ++ " ");
try self.setStyle(.{
.background = self.last_style.?.background,
2023-07-01 19:20:02 +02:00
.foreground = .Black,
2022-06-18 18:16:41 +02:00
});
}
2022-06-18 17:08:41 +02:00
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;
2022-06-18 18:35:31 +02:00
const head_err = c.git_repository_head(&head, repo);
// branch with no commits
if (head_err == c.GIT_EUNBORNBRANCH) {
const bg = Color{ .Grey = 200 };
2022-06-18 18:35:31 +02:00
try self.drawLeftSep(bg);
try self.setStyle(.{
.background = bg,
2023-07-01 19:20:02 +02:00
.foreground = .Black,
2022-06-18 18:35:31 +02:00
.font_style = .{ .bold = true },
});
try self.writer.writeAll(" <new branch>");
return;
}
2022-06-18 17:08:41 +02:00
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,
2023-07-01 19:20:02 +02:00
.foreground = .Black,
2022-06-18 17:08:41 +02:00
.font_style = .{ .bold = true },
});
// using print here because name is a cstring
try self.writer.print(" {s}", .{name});
if (counts.staged > 0) {
2023-07-01 19:20:02 +02:00
try self.drawLeftSep(.Green);
2022-06-18 17:08:41 +02:00
try self.setStyle(.{
2023-07-01 19:20:02 +02:00
.background = .Green,
.foreground = .Black,
2022-06-18 17:08:41 +02:00
});
try self.writer.print(" {}{s}", .{ counts.staged, symbols.staged });
}
if (counts.unstaged > 0) {
2023-07-01 19:20:02 +02:00
try self.drawLeftSep(.Magenta);
2022-06-18 17:08:41 +02:00
try self.setStyle(.{
2023-07-01 19:20:02 +02:00
.background = .Magenta,
.foreground = .Black,
2022-06-18 17:08:41 +02:00
});
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);
2022-06-18 17:08:41 +02:00
self.last_style = style;
}
fn drawLeftSep(self: *Self, new_bg: Color) !void {
2022-06-18 17:08:41 +02:00
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;
2023-06-27 23:00:13 +02:00
const counts: *GitStatusCounts = @ptrCast(@alignCast(counts_));
2022-06-18 17:08:41 +02:00
if (flags & staged_flags > 0)
counts.staged += 1;
if (flags & unstaged_flags > 0)
counts.unstaged += 1;
return 0;
}
const GitStatusCounts = struct {
2023-07-01 19:20:02 +02:00
staged: u32 = 0,
unstaged: u32 = 0,
2022-06-18 17:08:41 +02:00
pub fn getColor(self: *GitStatusCounts) Color {
2022-06-18 17:08:41 +02:00
const has_staged = self.staged > 0;
const has_unstaged = self.unstaged > 0;
return if (!has_staged and !has_unstaged)
2023-07-01 19:20:02 +02:00
.Blue
2022-06-18 17:08:41 +02:00
else if (has_staged and has_unstaged)
2023-07-01 19:20:02 +02:00
.Magenta
2022-06-18 17:08:41 +02:00
else if (has_staged)
2023-07-01 19:20:02 +02:00
.Green
2022-06-18 17:08:41 +02:00
else
2023-07-01 19:20:02 +02:00
.{ .Grey = 200 };
2022-06-18 17:08:41 +02:00
}
};