Repository: clap-rs/clap
Pull request #5586 (2024-07-17 14:53:48)

docs: Add document comments how to source dynamic completions
<!--
Thanks for helping out!

Please link the appropriate issue from your PR.

If you don't have an issue, we'd recommend starting with one first so the PR can focus on the
implementation (unless its an obvious bug or documentation fix that will have
little conversation).
-->

Related issue https://github.com/clap-rs/clap/issues/2488

#### What is left
In track issue https://github.com/clap-rs/clap/issues/3166
> Ideally, we have the registration script sourced on-demand in the docs so that it is "auto-updating" with the users tool


Patch:
diff --git a/clap_complete/src/dynamic/completer.rs b/clap_complete/src/dynamic/completer.rs
index 4c6d9c92..8bf121ea 100644
--- a/clap_complete/src/dynamic/completer.rs
+++ b/clap_complete/src/dynamic/completer.rs
@@ -334,11 +334,8 @@ fn possible_values(a: &clap::Arg) -> Option<Vec<clap::builder::PossibleValue>> {
 fn subcommands(p: &clap::Command) -> Vec<(String, Option<StyledStr>)> {
     debug!("subcommands: name={}", p.get_name());
     debug!("subcommands: Has subcommands...{:?}", p.has_subcommands());
+
     p.get_subcommands()
-        .flat_map(|sc| {
-            sc.get_name_and_visible_aliases()
-                .into_iter()
-                .map(|s| (s.to_string(), sc.get_about().cloned()))
-        })
+        .map(|sc| (sc.get_name().to_string(), sc.get_about().cloned()))
         .collect()
 }
diff --git a/clap_complete/src/dynamic/shells/elvish.rs b/clap_complete/src/dynamic/shells/elvish.rs
deleted file mode 100644
index 57351b17..00000000
--- a/clap_complete/src/dynamic/shells/elvish.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-/// 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<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());
-        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 b7fed5da..850d5104 100644
--- a/clap_complete/src/dynamic/shells/mod.rs
+++ b/clap_complete/src/dynamic/shells/mod.rs
@@ -1,16 +1,12 @@
 //! Shell support
 
 mod bash;
-mod elvish;
 mod fish;
 mod shell;
-mod zsh;
 
 pub use bash::*;
-pub use elvish::*;
 pub use fish::*;
 pub use shell::*;
-pub use zsh::*;
 
 use std::ffi::OsString;
 use std::io::Write as _;
@@ -20,6 +16,78 @@ use crate::dynamic::Completer as _;
 /// A subcommand definition to `flatten` into your CLI
 ///
 /// This provides a one-stop solution for integrating completions into your CLI
+///
+/// # Examples
+///
+/// The following example shows how to integrate completions into your CLI and generate completions.
+///
+/// 1. Build an application with derive API.
+/// 2. Call `clap_complete::dynamic::shells::CompleteCommand::augment_subcommands` to add the `complete` subcommand into the application.
+/// 3. Call `get_matches()`, or any of the other normal methods directly after.
+///
+/// For example:
+///
+/// ```no_run
+/// // src/main.rs
+/// use clap::{CommandFactory, FromArgMatches, Parser, Subcommand};
+/// use clap_complete::dynamic::shells::CompleteCommand;
+///
+/// #[derive(Parser, Debug)]
+/// #[clap(name = "dynamic", about = "A dynamic command line tool")]
+/// struct Cli {
+/// 	/// The subcommand to run complete
+/// 	#[command(subcommand)]
+///     complete: Option<CompleteCommand>,
+///     /// Input file path
+///     #[clap(short, long, value_hint = clap::ValueHint::FilePath)]
+///     input: Option<String>,
+///     /// Output format
+///     #[clap(short = 'F', long, value_parser = ["json", "yaml", "toml"])]
+///     format: Option<String>,
+/// }
+///
+/// fn main() {
+///     let cli = Cli::parse();
+///     if let Some(completions) = cli.complete {
+///         completions.complete(&mut Cli::command());
+///		}
+///
+/// 	// normal logic continues...
+/// }
+///```
+///
+/// # Usage for complete subcommand:
+///
+/// To generate shell completion scripts and source them, we can use the following command.
+///
+/// **NOTE**: If you have set a custom shell configuration file,
+/// please remember to modify the redirection output file in the following command.
+///
+/// - Bash
+/// 	```bash
+/// 	echo "source <(your_program complete --shell bash --register -)" >> ~/.bashrc
+/// 	```
+///
+/// - Fish
+/// 	```fish
+/// 	echo "source (your_program complete --shell fish --register - | psub)" >> ~/.config/fish/config.fish
+/// 	```
+///
+/// - Zsh
+/// 	```zsh
+/// 	echo "source <(your_program complete --shell zsh --register -)" >> ~/.zshrc
+/// 	```
+///
+/// - Elvish
+/// 	```elvish
+/// 	echo "eval (your_program complete --shell elvish --register -)" >> ~/.elvish/rc.elv
+/// 	```
+///
+/// - Powershell
+/// 	```powershell
+/// 	echo "your_program complete --shell powershell --register - | Invoke-Expression" >> $PROFILE
+/// 	```
+///
 #[derive(clap::Subcommand)]
 #[allow(missing_docs)]
 #[derive(Clone, Debug)]
diff --git a/clap_complete/src/dynamic/shells/shell.rs b/clap_complete/src/dynamic/shells/shell.rs
index dfc091c3..51386514 100644
--- a/clap_complete/src/dynamic/shells/shell.rs
+++ b/clap_complete/src/dynamic/shells/shell.rs
@@ -12,10 +12,6 @@ pub enum Shell {
     Bash,
     /// Friendly Interactive `SHell` (fish)
     Fish,
-    /// Z shell (zsh)
-    Zsh,
-    /// Elf `SHell` (elvish)
-    Elvish,
 }
 
 impl Display for Shell {
@@ -43,15 +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::Fish, Shell::Zsh, Shell::Elvish]
+        &[Shell::Bash, Shell::Fish]
     }
 
     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"),
