Files
agent-manager/src/commands/completion_cmd.rs
T

153 lines
5.5 KiB
Rust

//! completion: generate shell completion scripts (bash, zsh, fish,
//! powershell, elvish) via clap_complete. With --installed, a dynamic
//! layer is appended: Tab then queries 'am _agents --installed' at runtime,
//! so only installed agents, aliases and groups are proposed (issue #41).
use crate::cli::Cli;
use anyhow::Result;
use clap::CommandFactory;
use clap_complete::{generate, Shell};
use std::io::Write;
pub fn run(shell: Shell, installed: bool) -> Result<i32> {
let mut cmd = Cli::command();
let mut buf: Vec<u8> = Vec::new();
generate(shell, &mut cmd, "am", &mut buf);
if installed {
let mut text = String::from_utf8_lossy(&buf).to_string();
match shell {
Shell::PowerShell => {
// Re-bind the base completer to a variable so the wrapper
// can delegate to it.
let marker = "Register-ArgumentCompleter -Native -CommandName 'am' -ScriptBlock {";
if let Some(pos) = text.find(marker) {
text.replace_range(pos..pos + marker.len(), "$amBase = {");
text.push('\n');
text.push_str(powershell_snippet());
}
}
_ => {
text.push('\n');
text.push_str(snippet(shell));
}
}
std::io::stdout().write_all(text.as_bytes())?;
} else {
std::io::stdout().write_all(&buf)?;
}
Ok(0)
}
pub fn snippet(shell: Shell) -> &'static str {
match shell {
Shell::Bash => bash_snippet(),
Shell::Zsh => zsh_snippet(),
Shell::Fish => fish_snippet(),
Shell::Elvish => elvish_snippet(),
Shell::PowerShell => powershell_snippet(),
_ => "",
}
}
pub fn bash_snippet() -> &'static str {
r#"# --- dynamic completion of installed agents, aliases and groups (--installed) ---
_am_installed() {
local cur cmd
cur="${COMP_WORDS[COMP_CWORD]}"
cmd="${COMP_WORDS[1]}"
case "${cmd}" in
start|stop|restart|run|info|logs|open|install|uninstall|update|watch|favorite|unfavorite|note|tag|untag|status|sessions|stats|log)
if [[ ${COMP_CWORD} -eq 2 ]]; then
COMPREPLY=( $(compgen -W "$(am _agents --installed 2>/dev/null)" -- "${cur}") )
return 0
fi
;;
esac
_am "$@"
}
complete -F _am_installed -o nosort -o bashdefault -o default am
"#
}
pub fn zsh_snippet() -> &'static str {
r#"# --- dynamic completion of installed agents, aliases and groups (--installed) ---
_am_installed() {
local -a agents
if [[ ${CURRENT} -eq 3 ]] && [[ ${words[2]} =~ ^(start|stop|restart|run|info|logs|open|install|uninstall|update|watch|favorite|unfavorite|note|tag|untag|status|sessions|stats|log)$ ]]; then
agents=("${(@f)$(am _agents --installed 2>/dev/null)}")
_describe -t agents 'agent' agents
return
fi
_am "$@"
}
compdef _am_installed am
"#
}
pub fn fish_snippet() -> &'static str {
r#"# --- dynamic completion of installed agents, aliases and groups (--installed) ---
complete -c am -n "__fish_seen_subcommand_from start stop restart run info logs open install uninstall update watch favorite unfavorite note tag untag status sessions stats log" -f -a "(am _agents --installed 2>/dev/null)"
"#
}
pub fn elvish_snippet() -> &'static str {
r#"# --- dynamic completion of installed agents, aliases and groups (--installed) ---
var am-base = $edit:completion:arg-completer[am]
set edit:completion:arg-completer[am] = {|@words|
if (and (>= (count $words) 2) (<= (count $words) 3) (has-value [start stop restart run info logs open install uninstall update watch favorite unfavorite note tag untag status sessions stats log] $words[1])) {
put $@(am _agents --installed)
} else {
$am-base $@words
}
}
"#
}
pub fn powershell_snippet() -> &'static str {
r#"
# --- dynamic completion of installed agents, aliases and groups (--installed) ---
Register-ArgumentCompleter -Native -CommandName 'am' -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$tokens = $commandAst.CommandElements | ForEach-Object { $_.Extent.Text }
$agentCommands = @('start','stop','restart','run','info','logs','open','install','uninstall','update','watch','favorite','unfavorite','note','tag','untag','status','sessions','stats','log')
if ($tokens.Count -ge 2 -and $agentCommands -contains $tokens[1]) {
(am _agents --installed 2>$null) | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
return
}
& $amBase $wordToComplete $commandAst $cursorPosition
}
"#
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dynamic_snippets_are_produced_for_every_shell() {
for shell in [
Shell::Bash,
Shell::Zsh,
Shell::Fish,
Shell::Elvish,
Shell::PowerShell,
] {
let text = snippet(shell);
assert!(text.contains("_agents --installed"), "{shell:?}");
assert!(text.contains("favorite"), "{shell:?}");
}
}
#[test]
fn base_script_has_no_dynamic_layer() {
let mut cmd = Cli::command();
let mut buf: Vec<u8> = Vec::new();
generate(Shell::Bash, &mut cmd, "am", &mut buf);
let base = String::from_utf8(buf).unwrap();
assert!(!base.contains("_am_installed"));
assert!(base.contains("complete -F _am "));
}
}