rewrite prompt in zig

This commit is contained in:
LordMZTE 2022-06-18 17:08:41 +02:00
parent d7a535968c
commit f4e7597014
Signed by: LordMZTE
GPG key ID: B64802DC33A64FF6
13 changed files with 426 additions and 224 deletions

View file

@ -55,10 +55,7 @@ function todos
end
# install custom prompt to ~/.local/bin/prompt (or somewhere else in $PATH)
functions -e fish_mode_prompt
function fish_prompt
prompt $status $fish_bind_mode
end
prompt printfish | source
# custom title
functions -e fish_title

View file

@ -35,7 +35,5 @@ install-lsps-paru:
fi
install-prompt:
RUSTFLAGS="-C target-cpu=native" cargo build --release \
--manifest-path prompt/Cargo.toml
strip prompt/target/release/prompt
cp prompt/target/release/prompt ~/.local/bin
cd prompt/ && gyro build -Drelease-fast
cp prompt/zig-out/bin/prompt ~/.local/bin

6
prompt/.gitignore vendored
View file

@ -1,2 +1,4 @@
target/
Cargo.lock
zig-*/
.gyro/
deps.zig
gyro.lock

View file

@ -1,15 +0,0 @@
[package]
name = "prompt"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
lto = true
panic = "abort"
[dependencies.powerline]
git = "https://github.com/Gajus84/powerline-rust.git"
default-features = false
features = ["bare-shell", "libgit"]

41
prompt/build.zig Normal file
View file

@ -0,0 +1,41 @@
const std = @import("std");
const pkgs = @import("deps.zig").pkgs;
pub fn build(b: *std.build.Builder) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
const exe = b.addExecutable("prompt", "src/main.zig");
exe.setTarget(target);
exe.setBuildMode(mode);
exe.strip = mode != .Debug;
exe.linkLibC();
exe.linkSystemLibrary("libgit2");
pkgs.addAllTo(exe);
exe.install();
const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const exe_tests = b.addTest("src/main.zig");
exe_tests.setTarget(target);
exe_tests.setBuildMode(mode);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&exe_tests.step);
}

11
prompt/gyro.zzz Normal file
View file

@ -0,0 +1,11 @@
deps:
ansi-term:
git:
url: "https://github.com/ziglibs/ansi-term.git"
ref: 1c4b9aa23e159b297e4008181ff649c246dc8abe
root: src/main.zig
known-folders:
git:
url: "https://github.com/ziglibs/known-folders.git"
ref: 9db1b99219c767d5e24994b1525273fe4031e464
root: known-folders.zig

48
prompt/src/FishMode.zig Normal file
View file

@ -0,0 +1,48 @@
const std = @import("std");
const at = @import("ansi-term");
pub const Mode = enum {
default,
insert,
replace_one,
replace,
visual,
unknown,
};
mode: Mode,
const Self = @This();
pub fn parse(s: []const u8) Self {
inline for (@typeInfo(Mode).Enum.fields) |field, i| {
if (std.mem.eql(u8, field.name, s))
return .{ .mode = @intToEnum(Mode, i) };
}
return .{ .mode = .unknown };
}
/// Gets the color for the mode
pub fn getColor(self: *Self) at.Color {
return switch (self.mode) {
.default => .{ .Yellow = {} },
.insert => .{ .Green = {} },
.replace_one => .{ .Magenta = {} },
.replace => .{ .Blue = {} },
.visual => .{ .Magenta = {} },
.unknown => .{ .Red = {} },
};
}
/// Gets a string to show for the mode.
/// Returned string is static
pub fn getText(self: *Self) []const u8 {
return switch (self.mode) {
.default => "N",
.insert => "I",
.replace_one, .replace => "R",
.visual => "V",
.unknown => "?",
};
}

17
prompt/src/ffi.zig Normal file
View file

@ -0,0 +1,17 @@
const std = @import("std");
pub const c = @cImport({
@cInclude("git2.h");
});
pub fn checkGitError(errno: c_int) !void {
if (errno < 0) {
const err = c.git_error_last();
// TODO: this looks terrible. save to buf or something
std.log.err(
"libgit2 error: {}/{}: {s}",
.{ errno, err.*.klass, err.*.message },
);
return error.GitError;
}
}

