Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use Bash 5.3 compgen -V to generate completions including newlines #1222

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 121 additions & 50 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -655,29 +655,111 @@ _comp_compgen()
printf 'bash_completion: %s: unrecognized generator `%s'\'' (function %s not found)\n' "$FUNCNAME" "$1" "${_generator[0]}" >&2
return 2
fi
shift

((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}"
_comp_compgen__call_generator "$@"
else
# usage: _comp_compgen [options] -- [compgen_options]
if [[ $_icmd || $_xcmd ]]; then
printf 'bash_completion: %s: generator name is unspecified for `%s'\''\n' "$FUNCNAME" "${_icmd:+-i $_icmd}${_xcmd:+x $_xcmd}" >&2
return 2
fi

# Note: $* in the below checks would be affected by uncontrolled IFS in
# bash >= 5.0, so we need to set IFS to the normal value. The behavior
# in bash < 5.0, where unquoted $* in conditional command did not honor
# IFS, was a bug.
# Note: Also, ${_cur:+-- "$_cur"} and ${_append:+-a} would be affected
# by uncontrolled IFS.
local IFS=$' \t\n'
# Note: extglob *\$?(\{)[0-9]* can be extremely slow when the string
# "${*:2:_nopt}" becomes longer, so we test \$[0-9] and \$\{[0-9]
# separately.
if [[ $* == *\$[0-9]* || $* == *\$\{[0-9]* ]]; then
printf 'bash_completion: %s: positional parameter $1, $2, ... do not work inside this function\n' "$FUNCNAME" >&2
return 2
fi

_comp_compgen__call_builtin "$@"
fi
}

# Helper function for _comp_compgen. This function calls a generator.
# @param $1... generator_args
# @var[in] _dir
# @var[in] _cur
# @arr[in] _generator
# @arr[in] _upvars
# @var[in] _append
# @var[in] _var
_comp_compgen__call_generator()
{
((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}"

if [[ $_dir ]]; then
local _original_pwd=$PWD
local PWD=${PWD-} OLDPWD=${OLDPWD-}
# Note: We also redirect stdout because `cd` may output the target
# directory to stdout when CDPATH is set.
command cd -- "$_dir" &>/dev/null ||
{
_comp_compgen__error_fallback
return
}
fi

local _comp_compgen__append=$_append
local _comp_compgen__var=$_var
local _comp_compgen__cur=$_cur cur=$_cur
# Note: we use $1 as a part of a function name, and we use $2... as
# arguments to the function if any.
# shellcheck disable=SC2145
"${_generator[@]}" "$@"
local _status=$?

# Go back to the original directory.
# Note: Failure of this line results in the change of the current
# directory visible to the user. We intentionally do not redirect
# stderr so that the error message appear in the terminal.
# shellcheck disable=SC2164
[[ $_dir ]] && command cd -- "$_original_pwd"

return "$_status"
}

# Helper function for _comp_compgen. This function calls the builtin compgen.
# @param $1... compgen_args
# @var[in] _dir
# @var[in] _ifs
# @var[in] _cur
# @arr[in] _upvars
# @var[in] _append
# @var[in] _var
if ((BASH_VERSINFO[0] > 5 || BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3)); then
# bash >= 5.3 has `compgen -V array_name`
_comp_compgen__call_builtin()
{
if [[ $_dir ]]; then
local _original_pwd=$PWD
local PWD=${PWD-} OLDPWD=${OLDPWD-}
# Note: We also redirect stdout because `cd` may output the target
# directory to stdout when CDPATH is set.
command cd -- "$_dir" &>/dev/null ||
{
_comp_compgen__error_fallback
return
}
command cd -- "$_dir" &>/dev/null || {
_comp_compgen__error_fallback
return
}
fi

local _comp_compgen__append=$_append
local _comp_compgen__var=$_var
local _comp_compgen__cur=$_cur cur=$_cur
# Note: we use $1 as a part of a function name, and we use $2... as
# arguments to the function if any.
# shellcheck disable=SC2145
"${_generator[@]}" "${@:2}"
local _status=$?
local -a _result=()

# Note: We specify -X '' to exclude empty completions to make the
# behavior consistent with the implementation for Bash < 5.3 where
# `_comp_split -l` removes empty lines. If the caller specifies -X
# pat, the effect of -X '' is overwritten by the specified one.
IFS=$_ifs compgen -V _result -X '' "$@" ${_cur:+-- "$_cur"} || {
_comp_compgen__error_fallback
return
}

# Go back to the original directory.
# Note: Failure of this line results in the change of the current
Expand All @@ -686,46 +768,35 @@ _comp_compgen()
# shellcheck disable=SC2164
[[ $_dir ]] && command cd -- "$_original_pwd"

return "$_status"
fi

# usage: _comp_compgen [options] -- [compgen_options]
if [[ $_icmd || $_xcmd ]]; then
printf 'bash_completion: %s: generator name is unspecified for `%s'\''\n' "$FUNCNAME" "${_icmd:+-i $_icmd}${_xcmd:+x $_xcmd}" >&2
return 2
fi

# Note: $* in the below checks would be affected by uncontrolled IFS in
# bash >= 5.0, so we need to set IFS to the normal value. The behavior in
# bash < 5.0, where unquoted $* in conditional command did not honor IFS,
# was a bug.
# Note: Also, ${_cur:+-- "$_cur"} and ${_append:+-a} would be affected by
# uncontrolled IFS.
local IFS=$' \t\n'
# Note: extglob *\$?(\{)[0-9]* can be extremely slow when the string
# "${*:2:_nopt}" becomes longer, so we test \$[0-9] and \$\{[0-9]
# separately.
if [[ $* == *\$[0-9]* || $* == *\$\{[0-9]* ]]; then
printf 'bash_completion: %s: positional parameter $1, $2, ... do not work inside this function\n' "$FUNCNAME" >&2
return 2
fi

local _result
_result=$(
if [[ $_dir ]]; then
# Note: We also redirect stdout because `cd` may output the target
# directory to stdout when CDPATH is set.
command cd -- "$_dir" &>/dev/null || return
((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}"
((${#_result[@]})) || return
if [[ $_append ]]; then
eval -- "$_var+=(\"\${_result[@]}\")"
else
eval -- "$_var=(\"\${_result[@]}\")"
fi
IFS=$_ifs compgen "$@" ${_cur:+-- "$_cur"}
) || {
_comp_compgen__error_fallback
return
}
else
_comp_compgen__call_builtin()
{
local _result
_result=$(
if [[ $_dir ]]; then
# Note: We also redirect stdout because `cd` may output the target
# directory to stdout when CDPATH is set.
command cd -- "$_dir" &>/dev/null || return
fi
IFS=$_ifs compgen "$@" ${_cur:+-- "$_cur"}
) || {
_comp_compgen__error_fallback
return
}

((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}"
_comp_split -l ${_append:+-a} "$_var" "$_result"
}
((${#_upvars[@]})) && _comp_unlocal "${_upvars[@]}"
_comp_split -l ${_append:+-a} "$_var" "$_result"
}
fi

# usage: _comp_compgen_set [words...]
# Reset COMPREPLY with the specified WORDS. If no arguments are specified, the
Expand Down