-            Shell::Elvish => PossibleValue::new("elvish"),
         })
     }
 }
@@ -61,8 +55,6 @@ impl Shell {
         match self {
             Self::Bash => &super::Bash,
             Self::Fish => &super::Fish,
-            Self::Zsh => &super::Zsh,
-            Self::Elvish => &super::Elvish,
         }
     }
 }
diff --git a/clap_complete/src/dynamic/shells/zsh.rs b/clap_complete/src/dynamic/shells/zsh.rs
deleted file mode 100644
index d6e699f7..00000000
--- a/clap_complete/src/dynamic/shells/zsh.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-/// 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(())
-    }
-}


Test patch:
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
deleted file mode 100644
index 2e54dd4f..00000000
--- a/clap_complete/tests/snapshots/home/dynamic/exhaustive/elvish/elvish/rc.elv
+++ /dev/null
@@ -1,14 +0,0 @@
-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/dynamic/exhaustive/zsh/.zshenv b/clap_complete/tests/snapshots/home/dynamic/exhaustive/zsh/.zshenv
deleted file mode 100644
index 6d309f24..00000000
--- a/clap_complete/tests/snapshots/home/dynamic/exhaustive/zsh/.zshenv
+++ /dev/null
@@ -1,5 +0,0 @@
-fpath=($fpath $ZDOTDIR/zsh)
-autoload -U +X compinit && compinit -u # bypass compaudit security checking
-precmd_functions=""  # avoid the prompt being overwritten
-PS1='%% '
-PROMPT='%% '
diff --git a/clap_complete/tests/snapshots/home/dynamic/exhaustive/zsh/zsh/_exhaustive b/clap_complete/tests/snapshots/home/dynamic/exhaustive/zsh/zsh/_exhaustive
deleted file mode 100644
index 97d2d1ab..00000000
--- a/clap_complete/tests/snapshots/home/dynamic/exhaustive/zsh/zsh/_exhaustive
+++ /dev/null
@@ -1,13 +0,0 @@
-#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
diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc
index c552413c..bfbaec6f 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 zsh elvish" -- "${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 d371096e..a5bb92fb 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
@@ -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'',zsh\t'',elvish\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''}"
 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\')'
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 67fe3c40..a49a847f 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 zsh elvish)' \
+'--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/testsuite/dynamic.rs b/clap_complete/tests/testsuite/dynamic.rs
index fa482782..7d5f98b9 100644
--- a/clap_complete/tests/testsuite/dynamic.rs
+++ b/clap_complete/tests/testsuite/dynamic.rs
@@ -30,36 +30,6 @@ help	Print this message or the help of the given subcommand(s)
 "#]],);
 }
 
-#[test]
-fn suggest_subcommand_aliases() {
-    let mut cmd = Command::new("exhaustive")
-        .subcommand(
-            Command::new("hello-world")
-                .visible_alias("hello-world-foo")
-                .alias("hidden-world"),
-        )
-        .subcommand(
-            Command::new("hello-moon")
-                .visible_alias("hello-moon-foo")
-                .alias("hidden-moon"),
-        )
-        .subcommand(
-            Command::new("goodbye-world")
-                .visible_alias("goodbye-world-foo")
-                .alias("hidden-goodbye"),
-        );
-
-    assert_data_eq!(
-        complete!(cmd, "hello"),
-        snapbox::str![
-            "hello-moon
-hello-moon-foo
-hello-world
-hello-world-foo"
-        ],
-    );
-}
-
 #[test]
 fn suggest_long_flag_subset() {
     let mut cmd = Command::new("exhaustive")
diff --git a/clap_complete/tests/testsuite/elvish.rs b/clap_complete/tests/testsuite/elvish.rs
index 2aa95f7e..17cf1cf6 100644
--- a/clap_complete/tests/testsuite/elvish.rs
+++ b/clap_complete/tests/testsuite/elvish.rs
@@ -174,43 +174,3 @@ 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::<completest_pty::ElvishRuntimeBuilder>("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::<completest_pty::ElvishRuntimeBuilder>("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
diff --git a/clap_complete/tests/testsuite/zsh.rs b/clap_complete/tests/testsuite/zsh.rs
index e483e7fd..8acb42a6 100644
--- a/clap_complete/tests/testsuite/zsh.rs
+++ b/clap_complete/tests/testsuite/zsh.rs
@@ -162,42 +162,3 @@ 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);
-}
\ No newline at end of file