mirror of
https://github.com/clap-rs/clap
synced 2024-12-14 06:42:33 +00:00
feat(clap_complete): Add zsh support for native completion
This commit is contained in:
parent
44189f3388
commit
cef9393c5d
9 changed files with 132 additions and 4 deletions
|
@ -3,10 +3,12 @@
|
|||
mod bash;
|
||||
mod fish;
|
||||
mod shell;
|
||||
mod zsh;
|
||||
|
||||
pub use bash::*;
|
||||
pub use fish::*;
|
||||
pub use shell::*;
|
||||
pub use zsh::*;
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::io::Write as _;
|
||||
|
|
|
@ -12,6 +12,8 @@ pub enum Shell {
|
|||
Bash,
|
||||
/// Friendly Interactive `SHell` (fish)
|
||||
Fish,
|
||||
/// Z shell (zsh)
|
||||
Zsh,
|
||||
}
|
||||
|
||||
impl Display for Shell {
|
||||
|
@ -39,13 +41,14 @@ impl FromStr for Shell {
|
|||
// Hand-rolled so it can work even when `derive` feature is disabled
|
||||
impl ValueEnum for Shell {
|
||||
fn value_variants<'a>() -> &'a [Self] {
|
||||
&[Shell::Bash, Shell::Fish]
|
||||
&[Shell::Bash, Shell::Fish, Shell::Zsh]
|
||||
}
|
||||
|
||||
fn to_possible_value(&self) -> Option<PossibleValue> {
|
||||
Some(match self {
|
||||
Shell::Bash => PossibleValue::new("bash"),
|
||||
Shell::Fish => PossibleValue::new("fish"),
|
||||
Shell::Zsh => PossibleValue::new("zsh"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +58,7 @@ impl Shell {
|
|||
match self {
|
||||
Self::Bash => &super::Bash,
|
||||
Self::Fish => &super::Fish,
|
||||
Self::Zsh => &super::Zsh,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
65
clap_complete/src/dynamic/shells/zsh.rs
Normal file
65
clap_complete/src/dynamic/shells/zsh.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
/// Completion support for zsh
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Zsh;
|
||||
|
||||
impl crate::dynamic::Completer for Zsh {
|
||||
fn file_name(&self, name: &str) -> String {
|
||||
format!("{name}.zsh")
|
||||
}
|
||||
fn write_registration(
|
||||
&self,
|
||||
_name: &str,
|
||||
bin: &str,
|
||||
completer: &str,
|
||||
buf: &mut dyn std::io::Write,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let bin = shlex::quote(bin);
|
||||
let completer = shlex::quote(completer);
|
||||
let script = r#"#compdef BIN
|
||||
function _clap_dynamic_completer() {
|
||||
export _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
|
||||
export _CLAP_IFS=$'\n'
|
||||
|
||||
local completions=("${(@f)$(COMPLETER complete --shell zsh -- ${words} 2>/dev/null)}")
|
||||
|
||||
if [[ -n $completions ]]; then
|
||||
compadd -a completions
|
||||
fi
|
||||
}
|
||||
|
||||
compdef _clap_dynamic_completer BIN"#
|
||||
.replace("COMPLETER", &completer)
|
||||
.replace("BIN", &bin);
|
||||
|
||||
writeln!(buf, "{script}")?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_complete(
|
||||
&self,
|
||||
cmd: &mut clap::Command,
|
||||
args: Vec<std::ffi::OsString>,
|
||||
current_dir: Option<&std::path::Path>,
|
||||
buf: &mut dyn std::io::Write,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let index: usize = std::env::var("_CLAP_COMPLETE_INDEX")
|
||||
.ok()
|
||||
.and_then(|i| i.parse().ok())
|
||||
.unwrap_or_default();
|
||||
let ifs: Option<String> = std::env::var("_CLAP_IFS").ok().and_then(|i| i.parse().ok());
|
||||
|
||||
// If the current word is empty, add an empty string to the args
|
||||
let mut args = args.clone();
|
||||
if args.len() == index {
|
||||
args.push("".into());
|
||||
}
|
||||
let completions = crate::dynamic::complete(cmd, args, index, current_dir)?;
|
||||
|
||||
for (i, (completion, _)) in completions.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?;
|
||||
}
|
||||
write!(buf, "{}", completion.to_string_lossy())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
fpath=($fpath $ZDOTDIR/zsh)
|
||||
autoload -U +X compinit && compinit -u # bypass compaudit security checking
|
||||
precmd_functions="" # avoid the prompt being overwritten
|
||||
PS1='%% '
|
||||
PROMPT='%% '
|
|
@ -0,0 +1,13 @@
|
|||
#compdef exhaustive
|
||||
function _clap_dynamic_completer() {
|
||||
export _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
|
||||
export _CLAP_IFS=$'\n'
|
||||
|
||||
local completions=("${(@f)$(exhaustive complete --shell zsh -- ${words} 2>/dev/null)}")
|
||||
|
||||
if [[ -n $completions ]]; then
|
||||
compadd -a completions
|
||||
fi
|
||||
}
|
||||
|
||||
compdef _clap_dynamic_completer exhaustive
|
|
@ -245,7 +245,7 @@ _exhaustive() {
|
|||
fi
|
||||
case "${prev}" in
|
||||
--shell)
|
||||
COMPREPLY=($(compgen -W "bash fish" -- "${cur}"))
|
||||
COMPREPLY=($(compgen -W "bash fish zsh" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--register)
|
||||
|
|
|
@ -136,7 +136,7 @@ complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -l email -r
|
|||
complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -l global -d 'everywhere'
|
||||
complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -s h -l help -d 'Print help'
|
||||
complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -s V -l version -d 'Print version'
|
||||
complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -l shell -d 'Specify shell to complete for' -r -f -a "{bash\t'',fish\t''}"
|
||||
complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -l shell -d 'Specify shell to complete for' -r -f -a "{bash\t'',fish\t'',zsh\t''}"
|
||||
complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -l register -d 'Path to write completion-registration to' -r -F
|
||||
complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -l global -d 'everywhere'
|
||||
complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -s h -l help -d 'Print help (see more with \'--help\')'
|
||||
|
|
|
@ -325,7 +325,7 @@ _arguments "${_arguments_options[@]}" : \
|
|||
;;
|
||||
(complete)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--shell=[Specify shell to complete for]:SHELL:(bash fish)' \
|
||||
'--shell=[Specify shell to complete for]:SHELL:(bash fish zsh)' \
|
||||
'--register=[Path to write completion-registration to]:REGISTER:_files' \
|
||||
'--global[everywhere]' \
|
||||
'-h[Print help (see more with '\''--help'\'')]' \
|
||||
|
|
|
@ -162,3 +162,42 @@ pacman action alias value quote hint last --
|
|||
let actual = runtime.complete(input, &term).unwrap();
|
||||
assert_data_eq!(actual, expected);
|
||||
}
|
||||
|
||||
|
||||
#[cfg(all(unix, feature = "unstable-dynamic"))]
|
||||
#[test]
|
||||
fn register_dynamic() {
|
||||
common::register_example::<completest_pty::ZshRuntimeBuilder>("dynamic", "exhaustive");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, feature = "unstable-dynamic"))]
|
||||
fn complete_dynamic() {
|
||||
if !common::has_command("zsh") {
|
||||
return;
|
||||
}
|
||||
|
||||
let term = completest::Term::new();
|
||||
let mut runtime =
|
||||
common::load_runtime::<completest_pty::ZshRuntimeBuilder>("dynamic", "exhaustive");
|
||||
|
||||
let input = "exhaustive \t\t";
|
||||
let expected = snapbox::str![
|
||||
r#"% exhaustive
|
||||
--generate --help -V action complete hint pacman value
|
||||
--global --version -h alias help last quote "#
|
||||
];
|
||||
let actual = runtime.complete(input, &term).unwrap();
|
||||
assert_data_eq!(actual, expected);
|
||||
|
||||
let input = "exhaustive quote \t\t";
|
||||
let expected = snapbox::str![
|
||||
r#"% exhaustive quote
|
||||
--backslash --double-quotes --single-quotes cmd-backslash cmd-expansions
|
||||
--backticks --expansions --version cmd-backticks cmd-single-quotes
|
||||
--brackets --global -V cmd-brackets escape-help
|
||||
--choice --help -h cmd-double-quotes help "#
|
||||
];
|
||||
let actual = runtime.complete(input, &term).unwrap();
|
||||
assert_data_eq!(actual, expected);
|
||||
}
|
Loading…
Reference in a new issue