use crate::command::Command;
#[derive(Debug, Clone, Copy)]
pub enum Shell {
Bash,
Zsh,
Fish,
}
impl Command {
pub fn generate_completion(&self, shell: Shell) -> String {
match shell {
Shell::Bash => self.generate_bash_completion(),
Shell::Zsh => self.generate_zsh_completion(),
Shell::Fish => self.generate_fish_completion(),
}
}
fn generate_bash_completion(&self) -> String {
let name = self.name();
let upper = name.to_uppercase();
format!(
r#"# Bash completion for {name}
_{name}_complete() {{
local cur prev words cword
_get_comp_words_by_ref -n : cur prev words cword
# Call our binary with special completion env var
local IFS=$'\n'
local response
response=$({upper}_COMPLETE=bash "${{words[0]}}" __complete "${{words[@]:1:$((cword-1))}}" "$cur" 2>/dev/null)
if [[ -n "$response" ]]; then
# Use printf to handle each line separately
local lines=()
local help_messages=()
while IFS= read -r line; do
if [[ "$line" == _activehelp_* ]]; then
# Extract help message
help_messages+=("${{line#_activehelp_ }}")
else
lines+=("$line")
fi
done <<< "$response"
COMPREPLY=( "${{lines[@]}}" )
# Display help messages if any
if [[ ${{#help_messages[@]}} -gt 0 ]]; then
printf '\n'
for msg in "${{help_messages[@]}}"; do
printf '%s\n' "$msg"
done
printf '\n'
fi
fi
}}
complete -F _{name}_complete {name}
"#
)
}
fn generate_zsh_completion(&self) -> String {
let name = self.name();
let upper = name.to_uppercase();
format!(
r#"#compdef -P {name}
# Zsh completion for {name}
_{name}_complete() {{
local -a completions
local IFS=$'\n'
# Get the actual command from the command line
local cmd="${{words[1]}}"
if [[ "$cmd" != /* ]] && ! command -v "$cmd" &>/dev/null; then
# If not found in PATH, try relative path
if [[ -x "$cmd" ]]; then
cmd="./$cmd"
fi
fi
# Build completion arguments
local -a comp_line
comp_line=("__complete")
# Add all words except the command name
local i
for (( i = 2; i < CURRENT; i++ )); do
comp_line+=("${{words[$i]}}")
done
# Add the current word being completed
comp_line+=("${{words[CURRENT]}}")
# Call the command with completion environment variable
local response
response=$({upper}_COMPLETE=zsh "$cmd" "${{comp_line[@]}}" 2>/dev/null)
if [[ -n "$response" ]]; then
local -a values
local -a descriptions
local -a help_messages
local line
# Parse response lines
while IFS= read -r line; do
if [[ "$line" == _activehelp_::* ]]; then
# ActiveHelp message
help_messages+=("${{line#_activehelp_::}}")
elif [[ "$line" == *:* ]]; then
# Line has description
values+=("${{line%%:*}}")
descriptions+=("${{line#*:}}")
else
# No description
values+=("$line")
descriptions+=("")
fi
done <<< "$response"
# Display ActiveHelp messages if any
if [[ ${{#help_messages[@]}} -gt 0 ]]; then
local formatted_help=()
for msg in "${{help_messages[@]}}"; do
formatted_help+=("-- $msg --")
done
compadd -x "${{(j: :)formatted_help}}"
fi
# Add completions with descriptions
if [[ ${{#descriptions[@]}} -gt 0 ]] && [[ -n "${{descriptions[*]// }}" ]]; then
compadd -Q -d descriptions -a values
else
compadd -Q -a values
fi
fi
}}
compdef _{name}_complete {name}
"#
)
}
fn generate_fish_completion(&self) -> String {
let name = self.name();
let upper = name.to_uppercase();
format!(
"# Fish completion for {name}
function __{name}_complete
set -l cmd (commandline -opc)
set -l cursor (commandline -C)
set -l current (commandline -ct)
# Call our binary with special completion env var
set -l response (env {upper}_COMPLETE=fish $cmd[1] __complete $cmd[2..-1] $current 2>/dev/null)
# Process response and handle ActiveHelp
set -l help_messages
for line in $response
if string match -q '_activehelp_*' -- $line
# Extract help message
set -a help_messages (string replace '_activehelp_\t' '' -- $line)
else
echo $line
end
end
# Display help messages if any
if test (count $help_messages) -gt 0
for msg in $help_messages
echo \"ยป $msg\" >&2
end
end
end
complete -c {name} -f -a '(__{name}_complete)'
"
)
}
}