const VERBS: &[(&str, &str)] = &[
("search", "pickaxe a string through history"),
("scan", "audit repos for leaked secrets"),
("repos", "working-tree state across all repos"),
("sync", "fetch (and optionally fast-forward) all repos"),
("tidy", "find merged, safe-to-delete branches"),
("each", "run a git command in every repo"),
("trace", "a prettier history view"),
("irepos", "interactive repo explorer (TUI)"),
("ibranch", "interactive branch explorer (TUI)"),
("itag", "interactive tag explorer (TUI)"),
("ilog", "interactive log explorer (TUI)"),
("iscan", "interactive history search (TUI)"),
("istatus", "interactive status, stage and review (TUI)"),
("self", "update or check slu itself"),
("completions", "print a shell completion script"),
];
#[derive(Clone, Copy, clap::ValueEnum)]
pub enum Shell {
Bash,
Zsh,
Fish,
}
impl Shell {
fn script(self) -> String {
match self {
Shell::Bash => bash(),
Shell::Zsh => zsh(),
Shell::Fish => fish(),
}
}
fn rc_file(self) -> &'static str {
match self {
Shell::Bash => ".bashrc",
Shell::Zsh => ".zshrc",
Shell::Fish => ".config/fish/config.fish",
}
}
fn rc_line(self) -> &'static str {
match self {
Shell::Bash => "source <(slu completions bash)",
Shell::Zsh => "source <(slu completions zsh)",
Shell::Fish => "slu completions fish | source",
}
}
}
#[derive(clap::Args)]
pub struct Args {
#[arg(value_enum, value_name = "bash|zsh|fish")]
pub shell: Shell,
#[arg(long)]
pub add: bool,
}
pub fn run(args: Args) {
if args.add {
install(args.shell);
} else {
out!("{}", args.shell.script());
}
}
fn install(shell: Shell) {
use colored::Colorize;
let Some(home) = home_dir() else {
eprintln!("{}", "slu: could not find your home directory".red());
std::process::exit(1);
};
let rc = home.join(shell.rc_file());
let line = shell.rc_line();
if let Ok(existing) = std::fs::read_to_string(&rc)
&& existing.lines().any(|l| l.trim() == line)
{
outln!(
"{} {}",
"✓ already set up in".dimmed(),
rc.display().to_string().bold()
);
return;
}
if let Some(parent) = rc.parent()
&& let Err(e) = std::fs::create_dir_all(parent)
{
eprintln!("{} {e}", "slu: could not create".red());
std::process::exit(1);
}
let block = format!("\n# sluuz shell completion\n{line}\n");
match append(&rc, &block) {
Ok(()) => {
outln!(
"{} {}",
"✓ added slu completion to".green(),
rc.display().to_string().bold()
);
outln!(
"{}",
" restart your shell (or re-source the file) to use it.".dimmed()
);
}
Err(e) => {
eprintln!("{} {} - {e}", "slu: could not write".red(), rc.display());
std::process::exit(1);
}
}
}
fn append(path: &std::path::Path, text: &str) -> std::io::Result<()> {
use std::io::Write;
let mut f = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)?;
f.write_all(text.as_bytes())
}
fn home_dir() -> Option<std::path::PathBuf> {
std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(std::path::PathBuf::from)
}
fn verb_names() -> String {
VERBS
.iter()
.map(|(name, _)| *name)
.collect::<Vec<_>>()
.join(" ")
}
fn bash() -> String {
format!(
r#"# slu bash completion - `slu` is a git superset, so this reuses git's own
# completion (branches, tags, remotes, every git subcommand and flag) and adds
# slu's verbs on top.
# add to ~/.bashrc: source <(slu completions bash)
# git's bash completion is normally lazy-loaded - it only kicks in the first time
# you complete `git` itself. We complete `slu`, which never triggers that, so
# `__git_main` would never exist and there'd be nothing to delegate to. Force it
# to load up front: ask bash-completion's loader first, then fall back to known
# install paths.
if ! declare -F __git_main >/dev/null 2>&1; then
declare -F _completion_loader >/dev/null 2>&1 && _completion_loader git >/dev/null 2>&1
fi
if ! declare -F __git_main >/dev/null 2>&1; then
for __slu_gitc in \
/usr/share/bash-completion/completions/git \
/etc/bash_completion.d/git \
"$HOME/.local/share/bash-completion/completions/git"; do
[ -r "$__slu_gitc" ] && . "$__slu_gitc" && break
done
unset __slu_gitc
fi
# Let git set up its proper completion wrapper for `slu` (handles : and = in
# words correctly); harmless if git's completion still isn't loaded.
if declare -F __git_complete >/dev/null 2>&1; then
__git_complete slu __git_main >/dev/null 2>&1
fi
_slu() {{
# Everything git knows: branches, refs, subcommands, flags.
if declare -F __git_wrap__git_main >/dev/null 2>&1; then
__git_wrap__git_main
elif declare -F __git_main >/dev/null 2>&1; then
__git_main
fi
# slu's own verbs, only at the first argument.
if [ "${{COMP_CWORD:-0}}" -eq 1 ]; then
COMPREPLY+=( $(compgen -W '{verbs}' -- "${{COMP_WORDS[COMP_CWORD]}}") )
fi
}}
complete -o bashdefault -o default -o nospace -F _slu slu 2>/dev/null \
|| complete -o default -o nospace -F _slu slu
"#,
verbs = verb_names()
)
}
fn zsh() -> String {
let descs = VERBS
.iter()
.map(|(name, desc)| format!(" '{name}:{desc}'"))
.collect::<Vec<_>>()
.join("\n");
format!(
r#"#compdef slu
# slu zsh completion - `slu` is a git superset, so this reuses zsh's bundled
# git completion (branches, tags, remotes, flags) and adds slu's verbs on top.
# add to ~/.zshrc: source <(slu completions zsh)
_slu() {{
# slu's own verbs, only at the first argument.
if (( CURRENT == 2 )); then
local -a slu_cmds
slu_cmds=(
{descs}
)
_describe -t slu-commands 'slu command' slu_cmds
fi
# Everything git knows: complete as if we were git.
local service=git
_git
}}
compdef _slu slu
"#
)
}
fn fish() -> String {
let verbs = VERBS
.iter()
.map(|(name, desc)| {
format!("complete -c slu -n __fish_use_subcommand -a {name} -d '{desc}'")
})
.collect::<Vec<_>>()
.join("\n");
format!(
r#"# slu fish completion - `slu` is a git superset, so it inherits all of git's
# completions (branches, tags, remotes, flags) and adds slu's verbs on top.
# add to ~/.config/fish/config.fish: slu completions fish | source
complete -c slu -w git
{verbs}
"#
)
}