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"),
("ilog", "interactive log explorer (TUI)"),
("ibranch", "interactive branch explorer (TUI)"),
("update", "update 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)]
pub shell: Shell,
#[arg(long)]
pub add: bool,
}
pub fn run(args: Args) {
if args.add {
install(args.shell);
} else {
print!("{}", 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)
{
println!(
"{} {}",
"✓ 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(()) => {
println!(
"{} {}",
"✓ added slu completion to".green(),
rc.display().to_string().bold()
);
println!(
"{}",
" 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}
"#
)
}