shell-completions icon indicating copy to clipboard operation
shell-completions copied to clipboard

Execution of shell-completion causes change in console `IFS` value

Open alexiswl opened this issue 3 years ago • 2 comments

Hello,

I have an example yaml and output bash script from running the appspec completion command with the --bash parameter.

Command run

Click to expand!

The bash script is sourced in my ~/.bash_profile.

Example yaml file

Click to expand!
---
name: ica-context-switcher

title: Switch contexts

options:
  - name: project-name
    summary: |
      Name of the project
    type: string
    completion:
      command_string: |-
        cat "$HOME/.ica-ica-lazy/tokens/tokens.json" | jq -r 'keys[]'
  - name: scope
    summary: |
      The privilege level you'd like to associate with the token
    type: string
    enum: [ admin, read-only ]

Example bash completion script

Click to expand!
#!/usr/bin/env bash

# Generated with perl module App::Spec v0.013

_ica-context-switcher() {

    COMPREPLY=()
    local program=ica-context-switcher
    local cur prev words cword
    _init_completion -n : || return
    declare -a FLAGS
    declare -a OPTIONS
    declare -a MYWORDS

    local INDEX=`expr $cword - 1`
    MYWORDS=("${words[@]:1:$cword}")

    FLAGS=('--help' 'Show command help' '-h' 'Show command help')
    OPTIONS=('--project-name' 'Name of the project
' '--scope' 'The privilege level you'"\\'"'d like to associate with the token
')
    __ica-context-switcher_handle_options_flags

    case ${MYWORDS[$INDEX-1]} in
      --project-name)
        _ica-context-switcher__option_project_name_completion
      ;;
      --scope)
        _ica-context-switcher_compreply "admin" "read-only"
        return
      ;;

    esac
    case $INDEX in

    *)
        __comp_current_options || return
    ;;
    esac

}