View file

@ -1,102 +0,0 @@
use std::marker::PhantomData;
use powerline::{modules::Module, terminal::Color, Segment};
pub trait FishModeScheme {
const FISH_MODE_DEFAULT_BG: Color;
const FISH_MODE_DEFAULT_FG: Color;
const FISH_MODE_DEFAULT_STR: &'static str = "N";
const FISH_MODE_INSERT_BG: Color;
const FISH_MODE_INSERT_FG: Color;
const FISH_MODE_INSERT_STR: &'static str = "I";
const FISH_MODE_REPLACE_ONE_BG: Color;
const FISH_MODE_REPLACE_ONE_FG: Color;
const FISH_MODE_REPLACE_ONE_STR: &'static str = "R";
const FISH_MODE_REPLACE_BG: Color;
const FISH_MODE_REPLACE_FG: Color;
const FISH_MODE_REPLACE_STR: &'static str = "R";
const FISH_MODE_VISUAL_BG: Color;
const FISH_MODE_VISUAL_FG: Color;
const FISH_MODE_VISUAL_STR: &'static str = "V";
const FISH_MODE_UNKNOWN_BG: Color;
const FISH_MODE_UNKNOWN_FG: Color;
const FISH_MODE_UNKNOWN_STR: &'static str = "?";
}
pub struct FishMode<T> {
mode: FishModeMode,
scheme: PhantomData<T>,
}
impl<T: FishModeScheme> FishMode<T> {
pub fn new() -> Self {
Self {
mode: std::env::args()
.nth(2)
.map(|s| FishModeMode::from_fish_bind_mode(&s))
.unwrap_or(FishModeMode::Unknown),
scheme: PhantomData,
}
}
}
impl<T: FishModeScheme> Module for FishMode<T> {
fn append_segments(&mut self, segments: &mut Vec<Segment>) {
let (s, bg, fg) = match self.mode {
FishModeMode::Default => (
T::FISH_MODE_DEFAULT_STR,
T::FISH_MODE_DEFAULT_BG,
T::FISH_MODE_DEFAULT_FG,
),
FishModeMode::Insert => (
T::FISH_MODE_INSERT_STR,
T::FISH_MODE_INSERT_BG,
T::FISH_MODE_INSERT_FG,
),
FishModeMode::Replace => (
T::FISH_MODE_REPLACE_STR,
T::FISH_MODE_REPLACE_BG,
T::FISH_MODE_REPLACE_FG,
),
FishModeMode::ReplaceOne => (
T::FISH_MODE_REPLACE_ONE_STR,
T::FISH_MODE_REPLACE_ONE_BG,
T::FISH_MODE_REPLACE_ONE_FG,
),
FishModeMode::Visual => (
T::FISH_MODE_VISUAL_STR,
T::FISH_MODE_VISUAL_BG,
T::FISH_MODE_VISUAL_FG,
),
FishModeMode::Unknown => (
T::FISH_MODE_UNKNOWN_STR,
T::FISH_MODE_UNKNOWN_BG,
T::FISH_MODE_UNKNOWN_FG,
),
};
segments.push(Segment::simple(s, fg, bg));
}
}
enum FishModeMode {
Default,
Insert,
ReplaceOne,
Replace,
Visual,
Unknown,
}
impl FishModeMode {
fn from_fish_bind_mode(mode: &str) -> Self {
match mode {
"default" => Self::Default,
"insert" => Self::Insert,
"replace_one" => Self::ReplaceOne,
"replace" => Self::Replace,
"visual" => Self::Visual,
_ => Self::Unknown,
}
}
}

View file

