153 lines
5.5 KiB
Rust
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 "));
|
|
}
|
|
}
|