-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfunctions.zsh
208 lines (193 loc) · 8.41 KB
/
functions.zsh
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
# Usage: run-tracked [ {+|-}wfeoabt ] cmd...
#
# Runs `cmd...` and takes action when it detects changes in the global environment.
#
# Types of environment that run-tracked can monitor:
#
# TYPE DESCRIPTION
# w zle widgets
# f functions
# e exported parameters
# o options
# a aliases
# b bindings
# t traps
#
# For every type of environment the default action on detected changes is to print a warning.
# Options prefixed with `+` cause changes to be accepted without a warning. Options prefixed
# with '-' cause changes to be reverted, also without a warning.
#
# For zle widgets and functions, new entities are not considered changes. For all other
# environments they are. That is, it's absolutely fine for a sourced file to define a new
# function, but it's not fine (unless explicitly allowed) to define an alias or a key binding
# even if they don't clash with existing aliases/bindings.
#
# The primary purpose of `run-tracked` is to provide a modicum of protection against clashes when
# sourcing ZSH plugins. It can help with the following problems:
#
# * Ensure that plugins doesn't override each others' and your own functions, zle widgets,
# key bindings and traps.
# * Make use of certain definitions provided by a plugin (e.g., functions) without accepting
# a bundle of crap with them (say, aliases and key bindings).
# * Avoid environment polution by overeager plugins that slap `extern` on variables for no reason.
# * Prevent plugins from changing shell options without your knowledge.
#
# The solution is to source plugins with `run-tracked source /path/to/plugin.zsh` after
# defining your own widgets, functions, aliases, key bindings, etc. By doing it in this order you
# ensure that *your* stuff (functions, etc.) don't accidentally override plugins' definitions.
# When `run-tracked` prints a warning (for example, saying that the plugin has defined a key
# binding), you need to decide whether to accept the changes (`+b`) or reject them (`-b`).
#
# Note that you won't be able to manually run `source ~/.zshrc` if you are using `run-tracked`.
# Not a big loss since it's a bad practice anyway. Instead, run `exec zsh` to apply configuration
# changes.
#
# Example:
#
# run-tracked source /path/to/plugin.zsh
#
# Output:
#
# [WARNING]: exported vars changed by: source /path/to/plugin.zsh
# 39c39
# < PATH=/usr/bin:/bin
# ---
# < PATH=/usr/bin:/bin:/random/crap
# [WARNING]: bindings changed by: source /path/to/plugin.zsh
# 23a24
# > bindkey "^X" something-awesome
#
# Apart from these warnings, the effect of `run-tracked source /path/to/plugin.zsh` is the same as
# of `source /path/to/plugin.zsh`.
#
# Suppose you want to accept the new key binding but not the override of PATH. Add `+b` and `-e`
# to the invocation of `run-tracked`:
#
# run-tracked +b -e source /path/to/plugin.zsh
#
# Now there are no warnings and PATH stays unchanged.
function run-tracked() {
local -i finished=0
local traps1 traps2
{
local _start=$((EPOCHREALTIME*1000))
local opt
local -A flags
while getopts "wfeoabt" opt; do
case $opt in
'?') return 1;;
+*) flags[${opt:1}]=+;;
*) flags[$opt]=-;;
esac
done
local -a cmd=("${(@)*:$OPTIND}")
local -A widgets1=(${(kv)widgets})
local -A functions1=(${(kv)functions})
local env1 && env1=$(typeset -x) || return
local opts1 && opts1=$(setopt) || return
local aliases1 && aliases1=$(alias -rL; alias -gL; alias -sL) || return
local bindings1 && bindings1=$(bindkey -L) || return
traps1=$(command mktemp "${TMPDIR:-/tmp}"/traps1.XXXXXXXXXX) || return
trap >$traps1 || return
"${(@)cmd}"
local ret=$?
local k v
if [[ $flags[w] == - ]]; then
for k v in ${(kv)widgets1}; do
case $v in
user:*) zle -N $k ${v#*:} || return;;
completion:*) zle -C $k ${${(s.:.)v}[2,3]} || return;;
builtin) [[ $k == .* ]] || zle -A .$k $k || return;;
esac
done
elif [[ $flags[w] != '+' ]]; then
for k v in ${(kv)widgets1}; do
if [[ $v == (user:*|completion:*|builtin) && $v != ${widgets[$k]:-} ]]; then
echo -E "${(%):-%F{red\}}[WARNING]: widget '$k' changed by: ${(@q-)cmd}${(%):-%f}" >&2
command diff <(echo -E $v) <(echo -E ${widgets[$k]:-}) | command awk '{print " " $0}'
fi
done
fi
if [[ $flags[f] == - ]]; then
for k v in ${(kv)functions1}; do
functions[$k]=$v
done
elif [[ $flags[f] != '+' ]]; then
for k v in ${(kv)functions1}; do
if [[ $v != 'builtin autoload -XU' && $v != ${functions[$k]:-} ]]; then
echo -E "${(%):-%F{red\}}[WARNING]: function '$k' changed by: ${(@q-)cmd}${(%):-%f}" >&2
command diff <(echo -E $v) <(echo -E ${functions[$k]:-}) | command awk '{print " " $0}'
fi
done
fi
if [[ $flags[e] == - ]]; then
local env2 && env2=$(typeset +x) || return
typeset +x -- ${(@f)env2} || return
eval "typeset -x -- ${(@fz)env1}" || return
elif [[ $flags[e] != '+' ]]; then
local env2 && env2=$(typeset -x) || return
if [[ $env1 != $env2 ]]; then
echo -E "${(%):-%F{red\}}[WARNING]: exported vars changed by: ${(@q-)cmd}${(%):-%f}" >&2
command diff <(echo -E $env1) <(echo -E $env2) | command awk '{print " " $0}'
fi
fi
if [[ $flags[o] == - ]]; then
emulate zsh || return
setopt -- ${(@f)opts1} || return
elif [[ $flags[o] != '+' ]]; then
local opts2 && opts2=$(setopt) || return
if [[ $opts1 != $opts2 ]]; then
echo -E "${(%):-%F{red\}}[WARNING]: options changed by: ${(@q-)cmd}${(%):-%f}" >&2
command diff <(echo -E $opts1) <(echo -E $opts2) | command awk '{print " " $0}'
fi
fi
if [[ $flags[a] == - ]]; then
unalias -m \* || return
eval "$aliases1" || return
elif [[ $flags[a] != '+' ]]; then
local aliases2 && aliases2=$(alias -rL; alias -gL; alias -sL) || return
if [[ $aliases1 != $aliases2 ]]; then
echo -E "${(%):-%F{red\}}[WARNING]: aliases changed by: ${(@q-)cmd}${(%):-%f}" >&2
command diff <(echo -E $aliases1) <(echo -E $aliases2) | command awk '{print " " $0}'
fi
fi
if [[ $flags[b] == - ]]; then
bindkey -d || return
eval "$bindings1" || return
elif [[ $flags[b] != '+' ]]; then
local bindings2 && bindings2=$(bindkey -L) || return
if [[ $bindings1 != $bindings2 ]]; then
echo -E "${(%):-%F{red\}}[WARNING]: bindings changed by: ${(@q-)cmd}${(%):-%f}" >&2
command diff <(echo -E $bindings1) <(echo -E $bindings2) | command awk '{print " " $0}'
fi
fi
if [[ $flags[t] == - ]]; then
trap - || return
eval $(<$traps1) || return
elif [[ $flags[t] != '+' ]]; then
traps2=$(mktemp "${TMPDIR:-/tmp}"/traps2.XXXXXXXXXX) || return
trap >$traps2 || return
if ! diff -q $traps1 $traps2 &>/dev/null; then
echo -E "${(%):-%F{red\}}[WARNING]: traps changed by: ${(@q-)cmd}${(%):-%f}" >&2
command diff $traps1 $traps2 | command awk '{print " " $0}'
fi
fi
local _startupTime=$((EPOCHREALTIME*1000-_start))
if (( _startupTime > 999 )); then
echo -E "${(%):-%F{red\}}[WARNING]: ${(@q-)cmd} took $((_startupTime))ms to load." >&2
fi
finished=1
return $ret
} always {
if (( ! finished )); then
echo -E "${(%):-%F{red\}}[WARNING]: run-tracked internal error: ${(@q-)*}${(%):-%f}" >&2
fi
[[ -z $traps1 ]] || rm -f $traps1
[[ -z $traps2 ]] || rm -f $traps2
}
}
# The same as double-cliking on file/dir $1 in X File Manager.
function xopen() {
emulate -L zsh
xdg-open "$@" &>/dev/null &!
}