Repository: clap-rs/clap
Pull request #5684 (2024-08-19 15:26:36)

fix(complete): Don't cause endless completions for bash/zsh
Reported on #5677

<!--
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).
-->



Patch:
diff --git a/clap_complete/src/command/shells.rs b/clap_complete/src/command/shells.rs
index d7b8b70f..e057c001 100644
--- a/clap_complete/src/command/shells.rs
+++ b/clap_complete/src/command/shells.rs
@@ -187,15 +187,21 @@ impl CommandCompleter for Bash {
 
         let script = r#"
 _clap_complete_NAME() {
-    export IFS=$'\013'
-    export _CLAP_COMPLETE_INDEX=${COMP_CWORD}
-    export _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
+    local IFS=$'\013'
+    local _CLAP_COMPLETE_INDEX=${COMP_CWORD}
+    local _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
     if compopt +o nospace 2> /dev/null; then
-        export _CLAP_COMPLETE_SPACE=false
+        local _CLAP_COMPLETE_SPACE=false
     else
-        export _CLAP_COMPLETE_SPACE=true
+        local _CLAP_COMPLETE_SPACE=true
     fi
-    COMPREPLY=( $("COMPLETER" complete bash -- "${COMP_WORDS[@]}") )
+    COMPREPLY=( $( \
+        IFS="$IFS" \
+        _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
+        _CLAP_COMPLETE_COMP_TYPE="$_CLAP_COMPLETE_COMP_TYPE" \
+        _CLAP_COMPLETE_SPACE="$_CLAP_COMPLETE_SPACE" \
+        "COMPLETER" complete bash -- "${COMP_WORDS[@]}" \
+    ) )
     if [[ $? != 0 ]]; then
         unset COMPREPLY
     elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
@@ -471,10 +477,14 @@ impl CommandCompleter for Zsh {
 
         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 zsh -- ${words} 2>/dev/null)}")
+    local _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
+    local _CLAP_IFS=$'\n'
+
+    local completions=("${(@f)$( \
+        _CLAP_IFS="$_CLAP_IFS" \
+        _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
+        COMPLETER complete zsh -- ${words} 2>/dev/null \
+    )}")
 
     if [[ -n $completions ]]; then
         compadd -a completions
