# 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