about summary refs log tree commit diff
path: root/Completion/Core/compinstall
blob: ad05cb5a1f2a6da7f0eccc6745140039c627388b (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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# This script is to be run by a user to set up the new function based
# completion system.  The functions themselves are assumed to be already
# available in some directory; they should have been installed with the
# the shell.  If they have been, the commands `autoload -U compinit; compinit'
# in the shell startup file should be enough, although you can run
# compinstall for more configuration choices.
#
# Simply run this script as a function and answer the questions.
# Normally it will alter ~/.zshrc (or wherever ZDOTDIR puts it), but you
# can make that unwritable and it will leave the lines in a temporary file
# instead.  It doesn't matter if .zshrc didn't exist before.  If your
# .zshrc usually exits before the end, then you should take the code added
# by compinstall and put it (including the comment lines at the start and
# end) at the point you want it to be executed.  If you run compinstall
# again it will find and replace those lines, so you can use this script to
# modify what compinstall previously added to ~/.zshrc.
#
# It is safe to abort with ^C any time you are being prompted for
# information; your .zshrc will not be altered.
#
# To do:
#  - Should probably offer to set different options for _approximate than
#    for _complete if both are used.
#  - Could add code for setting other completers and options.
#  - Could add keys for context-sensitive help.


emulate -L zsh

typeset _ci_options _ci_f _ci_fdir _ci_files _ci_dumpfile _ci_lines
typeset _ci_type _ci_completer _ci_accept _ci_cprompt _ci_startline
typeset _ci_endline _ci_ifile _ci_tmpf _ci_compstyle _ci_warn
typeset _ci_dtype _ci_existing _ci_line _ci_end

# Look for the defaults.
_ci_startline='# The following lines were added by compinstall'
_ci_endline='# End of lines added by compinstall'

_ci_ifile=${ZDOTDIR:-~}/.zshrc
_ci_lines=''
_ci_existing=''

typeset -A _ci_defaults

if [[ -f $_ci_ifile ]]; then
  # This assumes the lines haven't been altered by the user too much
  # after they were added.
  _ci_compstyle=0
  sed -n "/^$_ci_startline/,/^$_ci_endline/p" $_ci_ifile |
  while read -rA _ci_line; do
    if (( $_ci_compstyle )); then
      # parse a compstyle component as first argument
      if [[ $_ci_line[-1] != \\ ]]; then
	_ci_end=-1
	_ci_compstyle=0
      else
	_ci_end=-2
      fi
      if [[ $_ci_line[1] = *=* ]]; then
	_ci_f="${${_ci_line[1,$_ci_end]}#*=}"
	if [[ $_ci_f = \'*\' ]]; then
	  # strip quotes
	  _ci_f=${_ci_f[2,-2]//\'\\\'\'/\'}
	fi
	_ci_defaults[${_ci_line[1]%%\=*}]=$_ci_f
      fi
      _ci_existing="${_ci_existing}  $_ci_line
"
    elif [[ $_ci_line[1] = compinit ]]; then
      # parse the line running compinit
      [[ $_ci_line[2] = -f ]]  && _ci_fdir=$_ci_line[3]
      [[ $_ci_line[-2] = -d ]] && _ci_dumpfile=$_ci_line[-1]
    elif [[ $_ci_line[1] = _compdir=* ]]; then
      _ci_fdir=${_ci_line[1]##_compdir=}
    elif [[ $_ci_line[1] = compstyle ]]; then
      # parse a compstyle component as second argument (should be completer)
      [[ $_ci_line[3] = completer ]] &&
        _ci_completer=${_ci_line[3,-1]}
      [[ $_ci_line[-1] == \\ ]] && _ci_compstyle=1
      _ci_existing="${_ci_existing}$_ci_line
"
    elif [[ $_ci_line[1] != \#* && $_ci_line[1] != (autoload|\[\[) ]]; then
      if [[ -z $_ci_warn ]]; then
	_ci_warn=1
	print "Warning:  existing lines in compinstall setup not understood:"
      fi
      print - $_ci_line
      _ci_existing="${_ci_existing}$_ci_line
"
    fi
  done
fi


# Find out where the completion functions are kept.

if [[ -z $_ci_fdir || ! -f ${~_ci_fdir}/compinit ||
  ! -f ${~_ci_fdir}/compdump ]]; then
  for _ci_f in $fpath; do
    if [[ $_ci_f != . && -f $_ci_f/compinit && -f $_ci_f/compdump ]]; then
      _ci_fdir=$_ci_f
      break
    elif [[ $_ci_f != . && -f $_ci_f/Core/compinit &&
      -f $_ci_f/Core/compdump ]]
    then
      _ci_fdir=$_ci_f/Core
      break
    fi
  done
fi

if [[ -z $_ci_fdir || ! -d ${~_ci_fdir} ]]; then
  print \
"Please edit the name of the directory where the completion functions are
installed.  If they are not installed, you will need to find them in the
Completion/* directories of the zsh distribution and install them yourself,
or insult your system manager for incompetence."
  vared -c _ci_fdir
  while [[ ! -d ${~_ci_fdir} || 
    ((! -f ${~_ci_fdir}/compinit || ! -f ${~_ci_fdir}/compdump) &&
    (! -f ${~_ci_fdir}/Core/compinit || ! -f ${~_ci_fdir}/Core/compdump)) ]]
  do
    print "I can't find them in that directory.  Try again or abort."
    vared _ci_fdir
  done
  if [[ -f ${~_ci_fdir}/Core/compinit && ! -f ${~_ci_fdir}/compinit ]]; then
    _ci_fdir=$_ci_fdir/Core
  fi
else
  print "Keeping existing completion directiory $_ci_fdir"
fi

if [[ ${~_ci_fdir} != /* ]]; then
  _ci_fdir=$(cd $_ci_fdir;builtin pwd)
fi

# Check if this is in fpath already, else put it there (with ~'s expanded).
_ci_f=${~_ci_fdir}
[[ -z ${fpath[(r)$_ci_f]} ]] && fpath=($fpath $_ci_f)

# Contract $HOME to ~ in the parameter to be used for writing.
_ci_fdir=${_ci_fdir/#$HOME/\~}

# Now check the fpath, ignoring the directory .
_ci_files=( ${^~fpath:/.}/_(|*[^~])(N:t) )
if [[ $#_ci_files -lt 20 ]]; then
  print "
Hmmm, completion functions seem a bit thin on the ground.  There should
be lots of files with names beginning with an underscore (_).  You should
look and see what's happened to these.
[Hit return to continue]"
  read
fi


# Set up the dumpfile
_ci_dtype=existing
if [[ -z $_ci_dumpfile ]]; then
  _ci_dumpfile="${ZDOTDIR:-$HOME}/.zcompdump"
  _ci_dtype=standard
fi

if [[ -w ${~_ci_dumpfile:h} && ( ! -f ${~_ci_dumpfile} ||
  -w ${~_ci_dumpfile} ) ]]
then
  print "
Using $_ci_dtype dumpfile
  ${_ci_dumpfile}
to speed up initialisation.
[Hit return to continue]"
  read
else
  print "
I will force completion to dump its status, which will speed up the shell's
start-up considerably.  However, I can't write the file I'd like to, namely
${_ci_dumpfile}.  Please edit a replacement."
  vared _ci_dumpfile
  while ! touch ${~_ci_dumpfile} >& /dev/null; do
    print "Sorry, I can't write that either.  Try again."
    vared _ci_dumpfile
  done
  [[ -s $_ci_dumpfile ]] || rm -f $_ci_dumpfile
fi

_ci_lines="${_ci_lines}_compdir=$_ci_fdir
[[ -z \$fpath[(r)\$_compdir] ]] && fpath=(\$fpath \$_compdir)
autoload -U compinit
compinit"
[[ $_ci_dtype != standard ]] && _ci_lines="${_ci_lines} $_ci_dumpfile"
_ci_lines="${_ci_lines}
"


print "
Would you like to set some more advanced options?  Otherwise, you
can re-run compinstall later to set these. [n]"

# The whole of the next part should be indented, but I can't be bothered.
if read -q; then

 print "
In addition to completion, zsh can also perform correction of the
current word, or approximate completion, i.e. completion where the part of
the word typed so far can be corrected; or it can try correction, then
approximate completion if that fails.  Would you like:
  0:  Just ordinary completion
  C:  Correction
  A:  Approximate completion
  B:  Both"
  if [[ -n $_ci_completer ]]; then
    print "  Default: use the current completers:\n$_ci_completer"
  else
    print "Please type one of the keys above."
  fi
  while read -k _ci_type; do
    print
    case $_ci_type in
      0*) _ci_completer=_complete
	  break
	  ;;
      [cC]*) _ci_completer='_complete _correct'
	     break
	     ;;
      [aA]*) _ci_completer='_complete _approximate'
	     break;
	     ;;
      [bB]*) _ci_completer='_complete _correct _approximate'
	     break
	     ;;
      *) [[ -n $_ci_completer ]] && break
	 print Try again
	 ;;
    esac
  done

  _ci_lines="${_ci_lines}zstyle ':completion*' completer $_ci_completer"


  if [[ $_ci_completer = *(correct|approx)* ]]; then
    _ci_accept=${_ci_defaults[correct_accept]}
    _ci_cprompt=${_ci_defaults[correct_prompt]}
    print "
Correction and approximation will allow up to ${${_ci_accept:-2}%%[^0-9]*} \
errors. "
    case $_ci_accept in
      *n*!*|*!*n) print "A numeric prefix, if not 1, will cause correction \
not to be done."
		  ;;
      *n*) print "A numeric prefix gives the maximum number of errors which \
will be accepted."
           ;;
      *) print "The numeric prefix will not be used."
    esac
print "The correction prompt is \`${_ci_cprompt:-correct to:}'.
Do you want to change any of this? [n]"
    if read -q; then
      print "Number of errors to accept normally (0 is OK):"
      _ci_accept=${_ci_accept%%[^0-9]*}
      vared _ci_accept
      while [[ $_ci_accept != <-> ]]; do
	print "Please enter a number:"
	vared _ci_accept
      done
      print \
"How would you like the numeric prefix to be treated:
  0:  Not used by correction
  U:  The number gives the largest number of errors which will be
      accepted when correcting
  I:  If present, and not 1, do not perform correction?
Please type one of the keys above:"
      while read -k _ci_type; do
	print
	case $_ci_type in
	  0*) break
	      ;;
	  [uU]*) _ci_accept="${_ci_accept}n"
		 break
		 ;;
	  [Ii]*) _ci_accept="${_ci_accept}!n"
		 break
		 ;;
	  *) print Try again
	     ;;
	esac
      done
      print "
Instead of the prompt \`correct to:', you can have no prompt, or a
prompt of your choosing which can display the number of errors found by
containing the string \`%e'.  Do you wish to change the correction
prompt? [n]"
      if read -q; then
	print "Edit a new prompt (may be empty):"
	vared _ci_cprompt
	[[ -z $_ci_cprompt ]] && _ci_cprompt=':empty:'
      fi
    fi
    if [[ -n $_ci_accept ]]; then
      _ci_lines="$_ci_lines \\
  correct_accept='$_ci_accept'"
      unset '_ci_defaults[correct_accept]'
    fi
    if [[ -n $_ci_cprompt ]]; then
      _ci_cprompt=${_ci_cprompt##:empty:}
      _ci_lines="$_ci_lines \\
  correct_prompt='${_ci_cprompt//\'/\'\\\'\'}'"
      unset '_ci_defaults[correct_prompt]'
    fi
  fi

  _ci_warn=''
  for _ci_f in ${(k)_ci_defaults}; do
    if [[ -z $_ci_warn ]]; then
      print "
(Keeping other existing configuration settings...)"
      _ci_warn=1
    fi
    _ci_lines="$_ci_lines \\
  ${_ci_f}='${_ci_defaults[$_ci_f]//\'/\'\\\'\'}'"
  done

  _ci_lines="$_ci_lines
"

else

  if [[ -n $_ci_existing ]]; then
    print -nr "
I will retain the following lines from the existing completion setup:
$_ci_existing"
    _ci_lines="$_ci_lines${_ci_existing}"
  fi

fi				# End of advanced options


[[ -f $_ci_ifile ]] || touch $_ci_ifile
_ci_tmpf=${TMPPPREFIX:-/tmp/zsh}compinstall$$

if [[ ! -w $_ci_ifile ]]; then
  print "\nI can't write to $_ci_ifile.  I will leave the lines to add in
\`$_ci_tmpf' and you must add them by hand."
  print -r - "$_ci_startline
$_ci_lines$_ci_endline" >$_ci_tmpf
elif grep $_ci_endline $_ci_ifile >& /dev/null; then
  print -r - "$_ci_startline
$_ci_lines$_ci_endline" >$_ci_tmpf
  sed -e "/^$_ci_endline/r $_ci_tmpf
/^$_ci_startline/,/^$_ci_endline/d" $_ci_ifile >${_ci_tmpf}2 && 
  mv ${_ci_tmpf}2 $_ci_ifile &&
  print "\nSuccesfully modified old compinstall lines in $_ci_ifile."
  rm -f $_ci_tmpf ${_ci_tmpf}2
else
  print -r - "$_ci_startline
$_ci_lines$_ci_endline" >>$_ci_ifile &&
  print "\nSuccessfully appended lines to $_ci_ifile."
fi

unfunction compinstall
autoload -U compinstall

return 0