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
|
# Starting with zsh-5.1, ZLE began to recognize the "bracketed paste"
# capability of terminal emulators, that is, the sequences $'\e[200~' to
# start a paste and $'\e[201~' to indicate the end of the pasted text.
# Pastes are handled by the bracketed-paste widget and insert literally
# into the editor buffer rather than being interpreted as keystrokes.
#
# This disables some common usages where the self-insert widget has been
# replaced in order to accomplish some extra processing. An example is
# the contributed url-quote-magic widget. The bracketed-paste-magic
# widget replaces bracketed-paste with a wrapper that re-enables these
# self-insert actions, and other actions as selected by the zstyles
# described below.
#
# Setup:
# autoload -Uz bracketed-paste-magic
# zle -N bracketed-paste bracketed-paste-magic
# The following zstyles may be set to control processing of pasted text.
#
# active-widgets
# Looked up in the context :bracketed-paste-magic to obtain a list of
# patterns that match widget names that should be activated during the
# paste. All other key sequences are processed as self-insert-unmeta.
# The default is 'self-*' so any user-defined widgets named with that
# prefix are active along with the builtin self-insert. If this style is
# not set (note: it must be explicitly deleted after loading this
# function, otherwise it becomes set by default) or has no value, no
# widgets are active and the pasted text is inserted literally. If the
# value includes undefined-key, any unknown sequences are discarded from
# the pasted text.
#
# inactive-keys
# This is the inverse of active-widgets, it lists key sequences that
# always use self-insert-unmeta even when bound to an active-widget.
# Note that this is a list of literal key sequences, not patterns.
# This style is in context :bracketed-paste-magic and has no default.
#
# paste-init
# paste-finish
# Also looked up in the context :bracketed-paste-magic, these styles
# each are a list of function names. They are executed in widget
# context but are called as functions (NOT as widgets with "zle name").
# As with hooks, the functions are called in order until one of them
# returns a nonzero exit status. The parameter PASTED contains the
# current state of the pasted text, other ZLE parameters are as usual.
# Although a nonzero status stops each list of functions, it does NOT
# abort the entire paste operation; use "zle send-break" for that.
# IMPORTANT: During processing of the paste (after paste-init and
# before paste-finish), BUFFER starts empty and history is restricted,
# so cursor motions etc. may not pass outside of the pasted content.
# However, the paste-init functions have access to the full history and
# the original BUFFER, so they may for example move words from BUFFER
# into PASTED to make those words visible to the active-widgets.
# Establish default values for styles, but only if not already set
zstyle -m :bracketed-paste-magic active-widgets '*' ||
zstyle ':bracketed-paste-magic' active-widgets 'self-*'
# Helper/example paste-init for exposing a word prefix inside PASTED.
# Useful with url-quote-magic if you have http://... on the line and
# are pasting additional text on the end of the URL.
#
# Usage:
# zstyle :bracketed-paste-magic paste-init backward-extend-paste
#
# TODO: rewrite this using match-words-by-style
#
backward-extend-paste() {
emulate -L zsh
integer bep_mark=$MARK bep_region=$REGION_ACTIVE
if (( REGION_ACTIVE && MARK < CURSOR )); then
zle .exchange-point-and-mark
fi
if (( CURSOR )); then
local -a bep_words=( ${(z)LBUFFER} )
if [[ -n $bep_words[-1] && $LBUFFER = *$bep_words[-1] ]]; then
PASTED=$bep_words[-1]$PASTED
LBUFFER=${LBUFFER%${bep_words[-1]}}
fi
fi
if (( MARK > bep_mark )); then
zle .exchange-point-and-mark
fi
REGION_ACTIVE=$bep_region
}
# Example paste-finish for quoting the pasted text.
#
# Usage e.g.:
# zstyle :bracketed-paste-magic paste-finish quote-paste
# zstyle :bracketed-paste-magic:finish quote-style qqq
#
# Using "zstyle -e" to examine $PASTED lets you choose different quotes
# depending on context.
#
# To forcibly turn off numeric prefix quoting, use e.g.:
# zstyle :bracketed-paste-magic:finish quote-style none
#
quote-paste() {
emulate -L zsh
local qstyle
# If there's a quoting style, be sure .bracketed-paste leaves it alone
zstyle -s :bracketed-paste-magic:finish quote-style qstyle && NUMERIC=1
case $qstyle in
(b) PASTED=${(b)PASTED};;
(q-) PASTED=${(q-)PASTED};;
(\\|q) PASTED=${(q)PASTED};;
(\'|qq) PASTED=${(qq)PASTED};;
(\"|qqq) PASTED=${(qqq)PASTED};;
(\$|qqqq) PASTED=${(qqqq)PASTED};;
(Q) PASTED=${(Q)PASTED};;
esac
}
# Now the actual function
bracketed-paste-magic() {
if [[ "$LASTWIDGET" = *vi-set-buffer ]]; then
# Fast exit in the vi-mode cut-buffer context
zle .bracketed-paste
return
else
# Capture the pasted text in $PASTED
local PASTED
zle .bracketed-paste PASTED
fi
# Really necessary to go to this much effort?
local bpm_emulate="$(emulate)" bpm_opts="$-"
emulate -L zsh
local -a bpm_hooks bpm_inactive
local bpm_func bpm_active bpm_keymap=$KEYMAP
# Run the paste-init functions
if zstyle -a :bracketed-paste-magic paste-init bpm_hooks; then
for bpm_func in $bpm_hooks; do
if (( $+functions[$bpm_func] )); then
function () {
emulate -L $bpm_emulate; set -$bpm_opts
$bpm_func || break
}
fi
done
fi
# Save context, create a clean slate for the paste
integer bpm_mark=$MARK bpm_cursor=$CURSOR bpm_region=$REGION_ACTIVE
integer bpm_numeric=${NUMERIC:-1}
local bpm_buffer=$BUFFER
fc -p -a /dev/null 0 0
BUFFER=
zstyle -a :bracketed-paste-magic inactive-keys bpm_inactive
if zstyle -s :bracketed-paste-magic active-widgets bpm_active '|'; then
# There are active widgets. Reprocess $PASTED as keystrokes.
NUMERIC=1
zle -U - $PASTED
if [[ $bmp_keymap = vicmd ]]; then
zle -K viins
fi
# Just in case there are active undo widgets
zle .split-undo
integer bpm_limit=$UNDO_LIMIT_NO bpm_undo=$UNDO_CHANGE_NO
UNDO_LIMIT_NO=$UNDO_CHANGE_NO
while [[ -n $PASTED ]] && zle .read-command; do
PASTED=${PASTED#$KEYS}
if [[ $KEYS = ${(~j:|:)${(b)bpm_inactive}} ]]; then
zle .self-insert-unmeta
else
case $REPLY in
(${~bpm_active}) function () {
emulate -L $bpm_emulate; set -$bpm_opts
zle $REPLY
};;
(*) zle .self-insert-unmeta;;
esac
fi
done
PASTED=$BUFFER
# Reset the undo state
zle undo $bpm_undo
UNDO_LIMIT_NO=$bpm_limit
zle -K $bpm_keymap
fi
# Restore state
BUFFER=$bpm_buffer
MARK=$bpm_mark
CURSOR=$bpm_cursor
REGION_ACTIVE=$bpm_region
NUMERIC=$bpm_numeric
fc -P
# PASTED has been updated, run the paste-finish functions
if zstyle -a :bracketed-paste-magic paste-finish bpm_hooks; then
for bpm_func in $bpm_hooks; do
if (( $+functions[$bpm_func] )); then
function () {
emulate -L $bpm_emulate; set -$bpm_opts
$bpm_func || break
}
fi
done
fi
# Reprocess $PASTED as an actual paste this time
zle -U - $PASTED$'\e[201~' # append paste-end marker
zle .bracketed-paste
zle .split-undo
# Arrange to display highlighting if necessary
if [[ -z $zle_highlight || -n ${(M)zle_highlight:#paste:*} ]]; then
zle -R
zle .read-command && zle -U - $KEYS
fi
}
# Handle zsh autoloading conventions
if [[ "$zsh_eval_context" = *loadautofunc && ! -o kshautoload ]]; then
bracketed-paste-magic "$@"
fi
|