about summary refs log tree commit diff
path: root/Functions/Zle/bracketed-paste-magic
blob: da106d1ac0eeca86386a2c5bac39768da892184d (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
# Starting with zsh-5.0.9, 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").
#  They also run in zsh emulation context set by bracketed-paste-magic.
#  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 -LR zsh	# Already set by bracketed-paste-magic
    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 -LR zsh	# Already set by bracketed-paste-magic
    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() {
    emulate -LR zsh
    local -a bpm_hooks bpm_inactive
    local PASTED bpm_func bpm_active

    # Set PASTED and run the paste-init functions
    zle .bracketed-paste PASTED
    if zstyle -a :bracketed-paste-magic paste-init bpm_hooks; then
	for bpm_func in $bpm_hooks; do
	    if (( $+functions[$bpm_func] )); then
		$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
	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}) zle $REPLY;;
		    (*) zle .self-insert-unmeta;;
		esac
	    fi
	done
	PASTED=$BUFFER
    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
		$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 [[ -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