experimental automatic setarch generation
[shellsnippets/shellsnippets.git] / zsh / vcsupinit
1 ##########
2 #
3 # This is based on an idea by Junio C Hamano on the git mailing list
4 # in <7vd3mchumz.fsf@alter.siamese.dyndns.org>:
5 #
6 #   <http://article.gmane.org/gmane.comp.version-control.git/168132>
7 #
8 # I want that, too, but not just for `git'. With `vcs_info' we can do
9 # quite a bit better, since all the information is already there. We
10 # just need to export it.
11 #
12 # To use this code, copy this file somewhere into `$fpath' and call:
13 #    % autoload -Uz vcsupinit; vcsupinit;
14 #
15 # Please make sure to do this *after* calling `compinit', if you want
16 # the included completion functions to work.
17 #
18 ##########
19 #
20 # LICENCE
21 #
22 #   Copyright (c) 2011, Frank Terbeck <ft@bewatermyfriend.org>
23 #
24 #   Permission to use, copy, modify, and/or distribute this software for any
25 #   purpose with or without fee is hereby granted, provided that the above
26 #   copyright notice and this permission notice appear in all copies.
27 #
28 #   THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
29 #   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
30 #   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
31 #   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
32 #   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
33 #   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
34 #   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35 #
36 ##########
37 #
38 # REQUIREMENTS:
39 #
40 #   - A fairly recent zsh; I think most 4.3.* versions and newer should do.
41 #   - A very new `vcs_info'. The one that will be included in 4.3.12 will do.
42 #
43 # It's probably easiest to build a current `zsh' snapshot from CVS or git
44 # to use with this. If you don't want to do that, you'll have to wait for
45 # zsh-4.3.12 to be released.
46 #
47 ##########
48 #
49 # Here is how it works:
50 #
51 # When a short command is called from a directory within a vcs repository,
52 # zsh should cd to the base directory of the repository and push the old
53 # directory to a stack. Calling that short command from the base directory
54 # should cd back to the topmost directory on the stack. Say that short
55 # command is a comma `,'.
56 #
57 ##########
58 #
59 # So much for basic operation. Here is a list of other actions possible:
60 #
61 # # Change to the top-most directory on the stack (this is basically the
62 # # same as calling the command without options):
63 #    % , -0
64 #    % , -
65 #
66 # # Change to the directory at the bottom of the stack:
67 #    % , +0
68 #    % , +
69 #
70 # # If you exchange the `0' for any integer `N', you can pick directories
71 # # other than the top-most directory or the one at the bottom of the
72 # # stack. For example, the following call picks the directory two steps
73 # # down the stack from the top-most stack entry (this works the same from
74 # # the bottom with a plus instead of a minus):
75 #    % , -2
76 #
77 # # If you would like to jump the repository's root directory without
78 # # pushing the current directory onto the stack (or any other side-effects
79 # # for that matter), do:
80 #    % , --
81 #
82 # # Finally, you can use the command instead of the `cd' builtin to chpwd
83 # # around. Side-effects with repect to the stack are configurable.
84 #    % , foobar/baz
85 #
86 ##########
87 #
88 # There is also a utility, which grants full control over the directory
89 # stack. It is called `vcsuptool' and can be called in several ways:
90 #
91 # # Push one or more items onto the stack:
92 #    % vcsuptool push item0 item1 item2 ...
93 #
94 # # Pop the top-most entry off the stack and put it into `$REPLY':
95 #    % vcsuptool pop
96 #
97 # # Pop the entry at a given index off the stack and put it into `$REPLY':
98 #    % vcsuptool pop 4
99 #
100 # # Show the current contents of the stack:
101 #    % vcsuptool list
102 #
103 # # Delete an entry from the stack by name:
104 #    % vcsuptool del /foo/bar/baz
105 #
106 # # Delete all entries of a certain name from the stack:
107 #    % vcsuptool del -a /foo/bar/baz
108 #
109 # # Clear the stack entirely:
110 #    % vcsuptool clear
111 #
112 ##########
113 #
114 # Both, the comma command and the `vcsuptool' utility, come with a decent
115 # compsys completion.
116 #
117 ##########
118 #
119 # The following styles are recognised in the ":vcsup:settings:<cntxt>"
120 # context:
121 #
122 #   always-to-root      (boolean) If called without argument, make sure
123 #                       to go the the repository's base directory, even
124 #                       if already in it. If `false', jump back to the
125 #                       top-most directory on the stack. (default: false)
126 #   stack-auto-push     (boolean) Every directory change pushes the
127 #                       old directory onto the stack. (default: false)
128 #   stack-auto-push-cd  (boolean) Let even ordinary cd-builtin invocations
129 #                       push directories onto the stack. (default: false)
130 #   stack-push-home     (boolean) If called without argument leads to a
131 #                       jump to the repository's base directory, auto-
132 #                       matically push the current directory onto the
133 #                       stack. (default: true)
134 #   stack-minus         (boolean) Like the `PUSHD_MINUS' option, but
135 #                       for the stack. (default: false)
136 #   stack-no-dups       (boolean) Like the `PUSHD_IGNORE_DUPS' option,
137 #                        but for the stack. (default: false)
138 #   stack-size          (integer) The size of the stack. (default: 10)
139 #
140 # ...and `<cntxt>' is one of:
141 #
142 #   chpwd               Used in the `chpwd' hook. Primarily interesting for
143 #                       the `stack-auto-push-cd' style.
144 #   argless             Used if the comma command is called without arguments.
145 #   plus                Used if the command is called with a +N argument.
146 #   minus               Ditto for a -N argument.
147 #   vcsuptool           Used if the `vcsuptool' utility is used.
148 #   default             Used when none of the above apply.
149 #
150 # If in doubt just use ":vcsup:settings:*" as the context for your styles.
151 #
152 ##########
153 #
154 # If you don't like the comma as the vcsup command, call `vcsupinit()'
155 # like this:
156 #
157 #   % vcsupinit foo
158 #
159 # ...and `foo' will be used instead of the comma.
160 #
161 ##########
162
163 emulate -L zsh
164
165 local cmd
166 cmd=${1:-,}
167
168 typeset -gA vcsupdata
169 typeset -ga vcsupstack
170
171 (( ${+functions[vcs_info_hookadd]} )) || autoload -Uz vcs_info_hookadd
172 vcs_info_hookadd post-backend vcsup-setup
173 vcs_info_hookadd no-vcs vcsup-destroy
174 add-zsh-hook chpwd VCSUP_CHPWD_HOOK
175
176 function +vi-vcsup-setup() {
177     vcsupdata[lastbasedir]=${vcsupdata[currentbasedir]}
178     vcsupdata[currentbasedir]=${hook_com[base]}
179     if [[ -n "${vcsupdata[lastbasedir]}" ]] && \
180        [[ "${vcsupdata[lastbasedir]}" != "${vcsupdata[currentbasedir]}" ]]
181     then
182         # If we're here, we switched repos without triggering the
183         # `no-vcs' hook. Clean up manually.
184         vcsupstack=()
185     fi
186 }
187
188 function +vi-vcsup-destroy() {
189     vcsupdata=()
190     vcsupstack=()
191 }
192
193 function VCSUP_CHPWD_HOOK() {
194     local context=':vcsup:settings:chpwd'
195     [[ x${VCSUPGUARD} == x ]] && return
196     zstyle -t "${context}" stack-auto-push-cd || return
197     VCSUP_push "${PWD}"
198 }
199
200 function VCSUP_del() {
201     local i
202     local -i all
203
204     if [[ "$1" == '-a' ]]; then
205         all=1
206         shift
207     fi
208     (( ${#argv} == 0 )) && usage=1
209     for i in "$@"; do
210         if [[ -n ${(M)vcsupstack:#$i} ]]; then
211             vcsupstack[(re)$i]=()
212         else
213             printf 'Not a stack member: "%s"\n' "$i" >&2
214             continue
215         fi
216         if (( all )); then
217             while [[ -n ${(M)vcsupstack:#$i} ]]; do
218                 vcsupstack[(re)$i]=()
219             done
220         fi
221     done
222
223     return 0
224 }
225
226 function VCSUP_list() {
227     local -i i
228     local fmt
229
230     if (( ${#argv} )); then
231         return 1
232     elif (( ${#vcsupstack} == 0 )); then
233         printf 'vcsupstack empty\n' >&2
234     else
235         zstyle -s $context list-format fmt || fmt=' %3s  %s\n'
236         for (( i = 1; i <= ${#vcsupstack}; i++ )); do
237             printf $fmt $(( i - 1 )) ${vcsupstack[$i]}
238         done
239     fi
240 }
241
242 function VCSUP_pop() {
243     local -i num
244
245     if (( ${#argv} == 1 )); then
246         num=$(( $1 + 1 ))
247     elif (( ${#argv} )); then
248         return 1
249     else
250         num=1
251     fi
252     if (( ${#vcsupstack} > 0 )); then
253         REPLY=${vcsupstack[$num]}
254         vcsupstack[$num]=()
255     else
256         return 2
257     fi
258     return 0
259 }
260
261 function VCSUP_push() {
262     local maxsize
263     local -i nodups
264     if (( ${#argv} == 0 )); then
265         return 1
266     fi
267
268     vcsupstack=( "${(Oa)argv[@]}" "${vcsupstack[@]}" )
269     nodups=0
270     zstyle -t ${context} stack-no-dups && nodups=1
271     if (( nodups )) && [[ ${(t)vcsupstack} != *unique* ]]; then
272         typeset -gU vcsupstack
273     fi
274     if ! (( nodups )) && [[ ${(t)vcsupstack} == *unique* ]]; then
275         typeset -g +U vcsupstack
276     fi
277     zstyle -s ${context} stack-size maxsize
278     if (( $? != 0 )) || [[ ${maxsize} != <-> ]]; then
279         maxsize=10
280     fi
281     if (( ${#vcsupstack} > size )); then
282         vcsupstack=( "${(@)vcsupstack[1,$maxsize]}" )
283     fi
284 }
285
286 function VCSUP_from_() {
287     VCSUP_pop $1
288     ret=$?
289     if (( ret == 2 )); then
290         printf 'vcsupstack empty\n' >&2
291     elif (( ret == 1 )); then
292         printf 'vcsup-BUG: Please report how this happened.\n' >&2
293     else
294         VCSUP_cd "${REPLY}"
295     fi
296     return 0
297 }
298
299 function VCSUP_stacksize() {
300     local -i num
301
302     num=$1
303     if (( num > ( ${#vcsupstack} - 1 ) )); then
304         printf 'No such entry on `vcsupstack'\'' (%d, max: %d).\n' \
305             ${num} $(( ${#vcsupstack} - 1 ))
306         return 1
307     fi
308 }
309
310 function VCSUP_from_bot() {
311     local num
312
313     num=${1:-0}
314     VCSUP_stacksize $num || return 1
315     VCSUP_from_ $(( ${#vcsupstack} - num - 1 ))
316 }
317
318 function VCSUP_from_top() {
319     local -i num
320
321     num=${1:-0}
322     VCSUP_stacksize $num || return 1
323     VCSUP_from_ ${num}
324 }
325
326 function VCSUP_cd() {
327     local -i ret
328     local VCSUPGUARD
329
330     VCSUPGUARD='-guarded-'
331     builtin cd "$1"
332     ret=$?
333     if (( ret == 0 )) && zstyle -t ${context} stack-auto-push; then
334         VCSUP_push ${PWD}
335     fi
336     unset VCSUPGUARD
337 }
338
339 function "${cmd}" () {
340     local cmd REPLY VCSUPGUARD
341     local context=':vcsup:settings'
342     local -i ret
343
344     ret=0
345     cmd=$1
346     case "${cmd}" in
347     ('')
348         context="${context}:argless"
349         if zstyle -t ${context} always-to-root; then
350             if [[ ${PWD} != ${vcsupdata[currentbasedir]} ]]; then
351                 VCSUPGUARD='-guarded-'
352                 builtin cd ${vcsupdata[currentbasedir]}
353                 unset VCSUPGUARD
354             fi
355         elif [[ ${PWD} != ${vcsupdata[currentbasedir]} ]]; then
356             if zstyle -T ${context} stack-push-home; then
357                 VCSUP_push ${PWD}
358             fi
359             VCSUPGUARD='-guarded-'
360             builtin cd ${vcsupdata[currentbasedir]}
361             unset VCSUPGUARD
362         else
363             VCSUP_from_top 0
364         fi
365         ;;
366     (+|(#b)+(<->))
367         context="${context}:plus"
368         if zstyle -t ${context} stack-minus; then
369             VCSUP_from_top "${match[@]}"
370         else
371             VCSUP_from_bot "${match[@]}"
372         fi
373         ;;
374     (-|(#b)-(<->))
375         context="${context}:minus"
376         if zstyle -t ${context} stack-minus; then
377             VCSUP_from_bot "${match[@]}"
378         else
379             VCSUP_from_top "${match[@]}"
380         fi
381         ;;
382     (-h|--help)
383         ;;
384     (--)
385         [[ -z ${vcsupdata[currentbasedir]} ]] && return 1
386         VCSUPGUARD='-guarded-'
387         builtin cd "${vcsupdata[currentbasedir]}"
388         unset VCSUPGUARD
389         ;;
390     (*)
391         context="${context}:default"
392         if (( ${#argv} != 1 )); then
393             printf 'usage: %s [<cmd>[ <ARG(s)...>]|<directory>]\n' "${cmd}" >&2
394             return 1
395         fi
396         VCSUP_cd "$1"
397         ;;
398     esac
399
400     return $ret
401 }
402
403 function vcsuptool() {
404     local cmd context REPLY
405     local -i usage ret
406
407     context=':vcsup:settings:vcstool'
408     usage=0
409     ret=0
410     if (( ${#argv} )); then
411         cmd=$1
412         shift
413     fi
414     case "${cmd}" in
415     (clear)
416         if (( ${#argv} )); then
417             usage=1
418         else
419             vcsupstack=( )
420         fi
421         ;;
422     (del|list|pop|push)
423         VCSUP_"${cmd}" "$@"
424         ret=$?
425         (( ret == 1 )) && usage=1
426         if (( ret == 0 )) && [[ ${cmd} == pop ]]; then
427             printf '%s\n' "${REPLY}"
428         fi
429         ;;
430     (*)
431         usage=1
432         ;;
433     esac
434
435     if (( usage )); then
436         printf 'usage: vcsuptool <cmd> <ARG(s)...>\n'
437         return 1
438     fi
439     return $ret
440 }
441
442 if (( ${+functions[compdef]} )); then
443 # compsys integration #######################
444
445 function _vcsupcmd_fill() {
446     local -i i
447
448     reply_num=( )
449     reply_disp=( )
450     for (( i = 0; i < ${#vcsupstack}; i++ )); do
451         reply_num+=( $i )
452         reply_disp+=( "${i} -- ${vcsupstack[$i + 1]}" )
453     done
454 }
455
456 function _vcsupcmd_fill_reversed() {
457     local -i i j max
458
459     max=${#vcsupstack}
460     reply_num=( )
461     reply_disp=( )
462     for (( i = max - 1; i >= 0; i-- )); do
463         j=$(( max - i - 1 ))
464         reply_num+=( $j )
465         reply_disp+=( "$j -- ${vcsupstack[$i + 1]}" )
466     done
467 }
468
469 function _vcsupcmd_complete() {
470     local context=':vcsup:settings'
471     local -i minus plused
472     local -a reply_num reply_disp
473
474     if (( CURRENT == 2 )) && [[ ${PREFIX} == [-+](|<->) ]]; then
475         # This is a stack lookup.
476         if [[ ${PREFIX} == -* ]]; then
477             context="${context}:minus"
478             compset -P -
479             plused=0
480         else
481             context="${context}:plus"
482             compset -P +
483             plused=1
484         fi
485         zstyle -t ${context} stack-minus && minus=1 || minus=0
486         if { (( minus )) && ! (( plused )) } ||
487            { ! (( minus )) && (( plused )) };
488         then
489             _vcsupcmd_fill_reversed
490         else
491             _vcsupcmd_fill
492         fi
493         _wanted -V indices expl 'stack indices' \
494             compadd "$@" -ld reply_disp -Q -a reply_num
495     elif (( CURRENT == 2 )); then
496         # This is something else, presumably a directory name.
497         _directories
498     else
499         _message 'no more arguments'
500     fi
501 }
502 compdef _vcsupcmd_complete "${cmd}"
503
504 function _vcsuptool-{clear,list}() {
505     _message 'no more arguments'
506 }
507
508 function _vcsuptool-pop() {
509     local expl
510     local -a num display
511     local -i i
512
513     for (( i = 0; i < ${#vcsupstack}; i++ )); do
514         num+=( $i )
515         display+=( "${i} -- ${vcsupstack[$i + 1]}" )
516     done
517     _wanted -V indices expl 'stack indices' compadd "$@" -ld display -Q -a num
518 }
519
520 function _vcsuptool-push() {
521     if [[ -z ${vcsupdata[currentbasedir]} ]]; then
522         _message "Not in a VCS repository"
523         return 1
524     fi
525     cd -q ${vcsupdata[currentbasedir]}
526     _path_files -/ "$@" -P "${PWD}"/ -
527 }
528
529 function _vcsuptool-del() {
530     typeset -aU copy
531     local expl
532
533     copy=( "${vcsupstack[@]}" )
534     _wanted entries expl 'stack entries' compadd ${expl} -- ${copy}
535 }
536
537 function _vcsuptool_complete() {
538     local curcontext="${curcontext}"
539     local subcmd ret
540     local -a desc
541
542     if (( CURRENT == 2 )); then
543         desc=(
544             "clear: clear the entire stack"
545             "del: delete named entris from the stack"
546             "list:show the current contents of the stack"
547             "pop:remove an entry from the stack and return it"
548             "push:push entries onto the stack"
549         )
550         _describe -t subcommands 'vcsuptool commands' desc
551     else
552         subcmd=${words[2]}
553         curcontext="${curcontext%:*:*}:vcsuptool-${subcmd}:"
554         _call_function ret _vcsuptool-${subcmd}
555     fi
556 }
557 compdef _vcsuptool_complete vcsuptool
558
559 # compsys integration end ###################
560 fi
561
562 unfunction vcsupinit