From a1b92616b1d786217e75b4496d5edf4e00084bff Mon Sep 17 00:00:00 2001 From: shannmu Date: Tue, 25 Jun 2024 13:35:36 +0800 Subject: [PATCH] feat(clap_complete): Add elvish support for native completion --- clap_complete/src/dynamic/shells/elvish.rs | 58 +++++++++++++++++++ clap_complete/src/dynamic/shells/mod.rs | 2 + clap_complete/src/dynamic/shells/shell.rs | 6 +- .../dynamic/exhaustive/elvish/elvish/rc.elv | 14 +++++ .../home/static/exhaustive/bash/.bashrc | 2 +- .../fish/fish/completions/exhaustive.fish | 2 +- .../static/exhaustive/zsh/zsh/_exhaustive | 2 +- clap_complete/tests/testsuite/elvish.rs | 40 +++++++++++++ 8 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 clap_complete/src/dynamic/shells/elvish.rs create mode 100644 clap_complete/tests/snapshots/home/dynamic/exhaustive/elvish/elvish/rc.elv diff --git a/clap_complete/src/dynamic/shells/elvish.rs b/clap_complete/src/dynamic/shells/elvish.rs new file mode 100644 index 000000000000..57351b171ee9 --- /dev/null +++ b/clap_complete/src/dynamic/shells/elvish.rs @@ -0,0 +1,58 @@ +/// Completion support for Bash +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Elvish; + +impl crate::dynamic::Completer for Elvish { + fn file_name(&self, name: &str) -> String { + format!("{name}.elv") + } + 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#" +set edit:completion:arg-completer[BIN] = { |@words| + set E:_CLAP_IFS = "\n" + + var index = (count $words) + set index = (- $index 1) + set E:_CLAP_COMPLETE_INDEX = (to-string $index) + + put (COMPLETER complete --shell elvish -- $@words) | to-lines +} +"# + .replace("COMPLETER", &completer) + .replace("BIN", &bin); + + writeln!(buf, "{script}")?; + Ok(()) + } + fn write_complete( + &self, + cmd: &mut clap::Command, + args: Vec, + 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 = std::env::var("_CLAP_IFS").ok().and_then(|i| i.parse().ok()); + 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(()) + } +} diff --git a/clap_complete/src/dynamic/shells/mod.rs b/clap_complete/src/dynamic/shells/mod.rs index 6cfed97ed5e1..7e720848e66f 100644 --- a/clap_complete/src/dynamic/shells/mod.rs +++ b/clap_complete/src/dynamic/shells/mod.rs @@ -1,10 +1,12 @@ //! Shell support mod bash; +mod elvish; mod fish; mod shell; pub use bash::*; +pub use elvish::*; pub use fish::*; pub use shell::*; diff --git a/clap_complete/src/dynamic/shells/shell.rs b/clap_complete/src/dynamic/shells/shell.rs index 513865142f19..633bf5412605 100644 --- a/clap_complete/src/dynamic/shells/shell.rs +++ b/clap_complete/src/dynamic/shells/shell.rs @@ -12,6 +12,8 @@ pub enum Shell { Bash, /// Friendly Interactive `SHell` (fish) Fish, + /// Elf `SHell` (elvish) + Elvish, } 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::Elvish] } fn to_possible_value(&self) -> Option { Some(match self { Shell::Bash => PossibleValue::new("bash"), Shell::Fish => PossibleValue::new("fish"), + Shell::Elvish => PossibleValue::new("elvish"), }) } } @@ -55,6 +58,7 @@ impl Shell { match self { Self::Bash => &super::Bash, Self::Fish => &super::Fish, + Self::Elvish => &super::Elvish, } } } diff --git a/clap_complete/tests/snapshots/home/dynamic/exhaustive/elvish/elvish/rc.elv b/clap_complete/tests/snapshots/home/dynamic/exhaustive/elvish/elvish/rc.elv new file mode 100644 index 000000000000..2e54dd4fd678 --- /dev/null +++ b/clap_complete/tests/snapshots/home/dynamic/exhaustive/elvish/elvish/rc.elv @@ -0,0 +1,14 @@ +set edit:rprompt = (constantly "") +set edit:prompt = (constantly "% ") + +set edit:completion:arg-completer[exhaustive] = { |@words| + set E:_CLAP_IFS = "\n" + + var index = (count $words) + set index = (- $index 1) + set E:_CLAP_COMPLETE_INDEX = (to-string $index) + + put (exhaustive complete --shell elvish -- $@words) | to-lines +} + + diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc index bfbaec6f95f6..1d1a01bd3a92 100644 --- a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc +++ b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc @@ -245,7 +245,7 @@ _exhaustive() { fi case "${prev}" in --shell) - COMPREPLY=($(compgen -W "bash fish" -- "${cur}")) + COMPREPLY=($(compgen -W "bash fish elvish" -- "${cur}")) return 0 ;; --register) diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish b/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish index 147a44f1cf3c..21bfdef7fea6 100644 --- a/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish +++ b/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish @@ -110,7 +110,7 @@ complete -c exhaustive -n "__fish_seen_subcommand_from hint" -l email -r -f complete -c exhaustive -n "__fish_seen_subcommand_from hint" -l global -d 'everywhere' complete -c exhaustive -n "__fish_seen_subcommand_from hint" -s h -l help -d 'Print help' complete -c exhaustive -n "__fish_seen_subcommand_from hint" -s V -l version -d 'Print version' -complete -c exhaustive -n "__fish_seen_subcommand_from complete" -l shell -d 'Specify shell to complete for' -r -f -a "{bash '',fish ''}" +complete -c exhaustive -n "__fish_seen_subcommand_from complete" -l shell -d 'Specify shell to complete for' -r -f -a "{bash '',fish '',elvish ''}" complete -c exhaustive -n "__fish_seen_subcommand_from complete" -l register -d 'Path to write completion-registration to' -r -F complete -c exhaustive -n "__fish_seen_subcommand_from complete" -l global -d 'everywhere' complete -c exhaustive -n "__fish_seen_subcommand_from complete" -s h -l help -d 'Print help (see more with \'--help\')' diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/zsh/zsh/_exhaustive b/clap_complete/tests/snapshots/home/static/exhaustive/zsh/zsh/_exhaustive index a49a847f9b1d..d6662569da19 100644 --- a/clap_complete/tests/snapshots/home/static/exhaustive/zsh/zsh/_exhaustive +++ b/clap_complete/tests/snapshots/home/static/exhaustive/zsh/zsh/_exhaustive @@ -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 elvish)' \ '--register=[Path to write completion-registration to]:REGISTER:_files' \ '--global[everywhere]' \ '-h[Print help (see more with '\''--help'\'')]' \ diff --git a/clap_complete/tests/testsuite/elvish.rs b/clap_complete/tests/testsuite/elvish.rs index eee5fee67460..d97aa7956827 100644 --- a/clap_complete/tests/testsuite/elvish.rs +++ b/clap_complete/tests/testsuite/elvish.rs @@ -174,3 +174,43 @@ value value "# 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::("dynamic", "exhaustive"); +} + +#[test] +#[cfg(all(unix, feature = "unstable-dynamic"))] +fn complete_dynamic() { + if !common::has_command("elvish") { + return; + } + + let term = completest::Term::new(); + let mut runtime = + common::load_runtime::("dynamic", "exhaustive"); + + let input = "exhaustive \t"; + let expected = snapbox::str![ + r#"% exhaustive --generate + COMPLETING argument +--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"; + let expected = snapbox::str![ + r#"% exhaustive quote --backslash + COMPLETING argument +--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); +} \ No newline at end of file