Repository: clap-rs/clap
Pull request #1444 (2019-03-31 17:09:23)

Improve quoting in Bash completions
The current Bash completions don't quote `$cur`, which causes a problem when attempting to complete a word which contains whitespace:

```console
$ rg "- foo"<tab>
-0
-a
-A
--after-context
...
```

The bug: the word `- foo` is expanded into the words `-` and `foo`, which are passed separately to `compgen`; all words except the first (`-`) are then silently discarded, and the completions generated are based solely on the `-`.

Quoting `$cur` ensures that the completions are based on the word as a whole.


Patch:
diff --git a/src/completions/bash.rs b/src/completions/bash.rs
index d042e2ea..37dfa669 100644
--- a/src/completions/bash.rs
+++ b/src/completions/bash.rs
@@ -20,19 +20,19 @@ impl<'a, 'b> BashGen<'a, 'b> {
         w!(
             buf,
             format!(
-                "_{name}() {{
+                r#"_{name}() {{
     local i cur prev opts cmds
     COMPREPLY=()
-    cur=\"${{COMP_WORDS[COMP_CWORD]}}\"
-    prev=\"${{COMP_WORDS[COMP_CWORD-1]}}\"
-    cmd=\"\"
-    opts=\"\"
+    cur="${{COMP_WORDS[COMP_CWORD]}}"
+    prev="${{COMP_WORDS[COMP_CWORD-1]}}"
+    cmd=""
+    opts=""
 
     for i in ${{COMP_WORDS[@]}}
     do
-        case \"${{i}}\" in
+        case "${{i}}" in
             {name})
-                cmd=\"{name}\"
+                cmd="{name}"
                 ;;
             {subcmds}
             *)
@@ -40,20 +40,20 @@ impl<'a, 'b> BashGen<'a, 'b> {
         esac
     done
 
-    case \"${{cmd}}\" in
+    case "${{cmd}}" in
         {name})
-            opts=\"{name_opts}\"
+            opts="{name_opts}"
             if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then
-                COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) )
+                COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
                 return 0
             fi
-            case \"${{prev}}\" in
+            case "${{prev}}" in
                 {name_opts_details}
                 *)
                     COMPREPLY=()
                     ;;
             esac
-            COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) )
+            COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
             return 0
             ;;
         {subcmd_details}
@@ -61,7 +61,7 @@ impl<'a, 'b> BashGen<'a, 'b> {
 }}
 
 complete -F _{name} -o bashdefault -o default {name}
-",
+"#,
                 name = self.p.meta.bin_name.as_ref().unwrap(),
                 name_opts = self.all_options_for_path(self.p.meta.bin_name.as_ref().unwrap()),
                 name_opts_details =
@@ -79,10 +79,10 @@ complete -F _{name} -o bashdefault -o default {name}
 
         for sc in &scs {
             subcmds = format!(
-                "{}
+                r#"{}
             {name})
-                cmd+=\"__{fn_name}\"
-                ;;",
+                cmd+="__{fn_name}"
+                ;;"#,
                 subcmds,
                 name = sc,
                 fn_name = sc.replace("-", "__")
@@ -101,22 +101,22 @@ complete -F _{name} -o bashdefault -o default {name}
 
         for sc in &scs {
             subcmd_dets = format!(
-                "{}
+                r#"{}
         {subcmd})
-            opts=\"{sc_opts}\"
+            opts="{sc_opts}"
             if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then
-                COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) )
+                COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
                 return 0
             fi
-            case \"${{prev}}\" in
+            case "${{prev}}" in
                 {opts_details}
                 *)
                     COMPREPLY=()
                     ;;
             esac
-            COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) )
+            COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
             return 0
-            ;;",
+            ;;"#,
                 subcmd_dets,
                 subcmd = sc.replace("-", "__"),
                 sc_opts = self.all_options_for_path(&*sc),
@@ -169,9 +169,9 @@ complete -F _{name} -o bashdefault -o default {name}
         debugln!("BashGen::vals_for: o={}", o.b.name);
         use args::AnyArg;
         if let Some(vals) = o.possible_vals() {
-            format!("$(compgen -W \"{}\" -- ${{cur}})", vals.join(" "))
+            format!(r#"$(compgen -W "{}" -- "${{cur}}")"#, vals.join(" "))
         } else {
-            String::from("$(compgen -f ${cur})")
+            String::from(r#"$(compgen -f "${cur}")"#)
         }
     }
 


Test patch:
diff --git a/tests/completions.rs b/tests/completions.rs
index 34566d3e..24409ade 100644
--- a/tests/completions.rs
+++ b/tests/completions.rs
@@ -34,7 +34,7 @@ static BASH: &'static str = r#"_myapp() {
         myapp)
             opts=" -h -V  --help --version  <file>  test help"
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
-                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
             fi
             case "${prev}" in
@@ -43,14 +43,14 @@ static BASH: &'static str = r#"_myapp() {
                     COMPREPLY=()
                     ;;
             esac
-            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
             return 0
             ;;
         
         myapp__help)
             opts=" -h -V  --help --version  "
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
-                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
             fi
             case "${prev}" in
@@ -59,26 +59,26 @@ static BASH: &'static str = r#"_myapp() {
                     COMPREPLY=()
                     ;;
             esac
-            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
             return 0
             ;;
         myapp__test)
             opts=" -h -V  --help --version --case  "
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
-                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
             fi
             case "${prev}" in
                 
                 --case)
-                    COMPREPLY=($(compgen -f ${cur}))
+                    COMPREPLY=($(compgen -f "${cur}"))
                     return 0
                     ;;
                 *)
                     COMPREPLY=()
                     ;;
             esac
-            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
             return 0
             ;;
     esac
@@ -561,7 +561,7 @@ static BASH_SPECIAL_CMDS: &'static str = r#"_my_app() {
         my_app)
             opts=" -h -V  --help --version  <file>  test some_cmd some-cmd-with-hypens help"
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
-                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
             fi
             case "${prev}" in
@@ -570,14 +570,14 @@ static BASH_SPECIAL_CMDS: &'static str = r#"_my_app() {
                     COMPREPLY=()
                     ;;
             esac
-            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
             return 0
             ;;
         
         my_app__help)
             opts=" -h -V  --help --version  "
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
-                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
             fi
             case "${prev}" in
@@ -586,13 +586,13 @@ static BASH_SPECIAL_CMDS: &'static str = r#"_my_app() {
                     COMPREPLY=()
                     ;;
             esac
-            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
             return 0
             ;;
         my_app__some__cmd__with__hypens)
             opts=" -h -V  --help --version  "
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
-                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
             fi
             case "${prev}" in
@@ -601,45 +601,45 @@ static BASH_SPECIAL_CMDS: &'static str = r#"_my_app() {
                     COMPREPLY=()
                     ;;
             esac
-            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
             return 0
             ;;
         my_app__some_cmd)
             opts=" -h -V  --help --version --config  "
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
-                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
             fi
             case "${prev}" in
                 
                 --config)
-                    COMPREPLY=($(compgen -f ${cur}))
+                    COMPREPLY=($(compgen -f "${cur}"))
                     return 0
                     ;;
                 *)
                     COMPREPLY=()
                     ;;
             esac
-            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
             return 0
             ;;
         my_app__test)
             opts=" -h -V  --help --version --case  "
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
-                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
             fi
             case "${prev}" in
                 
                 --case)
-                    COMPREPLY=($(compgen -f ${cur}))
+                    COMPREPLY=($(compgen -f "${cur}"))
                     return 0
                     ;;
                 *)
                     COMPREPLY=()
                     ;;
             esac
-            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
             return 0
             ;;
     esac