git-escape-magic
4333 bytes
1# -*- mode: sh -*-
2#
3# git-escape-magic - zle tweak for git command line arguments
4#
5# Copyright (c) 2011, 2012, 2014 Akinori MUSHA
6# Licensed under the 2-clause BSD license.
7#
8# This tweak eliminates the need for manually escaping shell
9# meta-characters such as [~^{}] that are used for specifying a git
10# object (commit or tree). Every time you type one of these
11# characters on a git command line, it is automatically escaped with a
12# backslash as necessary and as appropriate.
13#
14# If you want to use this with url-quote-magic, make sure to enable it
15# first.
16#
17# Usage:
18# autoload -Uz git-escape-magic
19# git-escape-magic
20#
21
22git-escape-magic.self-insert() {
23 emulate -L zsh
24 setopt extendedglob
25 local self_insert_function
26 zstyle -s ':git-escape-magic' self-insert-function self_insert_function
27
28 if [[ "$KEYS" == [{}~^]* ]] && {
29 local qkey="${(q)KEYS}"
30 [[ "$KEYS" != "$qkey" ]]
31 } && {
32 local lbuf="$LBUFFER$qkey"
33 [[ "${(Q)LBUFFER}$KEYS" == "${(Q)lbuf}" ]]
34 } && {
35 local -a words
36 words=("${(@Q)${(z)lbuf}}")
37 [[ "$words[(i)(*/|)git(|-[^/]##)]" -le $#words ]]
38 }
39 then
40 local i
41 i="$words[(I)([;(){\&]|\&[\&\!]|\|\||[=<>]\(*)]"
42 if [[ $i -gt 0 ]]; then
43 shift $((i-1)) words
44 if [[ "$words[1]" == [\=\<\>]\(* ]]; then
45 words[1]="${words[1]#[=<>]\(}"
46 else
47 [[ "$words[1]" == \; && $words[2] == (then|else|elif|do) ]] && shift words
48 shift words
49 fi
50 fi
51 while [[ "$words[1]" == (if|while|until|\!) ]]; do
52 shift words
53 done
54 while [[ "$words[1]" == [A-Za-z_][A-Za-z0-9_]#=* ]]; do
55 shift words
56 done
57 [[ "$words[1]" == (*/|)git(|-[^/]##) ]] && {
58 local subcommand
59 subcommand="${words[1]##*/git-}"
60 if [[ -z "$subcommand" ]]; then
61 shift words
62 subcommand="$words[1]"
63 fi
64 [[ $#words -ge 2 ]]
65 } &&
66 case "$subcommand" in
67 # commands that may take pathspec but never take refspec with [{}~^]
68 (add|rm|am|apply|check-attr|checkout-index|clean|clone|config|diff-files|hash-object|help|index-pack|mailinfo|mailsplit|merge-file|merge-index|mergetool|mktag|mv|pack-objects|pack-redundant|relink|send-email|show-index|show-ref|stage|status|verify-pack)
69 false ;;
70 # commands that may take pathspec but rarely take refspec with [{}~^]
71 (for-each-ref|grep|ls-files|update-index)
72 false ;;
73 (archive|ls-tree)
74 ! [[ $#words -ge 3 &&
75 "$words[-2]" == [^-]* ]] ;;
76 (diff-tree)
77 ! [[ $#words -ge 4 &&
78 "$words[-2]" == [^-]* &&
79 "$words[-3]" == [^-]* ]] ;;
80 (*)
81 [[ $words[(i)--] -gt $#words ]] ;;
82 esac &&
83 case "${words[-1]%%"$KEYS"}" in
84 (*[@^])
85 [[ "$KEYS" == [{~^]* ]] ;;
86 (*[@^]\{[^}]##)
87 [[ "$KEYS" == \}* ]] ;;
88 (?*)
89 [[ "$KEYS" == [~^]* ]] ;;
90 (*)
91 false ;;
92 esac &&
93 LBUFFER="$LBUFFER\\"
94 fi
95
96 zle "$self_insert_function"
97}
98
99git-escape-magic.on() {
100 emulate -L zsh
101 local self_insert_function="${$(zle -lL | awk \
102 '$1=="zle"&&$2=="-N"&&$3=="self-insert"{print $4;exit}'):-.self-insert}"
103
104 [[ "$self_insert_function" == git-escape-magic.self-insert ]] &&
105 return 0
106
107 # For url-quote-magic which does not zle -N itself
108 zle -la "$self_insert_function" || zle -N "$self_insert_function"
109
110 zstyle ':git-escape-magic' self-insert-function "$self_insert_function"
111
112 zle -A git-escape-magic.self-insert self-insert
113 return 0
114}
115
116git-escape-magic.off() {
117 emulate -L zsh
118 local self_insert_function
119 zstyle -s ':git-escape-magic' self-insert-function self_insert_function
120
121 [[ -n "$self_insert_function" ]] &&
122 zle -A "$self_insert_function" self-insert
123 return 0
124}
125
126zle -N git-escape-magic.self-insert
127zle -N git-escape-magic.on
128zle -N git-escape-magic.off
129
130git-escape-magic() {
131 git-escape-magic.on
132}
133
134[[ -o kshautoload ]] || git-escape-magic "$@"
135