_ica-context-switcher_compreply() {
    local prefix=""
    cur="$(printf '%q' "$cur")"
    IFS=$'\n' COMPREPLY=($(compgen -P "$prefix" -W "$*" -- "$cur"))
    __ltrim_colon_completions "$prefix$cur"

    # http://stackoverflow.com/questions/7267185/bash-autocompletion-add-description-for-possible-completions
    if [[ ${#COMPREPLY[*]} -eq 1 ]]; then # Only one completion
        COMPREPLY=( "${COMPREPLY[0]%% -- *}" ) # Remove ' -- ' and everything after
        COMPREPLY=( "${COMPREPLY[0]%%+( )}" ) # Remove trailing spaces
    fi
}

_ica-context-switcher__option_project_name_completion() {
    local CURRENT_WORD="${words[$cword]}"
    local param_project_name="$(cat "$HOME/.ica-ica-lazy/tokens/tokens.json" | jq -r 'keys[]')"
    _ica-context-switcher_compreply "$param_project_name"
}

__ica-context-switcher_dynamic_comp() {
    local argname="$1"
    local arg="$2"
    local name desc cols desclength formatted
    local comp=()
    local max=0

    while read -r line; do
        name="$line"
        desc="$line"
        name="${name%$'\t'*}"
        if [[ "${#name}" -gt "$max" ]]; then
            max="${#name}"
        fi
    done <<< "$arg"

    while read -r line; do
        name="$line"
        desc="$line"
        name="${name%$'\t'*}"
        desc="${desc/*$'\t'}"
        if [[ -n "$desc" && "$desc" != "$name" ]]; then
            # TODO portable?
            cols=`tput cols`
            [[ -z $cols ]] && cols=80
            desclength=`expr $cols - 4 - $max`
            formatted=`printf "%-*s -- %-*s" "$max" "$name" "$desclength" "$desc"`
            comp+=("$formatted")
        else
            comp+=("'$name'")
        fi
    done <<< "$arg"
    _ica-context-switcher_compreply ${comp[@]}
}

function __ica-context-switcher_handle_options() {
    local i j
    declare -a copy
    local last="${MYWORDS[$INDEX]}"
    local max=`expr ${#MYWORDS[@]} - 1`
    for ((i=0; i<$max; i++))
    do
        local word="${MYWORDS[$i]}"
        local found=
        for ((j=0; j<${#OPTIONS[@]}; j+=2))
        do
            local option="${OPTIONS[$j]}"
            if [[ "$word" == "$option" ]]; then
                found=1
                i=`expr $i + 1`
                break
            fi
        done
        if [[ -n $found && $i -lt $max ]]; then
            INDEX=`expr $INDEX - 2`
        else
            copy+=("$word")
        fi
    done
    MYWORDS=("${copy[@]}" "$last")
}

function __ica-context-switcher_handle_flags() {
    local i j
    declare -a copy
    local last="${MYWORDS[$INDEX]}"
    local max=`expr ${#MYWORDS[@]} - 1`
    for ((i=0; i<$max; i++))
    do
        local word="${MYWORDS[$i]}"
        local found=
        for ((j=0; j<${#FLAGS[@]}; j+=2))
        do
            local flag="${FLAGS[$j]}"
            if [[ "$word" == "$flag" ]]; then
                found=1
                break
            fi
        done
        if [[ -n $found ]]; then
            INDEX=`expr $INDEX - 1`
        else
            copy+=("$word")
        fi
    done
    MYWORDS=("${copy[@]}" "$last")
}

__ica-context-switcher_handle_options_flags() {
    __ica-context-switcher_handle_options
    __ica-context-switcher_handle_flags
}

__comp_current_options() {
    local always="$1"
    if [[ -n $always || ${MYWORDS[$INDEX]} =~ ^- ]]; then

      local options_spec=''
      local j=

      for ((j=0; j<${#FLAGS[@]}; j+=2))
      do
          local name="${FLAGS[$j]}"
          local desc="${FLAGS[$j+1]}"
          options_spec+="$name"$'\t'"$desc"$'\n'
      done

      for ((j=0; j<${#OPTIONS[@]}; j+=2))
      do
          local name="${OPTIONS[$j]}"
          local desc="${OPTIONS[$j+1]}"
          options_spec+="$name"$'\t'"$desc"$'\n'
      done
      __ica-context-switcher_dynamic_comp 'options' "$options_spec"

      return 1
    else
      return 0
    fi
}


complete -o default -F _ica-context-switcher ica-context-switcher

If I run the following command on a new console:

$ IFS="$IFS" python -c "import os; print(repr(os.environ['IFS']))"
'  \t\n'

But I then call the auto-completion script

ica-context-switcher --<tab><tab>

I get a different value when trying to get the IFS output

$ IFS="$IFS" python -c "import os; print(repr(os.environ['IFS']))"
'\n'

I am running through WSL2 and this hasn't caused me any troubles, however, any colleague that runs bash on MacOS and then installs autocompletion with brew install bash_completion@2 ends up with the following issue: https://github.com/scop/bash-completion/issues/515

Is there a way for IFS to be reset to the default after the completion script has been activated?

Work around has been to run brew install bash_completion@2 --HEAD since https://github.com/scop/bash-completion/issues/515 has been resolved by https://github.com/scop/bash-completion/pull/519

alexiswl avatar Jun 17 '21 01:06 alexiswl

Thanks for the bugreport. Indeed that shouldn't be the case, and hopefully I can just fix it by using local, too

perlpunk avatar Jun 17 '21 17:06 perlpunk

Setting local works, but beware of autocompletions, other autocompletions may call. If they haven't been patched then this will still have the same issue.

So

_<name>_compreply() {
    local prefix=""
    cur="$(printf '%q' "$cur")"
    IFS=$'\n' COMPREPLY=($(compgen -P "$prefix" -W "$*" -- "$cur"))
    ...

to

_<name>_compreply() {
    local prefix=""
    local IFS=$'\n'
    cur="$(printf '%q' "$cur")"
    IFS=$'\n' COMPREPLY=($(compgen -P "$prefix" -W "$*" -- "$cur"))
    ...

seems to work

alexiswl avatar Oct 06 '22 23:10 alexiswl