about summary refs log tree commit diff
path: root/Completion/Core/_multi_parts
blob: 1f51d2f6d9ca410e08e1fc8e44395d449b0c1791 (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
#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

_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'. The `eval' is used to replace our
# separator character by `*<sep>'.

if [[ -o globcomplete ]]; then
  patstr="${PREFIX}*${SUFFIX}*"
else
  patstr="${PREFIX:q}*${SUFFIX:q}*"
fi
orig="${PREFIX}${SUFFIX}"

matchflags=""
_match_pattern _path_files patstr matchflags
[[ -n "$_comp_correct" ]] && matchflags="$matchflags(#a$_comp_correct)"

patstr="${${patstr//$sep/*$sep}//\*##/*}"
#eval patstr\="\$patstr:gs-${sep}-\*${sep}-:gs/\*\*/\*/"

# 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'.

  pat="${${${patstr#*${sep}}%${sep}*}//\*/[^${sep}]#}${patstr##*${sep}}"
  tmp1=( "${(@M)matches:#${~matchflags}${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
  # (which will also remove all words not matching the string at all), 
  # and set `patstr' and `orig' to the next component.

  pref="$pref${orig%%${sep}*}${sep}"
  matches=( "${(@)${(@)matches#${orig%%${sep}*}${sep}}:#}" )
  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
tmp1=( "${(@M)matches:#${~matchflags}${~pat}}" )

if (( $#tmp1 )); then

  # There are words that are matched, put them int `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, but 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" -S '' - "${pref}${orig}"
  elif [[ $compstate[insert] = *menu ]]; then
    for i in "$matches[@]" ; do
      if [[ "$i" = *${sep}* ]]; then
        compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" \
	        -p "$pref" -qS "$sep" - "${i%%${sep}*}"
      else
        compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" \
	        -p "$pref" - "${i%%${sep}*}"
      fi
    done
  else
    for i in "$matches[@]" ; do
      if [[ "$i" = *${sep}* ]]; then
        compadd -U -i "$IPREFIX" -p "$pref" -s "${sep}${i#*${sep}}" \
	        "$group[@]" "$expl[@]" -M "r:|${sep}=*" - "${i%%${sep}*}"
      else
        compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -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.

    tmp1=( "${(@M)matches:#${~matchflags}${~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" -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" -p "$pref" - "$orig"
fi

# This sets the return value to indicate that we added matches (or not).

[[ nm -ne compstate[nmatches] ]]