use clap::CommandFactory;
use clap_complete::{generate, Shell};
use crate::cli::{Cli, CompletionShell, ShellType};
use crate::error::Result;
use crate::shell::get_gwm_bin_expr;
const EDITOR_OPTIONS: &str = "code cursor zed";
pub fn run_completion(shell: CompletionShell, with_dynamic: bool) -> Result<()> {
let script = generate_completion(shell, with_dynamic);
print!("{}", script);
Ok(())
}
fn generate_completion(shell: CompletionShell, with_dynamic: bool) -> String {
match shell {
CompletionShell::Bash => generate_bash(with_dynamic),
CompletionShell::Zsh => generate_zsh(with_dynamic),
CompletionShell::Fish => generate_fish(with_dynamic),
}
}
pub fn generate_for_shell_type(shell: ShellType) -> String {
match shell {
ShellType::Bash => generate_bash(true),
ShellType::Zsh => generate_zsh(true),
ShellType::Fish => generate_fish(true),
}
}
fn generate_static_completion(shell: Shell) -> String {
let mut cmd = Cli::command();
let mut buf = Vec::new();
generate(shell, &mut cmd, "gwm", &mut buf);
match String::from_utf8(buf) {
Ok(script) => script,
Err(e) => {
eprintln!("Warning: Failed to generate completion script: {}", e);
"# Error: Failed to generate completion script\n".to_string()
}
}
}
fn generate_bash(with_dynamic: bool) -> String {
if with_dynamic {
generate_bash_dynamic()
} else {
generate_static_completion(Shell::Bash)
}
}
fn generate_bash_dynamic() -> String {
let gwm_bin = get_gwm_bin_expr();
format!(
r#"# gwm - Git Worktree Manager completion script (with dynamic completion)
_gwm_worktrees() {{
local gwm_bin={gwm_bin}
"$gwm_bin" list --format=names 2>/dev/null
}}
_gwm_branches() {{
git branch --format='%(refname:short)' 2>/dev/null
}}
_gwm() {{
local cur="${{COMP_WORDS[COMP_CWORD]}}"
local prev="${{COMP_WORDS[COMP_CWORD-1]}}"
local cmd="${{COMP_WORDS[1]}}"
# First argument: subcommand
if [[ $COMP_CWORD -eq 1 ]]; then
COMPREPLY=($(compgen -W "add go init list ls sync remove rm clean completion help" -- "$cur"))
return 0
fi
# Handle subcommand-specific completions
case "$cmd" in
go)
case "$prev" in
-o|--open)
COMPREPLY=($(compgen -W "{EDITOR_OPTIONS}" -- "$cur"))
return 0
;;
*)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-o --open" -- "$cur"))
else
local worktrees
worktrees=$(_gwm_worktrees)
COMPREPLY=($(compgen -W "$worktrees" -- "$cur"))
fi
return 0
;;
esac
;;
remove|rm)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-f --force" -- "$cur"))
else
local worktrees
worktrees=$(_gwm_worktrees)
COMPREPLY=($(compgen -W "$worktrees" -- "$cur"))
fi
return 0
;;
add)
case "$prev" in
-f|--from)
local branches
branches=$(_gwm_branches)
COMPREPLY=($(compgen -W "$branches" -- "$cur"))
return 0
;;
-o|--open)
COMPREPLY=($(compgen -W "{EDITOR_OPTIONS}" -- "$cur"))
return 0
;;
*)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-r --remote --from -o --open --no-cd --skip-hooks" -- "$cur"))
fi
return 0
;;
esac
;;
list|ls)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-c --compact --format" -- "$cur"))
elif [[ "$prev" == "--format" ]]; then
COMPREPLY=($(compgen -W "table json names" -- "$cur"))
fi
return 0
;;
clean)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-f --force -n --dry-run" -- "$cur"))
fi
return 0
;;
init|completion)
if [[ "$cur" == -* ]]; then
if [[ "$cmd" == "completion" ]]; then
COMPREPLY=($(compgen -W "--with-dynamic" -- "$cur"))
fi
else
COMPREPLY=($(compgen -W "bash zsh fish" -- "$cur"))
fi
return 0
;;
help)
COMPREPLY=($(compgen -W "add go init list sync remove clean completion" -- "$cur"))
return 0
;;
esac
}}
complete -F _gwm gwm
"#
)
}
fn generate_zsh(with_dynamic: bool) -> String {
if with_dynamic {
generate_zsh_dynamic()
} else {
generate_static_completion(Shell::Zsh)
}
}
fn generate_zsh_dynamic() -> String {
let gwm_bin = get_gwm_bin_expr();
format!(
r##"#compdef gwm
# gwm - Git Worktree Manager completion script (with dynamic completion)
_gwm_worktrees() {{
local gwm_bin={gwm_bin}
local -a worktrees
worktrees=(${{(f)"$("$gwm_bin" list --format=names 2>/dev/null)"}})
if [[ $#worktrees -gt 0 ]]; then
_describe -t worktrees 'worktree' worktrees
fi
}}
_gwm_branches() {{
local -a branches
branches=(${{(f)"$(git branch --format='%(refname:short)' 2>/dev/null)"}})
if [[ $#branches -gt 0 ]]; then
_describe -t branches 'branch' branches
fi
}}
_gwm_editors() {{
local -a editors
editors=(
'code:Visual Studio Code'
'cursor:Cursor'
'zed:Zed'
)
_describe -t editors 'editor' editors
}}
_gwm_shells() {{
local -a shells
shells=(bash zsh fish)
_describe -t shells 'shell' shells
}}
_gwm() {{
local context state state_descr line
typeset -A opt_args
_arguments -C \
'1: :->command' \
'*:: :->args'
case $state in
command)
local -a commands
commands=(
'add:Create a new worktree'
'go:Go to a worktree directory or open it in an editor'
'init:Print shell integration script'
'list:List all worktrees for the current project'
'ls:List all worktrees (alias for list)'
'sync:Update the main branch worktrees'
'remove:Remove one or more worktrees'
'rm:Remove worktrees (alias for remove)'
'clean:Clean up safe-to-delete worktrees'
'completion:Generate shell completion scripts'
'help:Show help for gwm or a specific command'
)
_describe -t commands 'gwm command' commands
;;
args)
case $words[1] in
go)
_arguments \
'(-o --open)'{{-o,--open}}'[Open in editor]:editor:_gwm_editors' \
'1: :_gwm_worktrees'
;;
remove|rm)
_arguments \
'(-f --force)'{{-f,--force}}'[Force removal without confirmation]' \
'*: :_gwm_worktrees'
;;
add)
_arguments \
'(-r --remote)'{{-r,--remote}}'[Use a remote branch]' \
'--from[Base branch to create from]:branch:_gwm_branches' \
'(-o --open)'{{-o,--open}}'[Open in editor after creation]:editor:_gwm_editors' \
'--no-cd[Show success message instead of path output]' \
'--skip-hooks[Skip post_create hooks]' \
'1:branch name:'
;;
list|ls)
_arguments \
'(-c --compact)'{{-c,--compact}}'[Compact output]' \
'--format[Output format]:(table json names)'
;;
clean)
_arguments \
'(-f --force)'{{-f,--force}}'[Force cleanup without confirmation]' \
'(-n --dry-run)'{{-n,--dry-run}}'[Show what would be deleted]'
;;
init)
_arguments \
'1:shell:_gwm_shells'
;;
completion)
_arguments \
'--with-dynamic[Enable dynamic completion]' \
'1:shell:_gwm_shells'
;;
help)
local -a help_topics
help_topics=(add go init list sync remove clean completion)
_describe -t topics 'topic' help_topics
;;
esac
;;
esac
}}
# Register the completion function (required for eval)
compdef _gwm gwm
"##
)
}
fn generate_fish(with_dynamic: bool) -> String {
let static_completion = generate_static_completion(Shell::Fish);
if !with_dynamic {
return static_completion;
}
let gwm_bin = get_gwm_bin_expr();
format!(
r#"{static_completion}
# Dynamic completion for gwm (worktree names)
function __gwm_worktrees
set -l gwm_bin {gwm_bin}
$gwm_bin list --format=names 2>/dev/null
end
function __gwm_branches
git branch --format='%(refname:short)' 2>/dev/null
end
# Add dynamic completion for go and remove commands
complete -c gwm -n "__fish_seen_subcommand_from go" -f -a "(__gwm_worktrees)"
complete -c gwm -n "__fish_seen_subcommand_from remove rm" -f -a "(__gwm_worktrees)"
complete -c gwm -n "__fish_contains_opt from" -f -a "(__gwm_branches)"
complete -c gwm -n "__fish_contains_opt o open" -f -a "{EDITOR_OPTIONS}"
"#
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_bash_static() {
let script = generate_bash(false);
assert!(script.contains("_gwm"));
assert!(!script.contains("_gwm_worktrees"));
}
#[test]
fn test_generate_bash_dynamic() {
let script = generate_bash(true);
assert!(script.contains("_gwm_worktrees"));
assert!(script.contains("list --format=names"));
assert!(script.contains("complete -F _gwm gwm"));
}
#[test]
fn test_generate_zsh_static() {
let script = generate_zsh(false);
assert!(script.contains("#compdef gwm"));
}
#[test]
fn test_generate_zsh_dynamic() {
let script = generate_zsh(true);
assert!(script.contains("_gwm_worktrees"));
assert!(script.contains("list --format=names"));
}
#[test]
fn test_generate_fish_static() {
let script = generate_fish(false);
assert!(script.contains("complete -c gwm"));
}
#[test]
fn test_generate_fish_dynamic() {
let script = generate_fish(true);
assert!(script.contains("__gwm_worktrees"));
assert!(script.contains("list --format=names"));
}
#[test]
fn test_run_completion_bash() {
let result = run_completion(CompletionShell::Bash, false);
assert!(result.is_ok());
}
#[test]
fn test_run_completion_zsh_with_dynamic() {
let result = run_completion(CompletionShell::Zsh, true);
assert!(result.is_ok());
}
#[test]
fn test_generate_for_shell_type() {
use crate::cli::ShellType;
let bash = generate_for_shell_type(ShellType::Bash);
assert!(bash.contains("_gwm_worktrees"));
assert!(bash.contains("complete -F _gwm gwm"));
let zsh = generate_for_shell_type(ShellType::Zsh);
assert!(zsh.contains("_gwm_worktrees"));
assert!(zsh.contains("#compdef gwm"));
let fish = generate_for_shell_type(ShellType::Fish);
assert!(fish.contains("__gwm_worktrees"));
assert!(fish.contains("complete -c gwm"));
}
}