diff --git a/clap_complete/src/env/shells.rs b/clap_complete/src/env/shells.rs
index 46a3ee37..5b8579f9 100644
--- a/clap_complete/src/env/shells.rs
+++ b/clap_complete/src/env/shells.rs
@@ -31,16 +31,21 @@ impl EnvCompleter for Bash {
 
         let script = r#"
 _clap_complete_NAME() {
-    export IFS=$'\013'
-    export _CLAP_COMPLETE_INDEX=${COMP_CWORD}
-    export _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
+    local IFS=$'\013'
+    local _CLAP_COMPLETE_INDEX=${COMP_CWORD}
+    local _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
     if compopt +o nospace 2> /dev/null; then
-        export _CLAP_COMPLETE_SPACE=false
+        local _CLAP_COMPLETE_SPACE=false
     else
-        export _CLAP_COMPLETE_SPACE=true
+        local _CLAP_COMPLETE_SPACE=true
     fi
-    export VAR="bash"
-    COMPREPLY=( $("COMPLETER" -- "${COMP_WORDS[@]}") )
+    COMPREPLY=( $( \
+        IFS="$IFS" \
+        _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
+        _CLAP_COMPLETE_COMP_TYPE="$_CLAP_COMPLETE_COMP_TYPE" \
+        VAR="bash" \
+        "COMPLETER" -- "${COMP_WORDS[@]}" \
+    ) )
     if [[ $? != 0 ]]; then
         unset COMPREPLY
     elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
@@ -347,11 +352,15 @@ impl EnvCompleter for Zsh {
 
         let script = r#"#compdef BIN
 function _clap_dynamic_completer() {
-    export _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
-    export _CLAP_IFS=$'\n'
-    export VAR="zsh"
-
-    local completions=("${(@f)$(COMPLETER -- ${words} 2>/dev/null)}")
+    local _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
+    local _CLAP_IFS=$'\n'
+
+    local completions=("${(@f)$( \
+        _CLAP_IFS="$_CLAP_IFS" \
+        _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
+        VAR="zsh" \
+        COMPLETER -- ${words} 2>/dev/null \
+    )}")
 
     if [[ -n $completions ]]; then
         compadd -a completions


Test patch:
diff --git a/clap_complete/tests/snapshots/home/dynamic-command/exhaustive/bash/.bashrc b/clap_complete/tests/snapshots/home/dynamic-command/exhaustive/bash/.bashrc
index 668029e8..71a223df 100644
--- a/clap_complete/tests/snapshots/home/dynamic-command/exhaustive/bash/.bashrc
+++ b/clap_complete/tests/snapshots/home/dynamic-command/exhaustive/bash/.bashrc
@@ -2,15 +2,21 @@ PS1='% '
 . /etc/bash_completion
 
 _clap_complete_exhaustive() {
-    export IFS=$'\013'
-    export _CLAP_COMPLETE_INDEX=${COMP_CWORD}
-    export _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
+    local IFS=$'\013'
+    local _CLAP_COMPLETE_INDEX=${COMP_CWORD}
+    local _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
     if compopt +o nospace 2> /dev/null; then
-        export _CLAP_COMPLETE_SPACE=false
+        local _CLAP_COMPLETE_SPACE=false
     else
-        export _CLAP_COMPLETE_SPACE=true
+        local _CLAP_COMPLETE_SPACE=true
     fi
-    COMPREPLY=( $("exhaustive" complete bash -- "${COMP_WORDS[@]}") )
+    COMPREPLY=( $( \
+        IFS="$IFS" \
+        _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
+        _CLAP_COMPLETE_COMP_TYPE="$_CLAP_COMPLETE_COMP_TYPE" \
+        _CLAP_COMPLETE_SPACE="$_CLAP_COMPLETE_SPACE" \
+        "exhaustive" complete bash -- "${COMP_WORDS[@]}" \
+    ) )
     if [[ $? != 0 ]]; then
         unset COMPREPLY
     elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
diff --git a/clap_complete/tests/snapshots/home/dynamic-command/exhaustive/zsh/zsh/_exhaustive b/clap_complete/tests/snapshots/home/dynamic-command/exhaustive/zsh/zsh/_exhaustive
index a7082462..fbc8bc36 100644
--- a/clap_complete/tests/snapshots/home/dynamic-command/exhaustive/zsh/zsh/_exhaustive
+++ b/clap_complete/tests/snapshots/home/dynamic-command/exhaustive/zsh/zsh/_exhaustive
@@ -1,9 +1,13 @@
 #compdef exhaustive
 function _clap_dynamic_completer() {
-    export _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
-    export _CLAP_IFS=$'\n'
+    local _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
+    local _CLAP_IFS=$'\n'
 
-    local completions=("${(@f)$(exhaustive complete zsh -- ${words} 2>/dev/null)}")
+    local completions=("${(@f)$( \
+        _CLAP_IFS="$_CLAP_IFS" \
+        _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
+        exhaustive complete zsh -- ${words} 2>/dev/null \
+    )}")
 
     if [[ -n $completions ]]; then
         compadd -a completions
diff --git a/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/bash/.bashrc b/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/bash/.bashrc
index 4384e8d1..ffc7e790 100644
--- a/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/bash/.bashrc
+++ b/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/bash/.bashrc
@@ -2,16 +2,21 @@ PS1='% '
 . /etc/bash_completion
 
 _clap_complete_exhaustive() {
-    export IFS=$'\013'
-    export _CLAP_COMPLETE_INDEX=${COMP_CWORD}
-    export _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
+    local IFS=$'\013'
+    local _CLAP_COMPLETE_INDEX=${COMP_CWORD}
+    local _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
     if compopt +o nospace 2> /dev/null; then
-        export _CLAP_COMPLETE_SPACE=false
+        local _CLAP_COMPLETE_SPACE=false
     else
-        export _CLAP_COMPLETE_SPACE=true
+        local _CLAP_COMPLETE_SPACE=true
     fi
-    export COMPLETE="bash"
-    COMPREPLY=( $("exhaustive" -- "${COMP_WORDS[@]}") )
+    COMPREPLY=( $( \
+        IFS="$IFS" \
+        _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
+        _CLAP_COMPLETE_COMP_TYPE="$_CLAP_COMPLETE_COMP_TYPE" \
+        COMPLETE="bash" \
+        "exhaustive" -- "${COMP_WORDS[@]}" \
+    ) )
     if [[ $? != 0 ]]; then
         unset COMPREPLY
     elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
diff --git a/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/zsh/zsh/_exhaustive b/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/zsh/zsh/_exhaustive
index 6c5bd28a..b280f7c6 100644
--- a/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/zsh/zsh/_exhaustive
+++ b/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/zsh/zsh/_exhaustive
@@ -1,10 +1,14 @@
 #compdef exhaustive
 function _clap_dynamic_completer() {
-    export _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
-    export _CLAP_IFS=$'\n'
-    export COMPLETE="zsh"
+    local _CLAP_COMPLETE_INDEX=$(expr $CURRENT - 1)
+    local _CLAP_IFS=$'\n'
 
-    local completions=("${(@f)$(exhaustive -- ${words} 2>/dev/null)}")
+    local completions=("${(@f)$( \
+        _CLAP_IFS="$_CLAP_IFS" \
+        _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" \
+        COMPLETE="zsh" \
+        exhaustive -- ${words} 2>/dev/null \
+    )}")
 
     if [[ -n $completions ]]; then
         compadd -a completions
diff --git a/clap_complete/tests/snapshots/register_minimal.bash b/clap_complete/tests/snapshots/register_minimal.bash
index 925c3835..d2da036c 100644
--- a/clap_complete/tests/snapshots/register_minimal.bash
+++ b/clap_complete/tests/snapshots/register_minimal.bash
@@ -1,14 +1,20 @@
 
 _clap_complete_my_app() {
-    export IFS=$'/013'
-    export _CLAP_COMPLETE_INDEX=${COMP_CWORD}
-    export _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
+    local IFS=$'/013'
+    local _CLAP_COMPLETE_INDEX=${COMP_CWORD}
+    local _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
     if compopt +o nospace 2> /dev/null; then
-        export _CLAP_COMPLETE_SPACE=false
+        local _CLAP_COMPLETE_SPACE=false
     else
-        export _CLAP_COMPLETE_SPACE=true
+        local _CLAP_COMPLETE_SPACE=true
     fi
-    COMPREPLY=( $("my-app" complete bash -- "${COMP_WORDS[@]}") )
+    COMPREPLY=( $( /
+        IFS="$IFS" /
+        _CLAP_COMPLETE_INDEX="$_CLAP_COMPLETE_INDEX" /
+        _CLAP_COMPLETE_COMP_TYPE="$_CLAP_COMPLETE_COMP_TYPE" /
+        _CLAP_COMPLETE_SPACE="$_CLAP_COMPLETE_SPACE" /
+        "my-app" complete bash -- "${COMP_WORDS[@]}" /
+    ) )
     if [[ $? != 0 ]]; then
         unset COMPREPLY
     elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then