diff --git a/lib/bash/cli/lib_cli.sh b/lib/bash/cli/lib_cli.sh index 40ef9d0..917ed75 100644 --- a/lib/bash/cli/lib_cli.sh +++ b/lib/bash/cli/lib_cli.sh @@ -1573,8 +1573,18 @@ base_cli_completion_script() { fi program="${__base_bash_libs_cli_models["$model|meta|name"]}" printf '%s\n' "$function_name() {" - printf '%s\n' ' local current="${COMP_WORDS[COMP_CWORD]-}"' - printf '%s\n' ' local -a cli_words=( "${COMP_WORDS[@]:1}" )' + printf '%s\n' ' local cursor="${COMP_CWORD:-0}" word_count candidate' + printf '%s\n' ' local -a completion_words=( "${COMP_WORDS[@]+${COMP_WORDS[@]}}" ) cli_words=()' + printf '%s\n' ' if [[ "$cursor" =~ ^[0-9]+$ ]]; then cursor=$((10#$cursor)); else cursor=0; fi' + printf '%s\n' ' word_count="${#completion_words[@]}"' + printf '%s\n' ' if ((cursor > 0)); then' + printf '%s\n' ' if ((cursor < word_count)); then' + printf '%s\n' ' cli_words=( "${completion_words[@]:1:cursor}" )' + printf '%s\n' ' else' + printf '%s\n' ' cli_words=( "${completion_words[@]:1}" )' + printf '%s\n' ' cli_words+=("")' + printf '%s\n' ' fi' + printf '%s\n' ' fi' printf '%s\n' ' COMPREPLY=()' printf ' while IFS= read -r candidate; do COMPREPLY+=("$candidate"); done < <(base_cli_complete %q -- "${cli_words[@]}")\n' "$model" printf '%s\n' '}' diff --git a/lib/bash/cli/tests/lib_cli.bats b/lib/bash/cli/tests/lib_cli.bats index ed4c939..71637ad 100644 --- a/lib/bash/cli/tests/lib_cli.bats +++ b/lib/bash/cli/tests/lib_cli.bats @@ -602,6 +602,56 @@ EOF [[ "$output" == *"complete -F _demo_complete demo"* ]] } +@test "generated completion honors the cursor and keeps scratch variables local" { + local completion_script="$TEST_TMPDIR/completion.bash" + local candidate=caller-owned + + base_cli_model_init cursor name=cursor + base_cli_command cursor admin "Administration" + base_cli_command cursor admin/user "User" + base_cli_option cursor '' channel value --channel enum=alpha,beta + base_cli_option cursor '' output value --output + base_cli_option cursor admin/user color value --color + base_cli_completion_script cursor _cursor_complete > "$completion_script" + source "$completion_script" + + COMP_WORDS=(cursor --ch stable --output result) + COMP_CWORD=1 + _cursor_complete + [ "${COMPREPLY[*]}" = --channel ] + [ "$candidate" = caller-owned ] + + COMP_WORDS=(cursor --channel a --output result) + COMP_CWORD=2 + _cursor_complete + [ "${#COMPREPLY[@]}" -eq 0 ] + + COMP_WORDS=(cursor admin --out --channel alpha) + COMP_CWORD=2 + _cursor_complete + [ "${COMPREPLY[*]}" = --output ] + + COMP_WORDS=(cursor admin user --co) + COMP_CWORD=3 + _cursor_complete + [ "${COMPREPLY[*]}" = --color ] + + COMP_WORDS=(cursor -- --channel) + COMP_CWORD=2 + _cursor_complete + [ "${#COMPREPLY[@]}" -eq 0 ] + + COMP_WORDS=(cursor admin) + COMP_CWORD=2 + _cursor_complete + [ "${COMPREPLY[*]}" = user ] + + COMP_WORDS=(cursor) + COMP_CWORD=1 + _cursor_complete + [ "${COMPREPLY[*]}" = admin ] +} + @test "completion consumes option values and honors the double-dash boundary" { base_cli_model_init complete name=complete base_cli_command complete admin "Administration" aliases=a