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
260
261
|
#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' 'X+:=expl' 'P:=opts' 'F:=opts' \
S: r: R: q 1 2 o+: n 'f=opts' '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
# 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
# The purpose of this check (or one purpose, anyway) seems to be to ensure
# that the suffix for the current segment on the command line doesn't
# match across segments. For example, we want $matches for a<TAB>c to
# include abc/d, but not abd/c. If we don't have anything on the command
# line for this segment, though, we can skip it. (The difference is only
# noticeable when there are a huge number of possibilities)
[[ -n $pre$suf ]] &&
matches=( ${(@M)matches:#(${(j<|>)~${(@b)tmp1}})*} )
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
|