@ -1,22 +0,0 @@
use powerline::{
modules::{Cmd, Cwd, ExitCode, Git, ReadOnly},
Powerline,
};
use crate::{fish_mode::FishMode, theme::Theme};
mod fish_mode;
mod theme;
fn main() {
let mut prompt = Powerline::new();
prompt.add_module(ReadOnly::<Theme>::new());
prompt.add_module(Cwd::<Theme>::new(40, 5, false));
prompt.add_module(Git::<Theme>::new());
prompt.add_module(FishMode::<Theme>::new());
prompt.add_module(ExitCode::<Theme>::new());
prompt.add_module(Cmd::<Theme>::new());
println!("{}", prompt);
}

29
prompt/src/main.zig Normal file
View file

@ -0,0 +1,29 @@
const std = @import("std");
const FishMode = @import("FishMode.zig");
const prompt = @import("prompt.zig");
const fish_code =
\\functions -e fish_mode_prompt
\\function fish_prompt
\\ {s} show $status $fish_bind_mode
\\end
;
pub fn main() !void {
if (std.os.argv.len < 2)
return error.NotEnoughArguments;
if (std.cstr.cmp(std.os.argv[1], "printfish") == 0) {
const stdout = std.io.getStdOut();
try stdout.writer().print(fish_code ++ "\n", .{std.os.argv[0]});
} else if (std.cstr.cmp(std.os.argv[1], "show") == 0) {
if (std.os.argv.len < 4)
return error.NotEnoughArguments;
const status = try std.fmt.parseInt(i16, std.mem.sliceTo(std.os.argv[2], 0), 10);
const mode = FishMode.parse(std.mem.sliceTo(std.os.argv[3], 0));
try prompt.render(std.io.getStdOut().writer(), status, mode);
} else {
return error.UnknownCommand;
}
}

273
prompt/src/prompt.zig Normal file
View file

@ -0,0 +1,273 @@
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 symbols = struct {
const left_separator = "";
const right_separator = "";
const top_left = "";
const bottom_left = "";
const path_separator = "";
const top_end = "";
const staged = "";
const unstaged = "";
};
pub fn render(writer: anytype, status: i16, mode: FishMode) !void {
_ = status;
_ = mode;
try (Renderer(@TypeOf(writer)){
.last_style = null,
.writer = writer,
.status = status,
.mode = mode,
}).render();
}
fn Renderer(comptime Writer: type) type {
return struct {
last_style: ?at.Style,
writer: Writer,
status: i16,
mode: FishMode,
const Self = @This();
pub fn render(self: *Self) !void {
//const alloc = std.heap.c_allocator;
const left_color = if (self.status == 0)
at.Color{ .Green = {} }
else
at.Color{ .Red = {} };
try self.setStyle(.{ .foreground = left_color });
try self.writer.writeAll(symbols.top_left);
try self.setStyle(.{ .background = left_color });
try self.renderCwd();
self.renderGit() catch |err| {
switch (err) {
error.GitError => {},
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.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.mode.getText());
try self.writer.writeAll(" ");
try self.setStyle(.{ .foreground = mode_color });
try self.writer.writeAll(symbols.right_separator ++ " ");
try self.setStyle(.{});
}
// TODO: fancify (using symbols.path_separator) + some formatting
fn renderCwd(self: *Self) !void {
const pwd = std.fs.cwd();
const realpath = try pwd.realpathAlloc(std.heap.c_allocator, ".");
defer std.heap.c_allocator.free(realpath);
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, realpath, home)) {
try self.setStyle(.{
.background = .{ .Yellow = {} },
.foreground = .{ .Magenta = {} },
.font_style = .{ .bold = true },
});
try self.writer.writeAll(" ~/");
if (home.len != realpath.len) {
try self.setStyle(.{
.background = .{ .Yellow = {} },
.foreground = .{ .Black = {} },
});
try self.writer.writeAll(realpath[(home.len + 1)..]);
}
written_path = true;
}
}
if (!written_path) {
try self.setStyle(.{
.background = .{ .Yellow = {} },
.foreground = .{ .Black = {} },
});
try self.writer.writeAll(" ");
try self.writer.writeAll(realpath);
}
}
fn renderGit(self: *Self) !void {
_ = self;
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;
try checkGitError(c.git_repository_head(&head, repo));
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: at.Style) !void {
try at.updateStyle(self.writer, style, self.*.last_style);
self.last_style = style;
}
fn drawLeftSep(self: *Self, new_bg: at.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 = @ptrCast(
*GitStatusCounts,
@alignCast(@alignOf(GitStatusCounts), counts_),
);
if (flags & staged_flags > 0)
counts.staged += 1;
if (flags & unstaged_flags > 0)
counts.unstaged += 1;
return 0;
}
const GitStatusCounts = struct {
staged: c_int = 0,
unstaged: c_int = 0,
pub fn getColor(self: *GitStatusCounts) at.Color {
const has_staged = self.staged > 0;
const has_unstaged = self.unstaged > 0;
return if (!has_staged and !has_unstaged)
at.Color{ .Blue = {} }
else if (has_staged and has_unstaged)
at.Color{ .Magenta = {} }
else if (has_staged)
at.Color{ .Green = {} }
else
at.Color{ .Grey = 200 };
}
};

