@@ -381,51 +381,96 @@ _comp_split()
381381 (( _new_size > _old_size))
382382}
383383
384- # Call `compgen` with the specified arguments and store the results in the
385- # specified array.
386- # Usage: _comp_compgen [-alR|-F sep|-v arr|-c cur] -- args...
387- # This function essentially performs arr=($(compgen args...)) but properly
388- # handles shell options, IFS, etc. using _comp_split. This function is
389- # equivalent to `_comp_split [-a] -l arr "$(IFS=sep; compgen args... -- cur)"`,
390- # but this pattern is frequent in the codebase and is good to separate out as a
391- # function for the possible future implementation change.
384+ # Provide a common interface to generate completion candidates in COMPREPLY or
385+ # in a specified array.
392386# OPTIONS
393387# -a Append to the array
394388# -v arr Store the results to the array ARR. The default is `COMPREPLY`.
389+ # The array name should not start with an underscores "_", which is
390+ # internally used. The array name should not be either "IFS" or
391+ # "OPT{IND,ARG,ERR}".
395392# -F sep Set a set of separator characters (used as IFS in evaluating
396393# `compgen'). The default separator is $' \t\n'. Note that this is
397394# not the set of separators to delimit output of `compgen', but the
398395# separators in evaluating the expansions of `-W '...'`, etc. The
399396# delimiter of the output of `compgen` is always a newline.
400- # -l The same as -F $'\n'.
397+ # -l The same as -F $'\n'. Use lines as words in evaluating compgen.
401398# -c cur Set a word used as a prefix to filter the completions. The default
402399# is ${cur-}.
403- # -R The same as -c ''.
404- # @param $1 array_name The array name
405- # The array name should not start with an underscores "_", which is
406- # internally used. The array name should not be either "IFS" or
407- # "OPT{IND,ARG,ERR}".
408- # @param $2... args The arguments that are passed to compgen
400+ # -R The same as -c ''. Use raw outputs without filtering.
401+ # @var[in,opt] cur Used as the default value of a prefix to filter the
402+ # completions.
403+ #
404+ # Usage #1: _comp_compgen [-alR|-F sep|-v arr|-c cur] -- options...
405+ # Call `compgen` with the specified arguments and store the results in the
406+ # specified array. This function essentially performs arr=($(compgen args...))
407+ # but properly handles shell options, IFS, etc. using _comp_split. This
408+ # function is equivalent to `_comp_split [-a] -l arr "$(IFS=sep; compgen
409+ # args... -- cur)"`, but this pattern is frequent in the codebase and is good
410+ # to separate out as a function for the possible future implementation change.
411+ # @param $1... options Arguments that are passed to compgen (if $1 starts with
412+ # a hyphen `-`).
413+ #
409414# Note: References to positional parameters $1, $2, ... (such as -W '$1')
410415# will not work as expected because these reference the arguments of
411416# `_comp_compgen' instead of those of the caller function. When there are
412417# needs to reference them, save the arguments to an array and reference the
413- # array instead. The array option `-V arr` in bash >= 5.3 should be instead
414- # specified as `-v arr` as a part of `_comp_compgen` options.
415- # @var[in] cur Used as the default value of a prefix to filter the
416- # completions.
418+ # array instead.
419+ #
420+ # Note: The array option `-V arr` in bash >= 5.3 should be instead specified
421+ # as `-v arr` as a part of the `_comp_compgen` options.
422+ #
423+ # Usage #2: _comp_compgen [-alR|-v arr|-c cur] name args...
424+ # Call `_comp_compgen_NAME ARGS...` with the specified options. This provides
425+ # a common interface to call the functions `_comp_compgen_NAME`, which produce
426+ # completion candidates, with custom options [-alR|-v arr|-cur]. The option
427+ # `-F sep` is not used with this usage.
428+ # @param $1... name args Calls the function _comp_compgen_NAME with the
429+ # specified ARGS (if $1 does not start with a hyphen `-`). The options
430+ # [-alR|-v arr|-c cur] are inherited by the child calls of `_comp_compgen`
431+ # inside `_comp_compgen_NAME` unless the child call `_comp_compgen` receives
432+ # overriding options.
433+ # @var[in,opt,internal] _comp_compgen__append
434+ # @var[in,opt,internal] _comp_compgen__var
435+ # @var[in,opt,internal] _comp_compgen__cur
436+ # These variables are internally used to pass the effect of the options
437+ # [-alR|-v arr|-c cur] to the child calls of `_comp_compgen` in
438+ # `_comp_compgen_NAME`.
439+ #
440+ # @remarks Design `_comp_compgen_NAME`: a function that produce completions can
441+ # be defined with the name _comp_compgen_NAME. The function is supposed to
442+ # generate completions by calling `_comp_compgen`. To reflect the options
443+ # specified to the outer calls of `_comp_compgen`, the function should not
444+ # directly modify `COMPREPLY`. To add words, one can call
445+ #
446+ # _comp_compgen -- -W '"${words[@]}"'
447+ #
448+ # To directly add words without filtering by `cur`, one can call
449+ #
450+ # _comp_compgen -R -- -W '"${words[@]}"'
451+ #
452+ # or use the utility `_comp_compgen_set`:
453+ #
454+ # _comp_compgen_set "${words[@]}"
455+ #
456+ # Other nested calls of _comp_compgen can also be used. The function is
457+ # supposed to replace the existing content of the array by default to allow the
458+ # caller control whether to replace or append by the option `-a`.
459+ #
417460_comp_compgen ()
418461{
419- local _append=" " _var=COMPREPLY _cur=${cur-} _ifs=$' \t\n '
420- local -a _split_options=(-l)
462+ local _append=${_comp_compgen__append-}
463+ local _var=${_comp_compgen__var-COMPREPLY}
464+ local _cur=${_comp_compgen__cur-${cur-} }
465+ local _ifs=$' \t\n '
421466
422467 local OPTIND=1 OPTARG=" " OPTERR=0 _opt
423468 while getopts ' :alF:v:Rc:' _opt " $@ " ; do
424469 case $_opt in
425- a) _append=set _split_options+=(-a) ;;
470+ a) _append=set ;;
426471 v)
427472 if [[ $OPTARG == @ (* [^_a-zA-Z0-9]* | [0-9]* | ' ' | _* | IFS| OPTIND| OPTARG| OPTERR) ]]; then
428- printf ' bash_completion: %s: invalid array name `%s' \' ' .\n' " $FUNCNAME " " -v $OPTARG " >&2
473+ printf ' bash_completion: %s: -v: invalid array name `%s' \' ' .\n' " $FUNCNAME " " $OPTARG " >&2
429474 return 2
430475 fi
431476 _var=$OPTARG
@@ -445,17 +490,38 @@ _comp_compgen()
445490 printf ' bash_completion: %s: unexpected number of arguments.\n' " $FUNCNAME " >&2
446491 printf ' usage: %s [-alR|-F SEP|-v ARR|-c CUR] -- ARGS...' " $FUNCNAME " >&2
447492 return 2
448- elif
449- # Note: $* in the below checks would be affected by uncontrolled IFS in
450- # bash >= 5.0, so we need to set IFS to the normal value. The behavior
451- # in bash < 5.0, where unquoted $* in conditional command did not honor
452- # IFS, was a bug.
453- local IFS=$' \t\n '
454- [[ $* == * \$ [0-9]* || $* == * \$\{ [0-9]* ]]
455- then
456- # Note: extglob *\$?(\{)[0-9]* can be extremely slow when the string
457- # "${*:2:_nopt}" becomes longer, so we test \$[0-9] and \$\{[0-9]
458- # separately.
493+ fi
494+
495+ if [[ $1 != -* ]]; then
496+ # usage: _comp_compgen [options] NAME args
497+ if ! declare -F " _comp_compgen_$1 " & > /dev/null; then
498+ printf ' bash_completion: %s: unrecognized category `%s' \' ' (function _comp_compgen_%s not found).\n' " $FUNCNAME " " $1 " " $1 " >&2
499+ return 2
500+ fi
501+
502+ local _comp_compgen__append=$_append
503+ local _comp_compgen__var=$_var
504+ local _comp_compgen__cur=$_cur cur=$_cur
505+ # Note: we use $1 as a part of a function name, and we use $2... as
506+ # arguments to the function if any.
507+ # shellcheck disable=SC2145
508+ _comp_compgen_" $@ "
509+ return
510+ fi
511+
512+ # usage: _comp_compgen [options] -- [compgen_options]
513+
514+ # Note: $* in the below checks would be affected by uncontrolled IFS in
515+ # bash >= 5.0, so we need to set IFS to the normal value. The behavior in
516+ # bash < 5.0, where unquoted $* in conditional command did not honor IFS,
517+ # was a bug.
518+ # Note: Also, ${_cur:+-- "$_cur"} and ${_append:+-a} would be affected by
519+ # uncontrolled IFS.
520+ local IFS=$' \t\n '
521+ # Note: extglob *\$?(\{)[0-9]* can be extremely slow when the string
522+ # "${*:2:_nopt}" becomes longer, so we test \$[0-9] and \$\{[0-9]
523+ # separately.
524+ if [[ $* == * \$ [0-9]* || $* == * \$\{ [0-9]* ]]; then
459525 printf ' bash_completion: %s: positional parameter $1, $2, ... do not work inside this function.\n' " $FUNCNAME " >&2
460526 return 2
461527 fi
@@ -475,7 +541,23 @@ _comp_compgen()
475541 return " $_status "
476542 }
477543
478- _comp_split " ${_split_options[@]} " " $_var " " $_result "
544+ _comp_split -l ${_append: +-a} " $_var " " $_result "
545+ }
546+
547+ # usage: _comp_compgen_set [words...]
548+ # Reset COMPREPLY with the specified WORDS. If no arguments are specified, the
549+ # array is cleared.
550+ #
551+ # When an array name is specified by `-v VAR` in a caller _comp_compgen, the
552+ # array is reset instead of COMPREPLY. When the `-a` flag is specified in a
553+ # caller _comp_compgen, the words are appended to the existing elements of the
554+ # array instead of replacing the existing elements. This function ignores
555+ # ${cur-} or the prefix specified by `-v CUR`.
556+ _comp_compgen_set ()
557+ {
558+ local _append=${_comp_compgen__append-}
559+ local _var=${_comp_compgen__var-COMPREPLY}
560+ eval -- " $_var ${_append: ++} =(\"\$ @\" )"
479561}
480562
481563# Check if the argument looks like a path.
@@ -766,10 +848,9 @@ _comp_quote_compgen()
766848# completions with `.$1' and the uppercase version of it as file
767849# extension.
768850#
769- # TODO: rename per API conventions
770- _filedir ()
851+ _comp_compgen_filedir ()
771852{
772- _tilde " ${cur-} " || return
853+ _comp_compgen_tilde && return
773854
774855 local -a toks
775856 local arg=${1-}
@@ -805,11 +886,15 @@ _filedir()
805886 fi
806887
807888 if (( ${# toks[@]} != 0 )) ; then
808- # 2>/dev/null for direct invocation, e.g. in the _filedir unit test
889+ # 2>/dev/null for direct invocation, e.g. in the _comp_compgen_filedir unit test
809890 compopt -o filenames 2> /dev/null
810- COMPREPLY+=(" ${toks[@]} " )
811891 fi
812- } # _filedir()
892+
893+ # Note: bash < 4.4 has a bug that all the elements are connected with
894+ # ${v-"${a[@]}"} when IFS does not contain whitespace.
895+ local IFS=$' \t\n '
896+ _comp_compgen_set ${toks[@]+" ${toks[@]} " }
897+ } # _comp_compgen_filedir()
813898
814899# This function splits $cur=--foo=bar into $prev=--foo, $cur=bar, making it
815900# easier to support both "--foo bar" and "--foo=bar" style completions.
@@ -955,7 +1040,7 @@ _comp_variable_assignments()
9551040 case $prev in
9561041 TZ)
9571042 cur=/usr/share/zoneinfo/$cur
958- _filedir
1043+ _comp_compgen -a filedir
9591044 if (( ${# COMPREPLY[@]} )) ; then
9601045 for i in " ${! COMPREPLY[@]} " ; do
9611046 if [[ ${COMPREPLY[i]} == * .tab ]]; then
@@ -980,7 +1065,7 @@ _comp_variable_assignments()
9801065 ;;
9811066 * )
9821067 _variables && return 0
983- _filedir
1068+ _comp_compgen -a filedir
9841069 ;;
9851070 esac
9861071
@@ -995,9 +1080,12 @@ _comp_variable_assignments()
9951080#
9961081# Options:
9971082# -n EXCLUDE Passed to _comp_get_words -n with redirection chars
998- # -e XSPEC Passed to _filedir as first arg for stderr redirections
999- # -o XSPEC Passed to _filedir as first arg for other output redirections
1000- # -i XSPEC Passed to _filedir as first arg for stdin redirections
1083+ # -e XSPEC Passed to _comp_compgen_filedir as first arg for stderr
1084+ # redirections
1085+ # -o XSPEC Passed to _comp_compgen_filedir as first arg for other output
1086+ # redirections
1087+ # -i XSPEC Passed to _comp_compgen_filedir as first arg for stdin
1088+ # redirections
10011089# -s Split long options with _comp__split_longopt, implies -n =
10021090# @param $1...$3 args Original arguments specified to the completion function.
10031091# The first argument $1 is command name. The second
@@ -1008,9 +1096,9 @@ _comp_variable_assignments()
10081096# @var[out] prev Reconstructed previous word
10091097# @var[out] words Reconstructed words
10101098# @var[out] cword Current word index in `words`
1011- # @var[out] comp_args Original arguments specified to the completion function
1012- # are saved in this array, if the arguments $1...$3 is
1013- # specified.
1099+ # @var[out] comp_args Original arguments specified to the completion
1100+ # function are saved in this array, if the arguments
1101+ # $1...$3 is specified.
10141102# @var[out,opt] was_split When "-s" is specified, `"set"/""` is set depending
10151103# on whether the split happened.
10161104# @return True (0) if completion needs further processing,
@@ -1066,7 +1154,7 @@ _comp_initialize()
10661154 ;;
10671155 esac
10681156 cur=${cur## " $redir " }
1069- _filedir " $xspec "
1157+ _comp_compgen filedir " $xspec "
10701158 return 1
10711159 fi
10721160
@@ -1370,21 +1458,21 @@ _ncpus()
13701458}
13711459
13721460# Perform tilde (~) completion
1373- # @return True (0 ) if completion needs further processing,
1374- # False (1 ) if tilde is followed by a valid username, completions are
1461+ # @return False (1 ) if completion needs further processing,
1462+ # True (0 ) if tilde is followed by a valid username, completions are
13751463# put in COMPREPLY and no further processing is necessary.
1376- # TODO: rename per API conventions
1377- _tilde ()
1464+ _comp_compgen_tilde ()
13781465{
1379- if [[ ${1 -} == \~ * && $1 != * /* ]]; then
1466+ if [[ ${cur -} == \~ * && $cur != * /* ]]; then
13801467 # Try generate ~username completions
1381- if _comp_compgen -c " ${1# \~ } " -- -P ' ~' -u; then
1382- # 2>/dev/null for direct invocation, e.g. in the _tilde unit test
1468+ if _comp_compgen -c " ${cur# \~ } " -- -P ' ~' -u; then
1469+ # 2>/dev/null for direct invocation, e.g. in the
1470+ # _comp_compgen_tilde unit test
13831471 compopt -o filenames 2> /dev/null
1384- return 1
1472+ return 0
13851473 fi
13861474 fi
1387- return 0
1475+ return 1
13881476}
13891477
13901478# Expand variable starting with tilde (~)
@@ -1431,7 +1519,7 @@ _expand()
14311519 __expand_tilde_by_ref cur
14321520 ;;
14331521 ~ * )
1434- _tilde " $cur " ||
1522+ _comp_compgen -v COMPREPLY tilde &&
14351523 eval " COMPREPLY[0]=$( printf ~ %q " ${COMPREPLY[0]# \~ } " ) "
14361524 return ${# COMPREPLY[@]}
14371525 ;;
@@ -2412,23 +2500,23 @@ _comp_longopt()
24122500 return
24132501 ;;
24142502 --! (no-* )dir* )
2415- _filedir -d
2503+ _comp_compgen -a filedir -d
24162504 return
24172505 ;;
24182506 --! (no-* )@ (file| path)* )
2419- _filedir
2507+ _comp_compgen -a filedir
24202508 return
24212509 ;;
24222510 --+ ([-a-z0-9_]))
24232511 local argtype=$( LC_ALL=C $1 --help 2>&1 | command sed -ne \
24242512 " s|.*$prev \[\{0,1\}=[<[]\{0,1\}\([-A-Za-z0-9_]\{1,\}\).*|\1|p" )
24252513 case ${argtype,,} in
24262514 * dir* )
2427- _filedir -d
2515+ _comp_compgen -a filedir -d
24282516 return
24292517 ;;
24302518 * file* | * path* )
2431- _filedir
2519+ _comp_compgen -a filedir
24322520 return
24332521 ;;
24342522 esac
@@ -2445,10 +2533,10 @@ _comp_longopt()
24452533 done)"
24462534 [[ ${COMPREPLY-} == *= ]] && compopt -o nospace
24472535 elif [[ $1 == *@(rmdir|chroot) ]]; then
2448- _filedir -d
2536+ _comp_compgen -a filedir -d
24492537 else
24502538 [[ $1 == *mkdir ]] && compopt -o nospace
2451- _filedir
2539+ _comp_compgen -a filedir
24522540 fi
24532541}
24542542# makeinfo and texi2dvi are defined elsewhere.
@@ -2468,7 +2556,7 @@ _filedir_xspec()
24682556 local cur prev words cword comp_args
24692557 _comp_initialize -- " $@ " || return
24702558
2471- _tilde " $cur " || return
2559+ _comp_compgen_tilde && return
24722560
24732561 local ret
24742562 _comp_quote_compgen " $cur "
0 commit comments