#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 menu origflags mflags _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'. if [[ $#compstate[pattern_match] -ne 0 ]]; then if [[ "${compstate[pattern_match]-*}" = \** ]]; then str="${PREFIX}*${SUFFIX}*" else str="${PREFIX}${SUFFIX}" fi else patstr="${PREFIX:q}*${SUFFIX:q}*" fi orig="${PREFIX}${SUFFIX}" [[ $compstate[insert] = *menu || -n "$_comp_correct" || ( $#compstate[pattern_match] -ne 0 && "$orig" != "${orig:q}" ) ]] && menu=yes matchflags="" _match_pattern _path_files patstr matchflags origflags="$matchflags" [[ -n "$_comp_correct" ]] && matchflags="$matchflags(#a$_comp_correct)" patstr="${${patstr//$sep/*$sep}//\*##/*}" # 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'. if [[ -n "$_comp_correct" && "${#orig%%${sep}*}" -le _comp_correct ]]; then mflags="$origflags" else mflags="$matchflags" fi pat="${${${patstr#*${sep}}%${sep}*}//\*/[^${sep}]#}" tmp1=( "${(@M)matches:#${~mflags}${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 # (and also remove all words not matching the string at all), and # set `patstr' and `orig' to the next component. tmp1="${orig%%${sep}*}${sep}" pref="$pref$tmp1" matches=("${(@)${(@)${(@M)matches:#${tmp1}*}#$tmp1}:#}") 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 if [[ -n "$_comp_correct" && "${#orig%%${sep}*}" -le _comp_correct ]]; then mflags="$origflags" else mflags="$matchflags" fi tmp1=( "${(@M)matches:#${~mflags}${~pat}}" ) if (( $#tmp1 )); then # There are words that are matched, put them into `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, put 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" -I "$ISUFFIX" -S '' - \ "${pref}${orig}" elif [[ -n "$menu" ]]; then if [[ "$orig" = *${sep}* ]]; then orig="${sep}${orig#*${sep}}" else orig='' fi for i in "$matches[@]" ; do if [[ "$i" = *${sep}* ]]; then compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \ -p "$pref" -s "$orig" - "${i%%${sep}*}${sep}" else compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \ -p "$pref" -s "$orig" - "${i%%${sep}*}" fi done else for i in "$matches[@]" ; do if [[ "$i" = *${sep}* ]]; then compadd -U -i "$IPREFIX" -I "$ISUFFIX" -p "$pref" -s "${i#*${sep}}" \ "$group[@]" "$expl[@]" -M "r:|${sep:q}=*" - "${i%%${sep}*}${sep}" else compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \ -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. if [[ -n "$_comp_correct" && "${#orig%%${sep}*}" -le _comp_correct ]]; then mflags="$origflags" else mflags="$matchflags" fi tmp1=( "${(@M)matches:#${~mflags}${~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" -I "$ISUFFIX" \ -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" -I "$ISUFFIX" \ -p "$pref" - "$orig" fi # This sets the return value to indicate that we added matches (or not). [[ nm -ne compstate[nmatches] ]]