about summary refs log tree commit diff
path: root/Completion/Core/_multi_parts
blob: ab94384941befaab2845ace308cd18a0eb10b3e0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#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] ]]