From 4f9cf6be203b19b8dcafa4b4260f9bc9e499748a Mon Sep 17 00:00:00 2001 From: Roland Fredenhagen Date: Fri, 28 Jul 2023 17:14:38 +0700 Subject: [PATCH] feat(complete): Dynamic fish completions --- clap_complete/src/dynamic/shells/fish.rs | 38 +++++++++++++++++++ clap_complete/src/dynamic/shells/mod.rs | 2 + clap_complete/src/dynamic/shells/shell.rs | 6 ++- .../fish/fish/completions/exhaustive.fish | 1 + .../dynamic/exhaustive/fish/fish/config.fish | 7 ++++ .../home/static/exhaustive/bash/.bashrc | 2 +- .../fish/fish/completions/exhaustive.fish | 2 +- .../static/exhaustive/zsh/zsh/_exhaustive | 2 +- .../tests/snapshots/register_dynamic.fish | 1 + clap_complete/tests/testsuite/fish.rs | 25 ++++++++++++ 10 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 clap_complete/src/dynamic/shells/fish.rs create mode 100644 clap_complete/tests/snapshots/home/dynamic/exhaustive/fish/fish/completions/exhaustive.fish create mode 100644 clap_complete/tests/snapshots/home/dynamic/exhaustive/fish/fish/config.fish create mode 100644 clap_complete/tests/snapshots/register_dynamic.fish diff --git a/clap_complete/src/dynamic/shells/fish.rs b/clap_complete/src/dynamic/shells/fish.rs new file mode 100644 index 00000000..79b8bda5 --- /dev/null +++ b/clap_complete/src/dynamic/shells/fish.rs @@ -0,0 +1,38 @@ +/// Fish completions +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Fish; + +impl crate::dynamic::Completer for Fish { + fn file_name(&self, name: &str) -> String { + format!("{name}.fish") + } + 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); + writeln!( + buf, + r#"complete -x -c {bin} -a "("'{completer}'" complete --shell fish -- (commandline --current-process --tokenize --cut-at-cursor) (commandline --current-token))""# + ) + } + 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 = args.len() - 1; + let completions = crate::dynamic::complete(cmd, args, index, current_dir)?; + + for completion in completions { + writeln!(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 f0ecc8c2..54d23a3d 100644 --- a/clap_complete/src/dynamic/shells/mod.rs +++ b/clap_complete/src/dynamic/shells/mod.rs @@ -1,9 +1,11 @@ //! Shell support mod bash; +mod fish; mod shell; pub use bash::*; +pub use fish::*; pub use shell::*; use std::ffi::OsString; diff --git a/clap_complete/src/dynamic/shells/shell.rs b/clap_complete/src/dynamic/shells/shell.rs index 3de8c5c6..a9f48cee 100644 --- a/clap_complete/src/dynamic/shells/shell.rs +++ b/clap_complete/src/dynamic/shells/shell.rs @@ -10,6 +10,8 @@ use clap::ValueEnum; pub enum Shell { /// Bourne Again SHell (bash) Bash, + /// Friendly Interactive SHell (fish) + Fish, } impl Display for Shell { @@ -37,12 +39,13 @@ 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::Bash, Shell::Fish] } fn to_possible_value<'a>(&self) -> Option { Some(match self { Shell::Bash => PossibleValue::new("bash"), + Shell::Fish => PossibleValue::new("fish"), }) } } @@ -51,6 +54,7 @@ impl Shell { fn completer(&self) -> &dyn crate::dynamic::Completer { match self { Self::Bash => &super::Bash, + Self::Fish => &super::Fish, } } } diff --git a/clap_complete/tests/snapshots/home/dynamic/exhaustive/fish/fish/completions/exhaustive.fish b/clap_complete/tests/snapshots/home/dynamic/exhaustive/fish/fish/completions/exhaustive.fish new file mode 100644 index 00000000..cfb35d74 --- /dev/null +++ b/clap_complete/tests/snapshots/home/dynamic/exhaustive/fish/fish/completions/exhaustive.fish @@ -0,0 +1 @@ +complete -x -c exhaustive -a "("'exhaustive'" complete --shell fish -- (commandline --current-process --tokenize --cut-at-cursor) (commandline --current-token))" diff --git a/clap_complete/tests/snapshots/home/dynamic/exhaustive/fish/fish/config.fish b/clap_complete/tests/snapshots/home/dynamic/exhaustive/fish/fish/config.fish new file mode 100644 index 00000000..74bd2f00 --- /dev/null +++ b/clap_complete/tests/snapshots/home/dynamic/exhaustive/fish/fish/config.fish @@ -0,0 +1,7 @@ +set -U fish_greeting "" +set -U fish_autosuggestion_enabled 0 +function fish_title +end +function fish_prompt + printf '%% ' +end; diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc index aafae870..9ef12ac7 100644 --- a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc +++ b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc @@ -236,7 +236,7 @@ _exhaustive() { fi case "${prev}" in --shell) - COMPREPLY=($(compgen -W "bash" -- "${cur}")) + COMPREPLY=($(compgen -W "bash fish" -- "${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 3f232cb1..77cfddd8 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 @@ -104,7 +104,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 }" +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 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 1fa0a62d..6548aa0e 100644 --- a/clap_complete/tests/snapshots/home/static/exhaustive/zsh/zsh/_exhaustive +++ b/clap_complete/tests/snapshots/home/static/exhaustive/zsh/zsh/_exhaustive @@ -309,7 +309,7 @@ _arguments "${_arguments_options[@]}" \ ;; (complete) _arguments "${_arguments_options[@]}" \ -'--shell=[Specify shell to complete for]:SHELL:(bash)' \ +'--shell=[Specify shell to complete for]:SHELL:(bash fish)' \ '--register=[Path to write completion-registration to]:REGISTER:_files' \ '--global[everywhere]' \ '-h[Print help (see more with '\''--help'\'')]' \ diff --git a/clap_complete/tests/snapshots/register_dynamic.fish b/clap_complete/tests/snapshots/register_dynamic.fish new file mode 100644 index 00000000..f8972bfc --- /dev/null +++ b/clap_complete/tests/snapshots/register_dynamic.fish @@ -0,0 +1 @@ +complete -x -c my-app -a 'my-app'" complete --shell fish -- (commandline --current-process --tokenize --cut-at-cursor) (commandline --current-token)" diff --git a/clap_complete/tests/testsuite/fish.rs b/clap_complete/tests/testsuite/fish.rs index 104919ba..6e2bbeb4 100644 --- a/clap_complete/tests/testsuite/fish.rs +++ b/clap_complete/tests/testsuite/fish.rs @@ -143,3 +143,28 @@ alias help (Print this message or the help of the given subcommand(s)) last let actual = runtime.complete(input, &term).unwrap(); snapbox::assert_eq(expected, actual); } + +#[cfg(all(unix, feature = "unstable-dynamic"))] +#[test] +fn register_dynamic() { + common::register_example("dynamic", "exhaustive", completest::Shell::Fish); +} + +#[test] +#[cfg(all(unix, feature = "unstable-dynamic"))] +fn complete_dynamic() { + if !common::has_command("fish") { + return; + } + + let term = completest::Term::new(); + let mut runtime = common::load_runtime("dynamic", "exhaustive", completest::Shell::Fish); + + let input = "exhaustive \t"; + let expected = r#"% exhaustive +action help pacman -h --global +alias hint quote -V --help +complete last value --generate --version"#; + let actual = runtime.complete(input, &term).unwrap(); + snapbox::assert_eq(expected, actual); +}