about summary refs log tree commit diff
path: root/Completion/Core/_multi_parts
blob: 935b13c74aa4703bbdd0c2900de7b120189222bf (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
#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 pref npref i tmp1 group expl menu pre suf opre osuf cpre
local opts sopts match
typeset -U tmp2

# Get the options.

group=()
expl=()
opts=()
sopts=()
while getopts "J:V:X:P:S:r:R:qM:12n" opt; do
  case "$opt" in
  [JV])   group=("-$opt" "$OPTARG");;
  X)      expl=(-X "$OPTARG");;
  P)      opts=( "$opts[@]" -P "$OPTARG")
          sopts=( "$sopts[@]" -P "$OPTARG");;
  [SrR])  sopts=( "$sopts[@]" -P "$OPTARG");;
  [q12n]) sopts=( "$sopts[@]" "-$opt");;
  M)      match="$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

# 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 menucompletion?

[[ $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 tmp1 -M "r:|${sep}=* r:|=* $match" - "$matches[@]"

(( $#tmp1 )) && 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'.

  tmp1=( "${(@M)matches:#${PREFIX}${SUFFIX}${sep}*}" )

  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 - "${(@)matches%%${sep}*}"

    [[ $#tmp1 -eq 0 && -n "$_comp_correct" ]] &&
      compadd -O tmp1 - "${(@)matches%%${sep}*}"

    tmp2=( "$tmp1[@]" )
    tmp1=( "$tmp2[@]" )

    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]}*}" )
	tmp2=( "${(@M)matches:#${tmp1[1]}${sep}*}" )

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

	if (( $#tmp2 )); then
	  compadd "$group[@]" "$expl[@]" -p "$pref" -qS "$sep" "$opts[@]" \
                  -M "r:|${sep}=* r:|=* $match" - "$tmp1[1]"
        else
	  compadd "$group[@]" "$expl[@]" -p "$pref" "$sopts[@]" \
                  -M "r:|${sep}=* r:|=* $match" - "$tmp1[1]"
        fi
	return 0
      fi
    elif (( $#tmp1 )); then

      # 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:|=* $match" - "$matches[@]"

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

      if [[ -n "$menu" ]]; then
        # With menucompletion we just add matches for the matching
        # components with the prefix we collected and the rest from the
        # line as a suffix.

        tmp2="$pre$suf"
        if [[ "$tmp2" = *${sep}* ]]; then
          compadd "$group[@]" "$expl[@]" "$sopts[@]" \
                  -p "$pref" -s "${sep}${tmp2#*${sep}}" \
                  -M "r:|${sep}=* r:|=* $match" - "$tmp1[@]"
        else
          compadd "$group[@]" "$expl[@]" -p "$pref" "$sopts[@]" \
                  -M "r:|${sep}=* r:|=* $match" - "$tmp1[@]"
        fi
      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.

        for i in "${(@M)matches:#(${(j:|:)~tmp1})*}"; do
	  if [[ "$i" = *${sep}* ]]; then
            compadd "$group[@]" "$expl[@]" -S '' "$opts[@]" \
	            -p "$pref" -s "${i#*${sep}}" \
                    -M "r:|${sep}=* r:|=* $match" - "${i%%${sep}*}${sep}"
          else
            compadd "$group[@]" "$expl[@]" -S '' "$opts[@]" -p "$pref" \
                    -M "r:|${sep}=* r:|=* $match" - "$i"
          fi
        done
      fi
      return 0
    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.

      [[ "$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:|=* $match" - "$pref$pre"
      else
        compadd "$group[@]" "$expl[@]" -S '' "$opts[@]" \
                -M "r:|${sep}=* r:|=* $match" - "$pref$pre"
      fi
      return 0
    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=""

    [[ -n "$pref" && "$orig" != "$pref" ]] &&
        compadd "$group[@]" "$expl[@]" -S '' "$opts[@]" \
                -M "r:|${sep}=* r:|=* $match" - "$pref"

    return
  fi
done