#autoload # This gets two arguments, a separator (which should be only one # character) and an array. As usual, the array may be given by it's # name or literal as in `(foo bar baz)' (words separated by spaces in # parentheses). # The parts of words from the array that are separated by the # separator character are then completed independently. local sep matches patstr orig matchflags pref i tmp1 tmp2 nm local group expl _match_test _multi_parts || return 1 # Save the current number of matches to be able to return if we added # matches or not. nm=$compstate[nmatches] # Get the options. group=() expl=() while getopts "J:V:X:" opt; do case "$opt" in [JV]) group=("-$opt" "$OPTARG");; X) expl=(-X "$OPTARG");; esac done shift OPTIND-1 # Get the arguments, first the separator, then the array. The array is # stored in `matches'. Further on this array will always contain those # words from the original array that still match everything we have # tried to match while we walk through the string from the line. sep="$1" if [[ "${2[1]}" = '(' ]]; then matches=( ${2[2,-2]} ) else matches=( "${(@P)2}" ) fi # Now build the pattern from what we have on the line. We also save # the original string in `orig'. The `eval' is used to replace our # separator character by `*'. if [[ -o globcomplete ]]; then patstr="${PREFIX}*${SUFFIX}*" else patstr="${PREFIX:q}*${SUFFIX:q}*" fi orig="${PREFIX}${SUFFIX}" matchflags="" _match_pattern _path_files patstr matchflags [[ -n "$_comp_correct" ]] && matchflags="$matchflags(#a$_comp_correct)" patstr="${${patstr//$sep/*$sep}//\*##/*}" #eval patstr\="\$patstr:gs-${sep}-\*${sep}-:gs/\*\*/\*/" # First we will skip over those parts of the matches for which we have # exact substrings on the line. In `pref' we will build the # unambiguous prefix string. pref='' while [[ "$orig" = *${sep}* ]] do # First build the pattern to use, then collect all strings from # `matches' that match the prefix we have and the exact substring in # the array `tmp1'. pat="${${${patstr#*${sep}}%${sep}*}//\*/[^${sep}]#}${patstr##*${sep}}" tmp1=( "${(@M)matches:#${~matchflags}${orig%%${sep}*}${sep}${~pat}}" ) # If there are no words matching the exact substring, stop. (( $#tmp1 )) || break # Otherwise add the part to the prefix, remove it from the matches # (which will also remove all words not matching the string at all), # and set `patstr' and `orig' to the next component. pref="$pref${orig%%${sep}*}${sep}" matches=( "${(@)${(@)matches#${orig%%${sep}*}${sep}}:#}" ) orig="${orig#*${sep}}" patstr="${patstr#*${sep}}" done # Now we get all the words that still match in `tmp1'. if [[ "$patstr" = *${sep}* ]]; then tmp1="${patstr%${sep}*}${sep}" pat="${tmp1//\*/[^${sep}]#}${patstr##*${sep}}" else pat="$patstr" fi tmp1=( "${(@M)matches:#${~matchflags}${~pat}}" ) if (( $#tmp1 )); then # There are words that are matched, put them int `matches' and then # move all unambiguous components from the beginning into `pref'. matches=( "$tmp1[@]" ) while [[ "$matches[1]" = *${sep}* ]]; do # We just take the first component of the first match and see if # there are other matches with a different prefix (these are # collected in `tmp2'). If there are any, we give up. tmp1="${matches[1]%%${sep}*}${sep}" tmp2=( "${(@)matches:#${tmp1}*}" ) (( $#tmp2 )) && break # All matches have the same prefix, but it into `pref' and remove # it from the matches. pref="$pref$tmp1" matches=( "${(@)${(@)matches#$tmp1}:#}" ) if [[ "$orig" = *${sep}* ]]; then orig="${orig#*${sep}}" else orig='' fi done # Now we can tell the completion code about the things we # found. Strings that have a separator will be added with a suffix. if [[ -z "$orig" && "$PREFIX$SUFFIX" != "$pref$orig" ]]; then compadd -QU "$group[@]" "$expl[@]" -i "$IPREFIX" -S '' - "${pref}${orig}" elif [[ $compstate[insert] = *menu ]]; then for i in "$matches[@]" ; do if [[ "$i" = *${sep}* ]]; then compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" \ -p "$pref" -qS "$sep" - "${i%%${sep}*}" else compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" \ -p "$pref" - "${i%%${sep}*}" fi done else for i in "$matches[@]" ; do if [[ "$i" = *${sep}* ]]; then compadd -U -i "$IPREFIX" -p "$pref" -s "${sep}${i#*${sep}}" \ "$group[@]" "$expl[@]" -M "r:|${sep}=*" - "${i%%${sep}*}" else compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -p "$pref" - "$i" fi done fi elif [[ "$patstr" = *${sep}* ]]; then # We had no words matching the string from the line. But we want to # be friendly and at least expand the prefix as far as we can. So we # will loop through the rest of the string from the line and test # the components one by one. while [[ "$patstr" = *${sep}* ]]; do # First we get all words matching at least this component in # `tmp1'. If there are none, we give up. tmp1=( "${(@M)matches:#${~matchflags}${~patstr%%${sep}*}${sep}*}" ) (( $#tmp1 )) || break # Then we check if there are words that have a different prefix. tmp2=( "${(@)tmp1:#${tmp1[1]%%${sep}*}${sep}*}" ) if (( $#tmp2 )); then # There are words with another prefix, so we have found an # ambiguous component. So we just give all possible prefixes to # the completion code together with our prefix and the rest of # the string from the line as the suffix. compadd -U "$group[@]" "$expl[@]" -S '' -i "$IPREFIX" -p "$pref" \ -s "${sep}${orig#*${sep}}" - "${(@)matches%%${sep}*}" return 0 fi # All words have the same prefix, so add it to `pref' again and # try the next component. pref="$pref${tmp1[1]%%${sep}*}${sep}" matches=( "${(@)matches#${tmp1[1]%%${sep}*}${sep}}" ) orig="${orig#*${sep}}" patstr="${patstr#*${sep}}" done # Finally, add the unambiguous prefix and the rest of the string # from the line. compadd -U "$group[@]" "$expl[@]" -S '' -i "$IPREFIX" -p "$pref" - "$orig" fi # This sets the return value to indicate that we added matches (or not). [[ nm -ne compstate[nmatches] ]]