View file

@ -1,75 +0,0 @@
use powerline::{
modules::{CmdScheme, CwdScheme, ExitCodeScheme, GitScheme, ReadOnlyScheme},
terminal::Color,
};
use crate::fish_mode::FishModeScheme;
// convenience alias
const fn c(color: u8) -> Color {
Color(color)
}
pub struct Theme;
impl CmdScheme for Theme {
const CMD_PASSED_FG: Color = c(4);
const CMD_PASSED_BG: Color = c(2);
const CMD_FAILED_BG: Color = c(1);
const CMD_FAILED_FG: Color = c(7);
}
impl CwdScheme for Theme {
const CWD_FG: Color = c(0);
const PATH_FG: Color = c(0);
const PATH_BG: Color = c(3);
const HOME_FG: Color = c(0);
const HOME_BG: Color = c(5);
const SEPARATOR_FG: Color = c(4);
}
impl GitScheme for Theme {
const GIT_AHEAD_BG: Color = c(2);
const GIT_AHEAD_FG: Color = c(0);
const GIT_BEHIND_BG: Color = c(4);
const GIT_BEHIND_FG: Color = c(0);
const GIT_STAGED_BG: Color = c(6);
const GIT_STAGED_FG: Color = c(0);
const GIT_NOTSTAGED_BG: Color = c(4);
const GIT_NOTSTAGED_FG: Color = c(0);
const GIT_UNTRACKED_BG: Color = c(69);
const GIT_UNTRACKED_FG: Color = c(0);
const GIT_CONFLICTED_BG: Color = c(1);
const GIT_CONFLICTED_FG: Color = c(0);
const GIT_REPO_CLEAN_BG: Color = c(4);
const GIT_REPO_CLEAN_FG: Color = c(0);
const GIT_REPO_DIRTY_BG: Color = c(250);
const GIT_REPO_DIRTY_FG: Color = c(0);
const GIT_REPO_ERROR_BG: Color = c(196);
const GIT_REPO_ERROR_FG: Color = c(0);
}
impl ExitCodeScheme for Theme {
const EXIT_CODE_BG: Color = c(5);
const EXIT_CODE_FG: Color = c(0);
}
impl ReadOnlyScheme for Theme {
const READONLY_FG: Color = c(1);
const READONLY_BG: Color = c(0);
}
impl FishModeScheme for Theme {
const FISH_MODE_DEFAULT_BG: Color = c(3);
const FISH_MODE_DEFAULT_FG: Color = c(0);
const FISH_MODE_INSERT_BG: Color = c(2);
const FISH_MODE_INSERT_FG: Color = c(0);
const FISH_MODE_REPLACE_ONE_BG: Color = c(5);
const FISH_MODE_REPLACE_ONE_FG: Color = c(0);
const FISH_MODE_REPLACE_BG: Color = c(4);
const FISH_MODE_REPLACE_FG: Color = c(0);
const FISH_MODE_VISUAL_BG: Color = c(5);
const FISH_MODE_VISUAL_FG: Color = c(0);
const FISH_MODE_UNKNOWN_BG: Color = c(1);
const FISH_MODE_UNKNOWN_FG: Color = c(0);
}