summaryrefslogtreecommitdiff
path: root/bash_completion
diff options
context:
space:
mode:
authorAndre Noll <maan@tuebingen.mpg.de>2025-04-19 13:39:26 +0200
committerAndre Noll <maan@tuebingen.mpg.de>2025-05-18 19:19:02 +0200
commitea69a51bdb003dc948d8ce3a054fd418a42cfc44 (patch)
tree84273c8a36bfa619220837c129bbe32ed45f6f30 /bash_completion
parent69d4838d0ae1eedb59166146dd3f1ead902dd6fc (diff)
Revamp bash_completion.
This rewrite of the bash_completion script improves completion for the para_client and para_audioc commands significantly. One flaw which is fixed by this patch is related to the fact that by default readline creates COMP_WORDS by splitting at characters that separate a shell command, including "=", but for paraslash commands we only want to split at spaces. So we modify the completer to return the special code 124 to instruct readline to try split again with space as the only delimiter.
Diffstat (limited to 'bash_completion')
-rw-r--r--bash_completion123
1 files changed, 64 insertions, 59 deletions
diff --git a/bash_completion b/bash_completion
index 5135101b..c3de2181 100644
--- a/bash_completion
+++ b/bash_completion
@@ -1,12 +1,11 @@
# Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING.
+
_para_complete()
{
- local prg="$1" # the program to execute
- local cur=${COMP_WORDS[$COMP_CWORD]}
- local line="$COMP_LINE" OLD_IFS="$IFS"
- local opts n
-
- # This extracts short and long options from the help output
+ local prg=$1 # the program to execute, either para_client or para_audioc
+ local cur OLD_IFS list opt
+ local -i n old_extglob=1 ddpos=-1 # position of the double-dash arg
+ # This awk script extracts short and long options from the help output.
local script='{
if ($1 ~ "^-[a-zA-Z]," && $2 ~ "^--[a-zA-Z]") {
print substr($1, 0, 2);
@@ -18,77 +17,83 @@ _para_complete()
}
}'
- if [[ "$cur" == -* ]]; then # option
- # Depending on whether '--' is one of the previous words we
- # complete either on local options, i.e. those of the program
- # to execute, or call the program to print possible completions
- # (to a subcommand).
- local_opts=true
- for ((i=0; i < $COMP_CWORD; i++)); do
- [[ "${COMP_WORDS[$i]}" != '--' ]] && continue
- local_opts=false
- break
- done
- if [[ "$local_opts" == "true" ]]; then
- result="-- $($prg --help | awk "$script")"
- COMPREPLY=($(compgen -W "$result" -- $cur))
+ # Ask readline to recreate COMP_WORDS using space as the only delimiter.
+ if [[ "$COMP_WORDBREAKS" != ' ' ]]; then
+ COMP_WORDBREAKS=' '
+ return 124
+ fi
+ n=$COMP_POINT
+ cur=
+ while ((n > 0)); do
+ let n--;
+ [[ "${COMP_LINE:$n:1}" == ' ' ]] && break
+ cur="${COMP_LINE:$n:1}$cur"
+ done
+ for ((n = 0; n < $COMP_CWORD; n++)); do
+ [[ "${COMP_WORDS[$n]}" != '--' ]] && continue
+ ddpos=$n
+ break
+ done
+ # Figure out whether we need to complete according to options of the
+ # given program or according to its subcommands. The 'para' shortcut
+ # is expected to be aliased to 'para_client --' and we always complete
+ # on subcommands in this case.
+ if [[ "$cur" == -* ]] && [[ "${COMP_WORDS[0]}" != 'para' ]]; then
+ # If '--' is none of the previous words, we complete on options
+ # of the given program.
+ if ((ddpos < 0 || COMP_CWORD < ddpos)); then
+ list=$($prg --help | awk "$script")
+ COMPREPLY=($(compgen -W "$list" -- "$cur"))
return
fi
fi
- # We need to call the program with --complete to get the possible
- # completions. Before that, all local options must be discarded.
- IFS=' '
- n=0
- for word in $line; do
- ((n > 0)) && ! [[ "$word" == -* ]] && break
- line="${line##*( )}" # remove leading whitespace
- line="${line##+([^ ])}"
- line="${line##*( )}"
- let n++
- [[ "$word" == '--' ]] && break
- done
- IFS="$OLD_IFS"
- s=$((${#COMP_LINE} - ${#line})) # how many characters have been cut
- if (($COMP_POINT > $s)); then
- COMP_POINT=$(($COMP_POINT - $s))
+ # Rebuild the command line with the first word and anything up to
+ # "--" stripped off. Then call the given program with --complete on
+ # the remainder. Treat the 'para' shortcut as if no double-dash had
+ # been given. i.e. only strip off the first word.
+ if [[ "${COMP_WORDS[0]}" == 'para' ]] || ((ddpos < 0)); then
+ n=0
else
- COMP_POINT=0
+ n=$ddpos
fi
- COMP_LINE="$line"
- #echo "line: $COMP_LINE, point: $COMP_POINT"
- export COMP_LINE COMP_POINT
- result=($($prg --complete))
-
- # the last line of the output contains the options for compopt,
- # prefixed with '-o='.
- n=${#result[@]}
- (($n == 0)) && return # oops, $prg did not write any output
- let n--
- opts="${result[$n]}"
- result[$n]=
- opts="${opts#-o=}"
+ shopt -pq extglob || old_extglob=0
+ shopt -s extglob
+ list=${COMP_LINE:0:$COMP_POINT}
+ while ((n >= 0)); do
+ list=${list##+([^ ])} # remove argument
+ list=${list##*( )} # remove leading whitespace
+ let n--
+ done
+ ((!old_extglob)) && shopt -u extglob
+ # prg relies on COMP_POINT and COMP_LINE, so adjust these
+ export COMP_POINT=${#list}
+ export COMP_LINE=$list
+ COMPREPLY=($($prg --complete 2>/dev/null))
+ ((${#COMPREPLY[@]} == 0)) && return # oops, $prg did not write any output
+ # The last line of the output contains the options for compopt, prefixed
+ # with '-o='.
+ n=$((${#COMPREPLY[@]} - 1))
+ OLD_IFS=$IFS
IFS=','
- compopt +o nospace
- for opt in $opts; do
- #echo "opt: $opt"
+ for opt in ${COMPREPLY[$n]#-o=}; do
case "$opt" in
- filenames) compopt -o filenames;;
+ filenames) compopt -o default;;
nospace) compopt -o nospace;;
esac
done
- IFS="$OLD_IFS"
- COMPREPLY=(${result[@]})
+ IFS=$OLD_IFS
+ unset COMPREPLY[$n]
+ COMPREPLY=($(compgen -W "${COMPREPLY[*]}" -- "$cur"))
}
_para_audioc()
{
_para_complete para_audioc
}
-complete -F _para_audioc para_audioc
_para_client()
{
_para_complete para_client
}
-complete -o default -o nospace -F _para_client para_client
-complete -o default -o nospace -F _para_client para
+complete -F _para_audioc para_audioc
+complete -F _para_client para_client para