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

# This code will try to correct the string on the line based on the
# strings generated for the context if `compconfig[correct]' is set.
# These corrected strings will be shown in a list and one can
# cycle through them as in a menucompletion or get the corrected prefix.
#
# Supported configuration keys:
#
#  approximate_accept
#    This should be set to a number, specifying the maximum number
#    of errors that should be accepted. If the string also contains
#    a `n' or `N', the code will use the numeric argument as the
#    maximum number of errors if a numeric argument was given. If no
#    numeric argument was given, the number from the value of this
#    key will be used. E.g. with `compconf approximate_accept=2n' two
#    errors will be accepted, but if the user gives another number
#    with the numeric argument, this will be prefered. Also, with
#    `compconf approximate_accept=0n', normally no correction will be
#    tried, but if a numeric argument is given, automatic correction
#    will be used. On the other hand, if the string contains an `!'
#    and a `n' or `N', correction is not attempted if a numeric
#    argument is given. Once the number of errors to accept is
#    determined, the code will repeatedly try to generate matches by
#    allowing one error, two errors, and so on. Independent of the
#    number of errors the user wants to accept, the code will allow
#    only fewer errors than there are characters in the string from
#    the line.
#
#  approximate_original
#    This value is used to determine if the original string should
#    be included in the list (and thus be presented to the user when
#    cycling through the corrections). If it is set to any non-empty
#    value, the original string will be offered. If it contains the
#    sub-string `last', the original string will appear as the last
#    string when cycling through the corrections, otherwise it will
#    appear as the first one (so that the command line does not
#    change immediately). Also, if the value contains the sub-string
#    `always', the original string will always be included, whereas
#    normally it is included only if more than one possible
#    correction was generated.
#
#  approximate_prompt
#    This can be set to a string that should be printed before the
#    list of corrected strings when cycling through them. This string
#    may contain the control sequences `%n', `%B', etc. known from
#    the `-X' option of `compctl'. Also, the sequence `%e' will be
#    replaced by the number of errors accepted to generate the
#    corrected strings.
#
#  approximate_insert
#    If this is set to a string starting with `unambig', the code
#    will try to insert a usable unambiguous string in the command
#    line instead of always cycling through the corrected strings.
#    If such a unambiguous string could be found, the original
#    string is not used, independent of the setting of
#    `approximate_original'. If no sensible string could be found,
#    one can cycle through the corrected strings as usual.
#
# If any of these keys is not set, but the the same key with the
# prefix `correct' instead of `approximate' is set, that value will
# be used.

local _comp_correct _correct_prompt comax
local cfgacc cfgorig cfgps cfgins

# Only if all global matchers hav been tried.

[[ compstate[matcher] -ne compstate[total_matchers] ]] && return 1

# We don't try correction if the string is too short.

[[ "${#:-$PREFIX$SUFFIX}" -le 1 ]] && return 1

# Get the configuration values, using either the prefix `correct' or
# `approximate'.

if [[ "$compstate[pattern_match]" = (|\**) ]]; then
  cfgacc="${compconfig[approximate_accept]:-$compconfig[correct_accept]}"
  cfgorig="${compconfig[approximate_original]:-$compconfig[correct_original]}"
  cfgps="${compconfig[approximate_prompt]:-$compconfig[correct_prompt]}"
  cfgins="${compconfig[approximate_insert]:-$compconfig[correct_insert]}"
else
  cfgacc="$compconfig[correct_accept]"
  cfgorig="$compconfig[correct_original]"
  cfgps="$compconfig[correct_prompt]"
  cfgins="$compconfig[correct_insert]"
fi

# Get the number of errors to accept.

if [[ "$cfgacc" = *[nN]* && NUMERIC -ne 1 ]]; then
  # Stop if we also have a `!'.

  [[ "$cfgacc" = *\!* ]] && return 1

  # Prefer the numeric argument if that has a sensible value.

  comax="$NUMERIC"
else
  comax="${cfgacc//[^0-9]}"
fi

# If the number of errors to accept is too small, give up.

[[ "$comax" -lt 1 ]] && return 1

# Otherwise temporarily define functions to use instead of
# the builtins that add matches. This is used to be able
# to stick the `(#a...)' into the right place (after an
# ignored prefix).

compadd() {
  [[ "$*" != *-([a-zA-Z/]#|)U* &&
     "${#:-$PREFIX$SUFFIX}" -le _comp_correct ]] && return

  if [[ "$PREFIX" = \~*/* ]]; then
    PREFIX="${PREFIX%%/*}/(#a${_comp_correct})${PREFIX#*/}"
  else
    PREFIX="(#a${_comp_correct})$PREFIX"
  fi
  if [[ -n "$_correct_prompt" ]]; then
    builtin compadd -X "$_correct_prompt" -J _correct "$@"
  else
    builtin compadd -J _correct "$@"
  fi
}

compgen() {
  [[ "$*" != *-([a-zA-Z/]#|)U* &&
     "${#:-$PREFIX$SUFFIX}" -le _comp_correct ]] && return

  if [[ "$PREFIX" = \~*/* ]]; then
    PREFIX="${PREFIX%%/*}/(#a${_comp_correct})${PREFIX#*/}"
  else
    PREFIX="(#a${_comp_correct})$PREFIX"
  fi
  if [[ -n "$_correct_prompt" ]]; then
    builtin compgen "$@" -X "$_correct_prompt" -J _correct
  else
    builtin compgen "$@" -J _correct
  fi
}

# Now initialise our counter. We also set `compstate[matcher]'
# to `-1'. This allows completion functions to use the simple
# `[[ compstate[matcher] -gt 1 ]] && return' to avoid being
# called for multiple global match specs and still be called 
# again when correction is done. Also, this makes it easy to
# test if correction is attempted since `compstate[matcher]'
# will never be set to a negative value by the completion code.

_comp_correct=1
compstate[matcher]=-1

_correct_prompt="${cfgps//\%e/1}"

# We also need to set `extendedglob' and make the completion
# code behave as if globcomplete were set.

setopt extendedglob

[[ -z "$compstate[pattern_match]" ]] && compstate[pattern_match]='*'

while [[ _comp_correct -le comax ]]; do
  if _complete; then
    if [[ "$cfgins" = unambig* &&
          "${#compstate[unambiguous]}" -ge "${#:-$PREFIX$SUFFIX}" ]]; then
      compstate[pattern_insert]=unambiguous
    elif [[ compstate[nmatches] -gt 1 || "$cfgorig" = *always* ]]; then
      if [[ "$cfgorig" = *last* ]]; then
        builtin compadd -U -V _correct_original -nQ - "$PREFIX$SUFFIX"
      elif [[ -n "$cfgorig" ]]; then
	builtin compadd -U -nQ - "$PREFIX$SUFFIX"
      fi

      # If you always want to see the list of possible corrections,
      # set `compstate[list]=list' here.

      compstate[force_list]=list
    fi
    compstate[matcher]="$compstate[total_matchers]"
    unfunction compadd compgen

    return 0
  fi

  [[ "${#:-$PREFIX$SUFFIX}" -le _comp_correct+1 ]] && break
  (( _comp_correct++ ))

  _correct_prompt="${cfgps//\%e/$_comp_correct}"
done

compstate[matcher]="$compstate[total_matchers]"
unfunction compadd compgen

return 1