about summary refs log tree commit diff
path: root/Completion/Base/Utility/_multi_parts
blob: 3e2f36c9ced3d92838533563608f5c0fe9227aec (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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#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 pref npref i tmp2 group expl menu pre suf opre osuf orig cpre
local opts sopts matcher imm
typeset -U tmp1 matches

# Get the options.

zparseopts -D -a sopts \
    'J+:=group' 'V+:=group' 'X+:=expl' 'P:=opts' 'F:=opts' \
    S: r: R: q 1 2 n f 'M+:=matcher' 'i=imm'

sopts=( "$sopts[@]" "$opts[@]" )
if (( $#matcher )); then
  matcher="${matcher[2]}"
else
  matcher=
fi

# Get the arguments, first the separator, then the array. The array is 
# stored in `tmp1'. Further on the array `matches' 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
  tmp1=( ${=2[2,-2]} )
else
  tmp1=( "${(@P)2}" )
fi

# In `pre' and `suf' we will hold the prefix and the suffix from the
# line while we walk through them. The original string are used 
# temporarily for matching.

pre="$PREFIX"
suf="$SUFFIX"
opre="$PREFIX"
osuf="$SUFFIX"
orig="$PREFIX$SUFFIX"

# Special handling for menu completion?

[[ $compstate[insert] = (*menu|[0-9]*) || -n "$_comp_correct" ||
   ( $#compstate[pattern_match] -ne 0 &&
     "$orig" != "${orig:q}" ) ]] && menu=yes

# In `pref' we collect the unambiguous prefix path.

pref=''

# If the string from the line matches at least one of the strings,
# we use only the matching strings.

compadd -O matches -M "r:|${sep}=* r:|=* $matcher" -a tmp1

(( $#matches )) || matches=( "$tmp1[@]" )

while true; do

  # Get the prefix and suffix for matching.

  if [[ "$pre" = *${sep}* ]]; then
    PREFIX="${pre%%${sep}*}"
    SUFFIX=""
  else
    PREFIX="${pre}"
    SUFFIX="${suf%%${sep}*}"
  fi

  # Check if the component for some of the possible matches is equal
  # to the string from the line. If there are such strings, we directly
  # use the stuff from the line. This avoids having `foo' complete to
  # both `foo' and `foobar'.

  if [[ -n "$PREFIX$SUFFIX" || "$pre" = ${sep}* ]]; then
    tmp1=( "${(@M)matches:#${PREFIX}${SUFFIX}${sep}*}" )
  else
    tmp1=()
  fi

  if (( $#tmp1 )); then
    npref="${PREFIX}${SUFFIX}${sep}"
  else
    # No exact match, see how many strings match what's on the line.

    builtin compadd -O tmp1 -M "r:|${sep}=* r:|=* $matcher" - "${(@)${(@)matches%%${sep}*}:#}"

    [[ $#tmp1 -eq 0 && -n "$_comp_correct" ]] &&
      compadd -O tmp1 -M "r:|${sep}=* r:|=* $matcher" - "${(@)${(@)matches%%${sep}*}:#}"

    if [[ $#tmp1 -eq 1 ]]; then

      # Only one match. If there are still separators from the line
      # we just accept this component. Otherwise we insert what we 
      # have collected, probably giving it a separator character
      # as a suffix.

      if [[ "$pre$suf" = *${sep}* ]]; then
        npref="${tmp1[1]}${sep}"
      else
        matches=( "${(@M)matches:#${tmp1[1]}*}" )

	PREFIX="${cpre}${pre}"
	SUFFIX="$suf"

	if [[ $#imm -ne 0 && $#matches -eq 1 ]] ||
           zstyle -t ":completion:${curcontext}:" expand suffix; then
	  compadd "$group[@]" "$expl[@]" "$sopts[@]" \
                  -M "r:|${sep}=* r:|=* $matcher" - $pref$matches
        else
	  if (( $matches[(I)${tmp1[1]}${sep}*] )); then
	    compadd "$group[@]" "$expl[@]" -p "$pref" -r "$sep" -S "$sep" "$opts[@]" \
                    -M "r:|${sep}=* r:|=* $matcher" - "$tmp1[1]"
          else
	    compadd "$group[@]" "$expl[@]" -p "$pref" "$sopts[@]" \
                    -M "r:|${sep}=* r:|=* $matcher" - "$tmp1[1]"
          fi
        fi
	return
      fi
    elif (( $#tmp1 )); then
      local ret=1 tt
      local -a mm

      # More than one match. First we get all strings that match the
      # rest from the line.

      PREFIX="$pre"
      SUFFIX="$suf"
      compadd -O matches -M "r:|${sep}=* r:|=* $matcher" -a matches

      if [[ "$pre" = *${sep}* ]]; then
 	PREFIX="${cpre}${pre%%${sep}*}"
	SUFFIX="${sep}${pre#*${sep}}${suf}"
      else
        PREFIX="${cpre}${pre}"
	SUFFIX="$suf"
      fi

      for tt in $tmp1
      do
        mm+=( "${(@M)matches:#$tt*}" )
      done
      matches=( $mm )

      if ! zstyle -t ":completion:${curcontext}:" expand suffix ||
         [[ -n "$menu" || -z "$compstate[insert]" ]]; then

        # With menu completion we add only the ambiguous component with
        # the prefix collected and a separator for the matches that
        # have more components.

        tmp2="$pre$suf"
        if [[ "$tmp2" = *${sep}* ]]; then
          tmp2=(-s "${sep}${tmp2#*${sep}}")
        else
	  tmp2=()
        fi


        compadd "$group[@]" "$expl[@]" -r "$sep" -S "$sep" "$opts[@]" \
	        -p "$pref" "$tmp2[@]" -M "r:|${sep}=* r:|=* $matcher" - \
                "${(@)${(@)${(@M)matches:#*${sep}}%%${sep}*}:#}" && ret=0
        (( $matches[(I)${sep}*] )) &&
            compadd "$group[@]" "$expl[@]" -S '' "$opts[@]" \
	            -p "$pref" \
                    -M "r:|${sep}=* r:|=* $matcher" - "$sep" && ret=0
        compadd "$group[@]" "$expl[@]" -r "$sep" -S "$sep" "$opts[@]" \
                -p "$pref" "$tmp2[@]" -M "r:|${sep}=* r:|=* $matcher" - \
                "${(@)${(@)${(@M)matches:#*?${sep}?*}%%${sep}*}:#}" && ret=0
        compadd "$group[@]" "$expl[@]" -S '' "$opts[@]" -p "$pref" "$tmp2[@]" \
                -M "r:|${sep}=* r:|=* $matcher" - \
                "${(@)matches:#*${sep}*}" && ret=0
      else
        # With normal completion we add all matches one-by-one with
	# the unmatched part as a suffix. This will insert the longest
	# unambiguous string for all matching strings.

        compadd "$group[@]" "$expl[@]" "$opts[@]" \
	        -p "$pref" -s "${i#*${sep}}" \
                -M "r:|${sep}=* r:|=* $matcher" - \
                "${(@)${(@)${(@M)matches:#*${sep}*}%%${sep}*}:#}" && ret=0
        compadd "$group[@]" "$expl[@]" -S '' "$opts[@]" -p "$pref" \
                -M "r:|${sep}=* r:|=* $matcher" - \
                "${(@)matches:#*${sep}*}" && ret=0
      fi
      return ret
    else
      # We are here if no string matched what's on the line. In this
      # case we insert the expanded prefix we collected if it differs
      # from the original string from the line.

      { ! zstyle -t ":completion:${curcontext}:" expand prefix ||
        [[ "$orig" = "$pref$pre$suf" ]] } && return 1

      PREFIX="${cpre}${pre}"
      SUFFIX="$suf"

      if [[ -n "$suf" ]]; then
        compadd "$group[@]" "$expl[@]" -s "$suf" "$sopts[@]" \
                -M "r:|${sep}=* r:|=* $matcher" - "$pref$pre"
      else
        compadd "$group[@]" "$expl[@]" -S '' "$opts[@]" \
                -M "r:|${sep}=* r:|=* $matcher" - "$pref$pre"
      fi
      return
    fi
  fi

  # We just accepted and/or expanded a component from the line. We
  # remove it from the matches (using only those that have a least
  # the skipped string) and ad it the `pref'.

  matches=( "${(@)${(@)${(@M)matches:#${npref}*}#*${sep}}:#}" )
  pref="$pref$npref"

  # Now we set `pre' and `suf' to their new values.

  if [[ "$pre" = *${sep}* ]]; then
    cpre="${cpre}${pre%%${sep}*}${sep}"
    pre="${pre#*${sep}}"
  elif [[ "$suf" = *${sep}* ]]; then
    cpre="${cpre}${pre}${suf%%${sep}*}${sep}"
    pre="${suf#*${sep}}"
    suf=""
  else
    # The string from the line is fully handled. If we collected an
    # unambiguous prefix and that differs from the original string,
    # we insert it.

    PREFIX="${opre}${osuf}"
    SUFFIX=""

    if [[ -n "$pref" && "$orig" != "$pref" ]]; then
      if [[ "$pref" = *${sep}*${sep} ]]; then
        compadd "$group[@]" "$expl[@]" "$opts[@]" \
                -p "${pref%${sep}*${sep}}${sep}" -S "$sep" \
                -M "r:|${sep}=* r:|=* $matcher" - "${${pref%${sep}}##*${sep}}"

      elif [[ "$pref" = *${sep}* ]]; then
        compadd "$group[@]" "$expl[@]" -S '' "$opts[@]" \
                -p "${pref%${sep}*}${sep}" \
                -M "r:|${sep}=* r:|=* $matcher" - "${pref##*${sep}}"
      else
        compadd "$group[@]" "$expl[@]" -S '' "$opts[@]" \
                -M "r:|${sep}=* r:|=* $matcher" - "$pref"
      fi
    fi
    return
  fi
done