1 # zsh mouse (and X clipboard) support v1.4
3 # QUICKSTART: jump to "how to use" below.
6 # - VT200 mouse tracking (at least xterm, gnome-terminal, rxvt)
7 # - GPM on Linux little-endian systems such as i386 (at least)
8 # - X clipboard handling if xsel(1) or xclip(1) is available (see
11 # addionnaly, if you are using xterm and don't want to use the mouse
12 # tracking system, you can map some button click events so that they
13 # send \E[M<bt>^X[<y><x> where <bt> is the character 0x20 + (0, 1, 2)
14 # <x>,<y> are the coordinate of the mouse pointer. This is usually done
15 # by adding those lines to your resource file for XTerm (~/.Xdefaults
18 # XTerm.VT100.translations: #override\
19 # Mod4 <Btn1Up>: ignore()\n\
20 # Mod4 <Btn2Up>: ignore()\n\
21 # Mod4 <Btn3Up>: ignore()\n\
22 # Mod4 <Btn1Down>: string(0x1b) string("[M ") dired-button()\n\
23 # Mod4 <Btn2Down>: string(0x1b) string("[M!") dired-button()\n\
24 # Mod4 <Btn3Down>: string(0x1b) string("[M") string(0x22) dired-button()\n\
25 # Mod4 <Btn4Down>,<Btn4Up>: string(0x10)\n\
26 # Mod4 <Btn5Down>,<Btn5Up>: string(0xe)
28 # That maps the button click events with the modifier 4 (when you hold
29 # the <Super> Key [possibly Windows keys] under recent versions of
30 # XFree86). The last two lines are for an easy support of the mouse
31 # wheel (map the mouse wheel events to ^N and ^P)
33 # Remember that even if you use the mouse tracking, you can still have
34 # access to the normal xterm selection mechanism by holding the <Shift>
37 # Note about X selection.
38 # By default, xterm uses the PRIMARY selection instead of CLIPBOARD
39 # for copy-paste. You may prefer changing that if you want
40 # <Shift-Insert> to insert the CLIPBOARD and a better communication
41 # between xterm and clipboard based applications like mozilla.
42 # A way to do that is to add those resources:
43 # XTerm.VT100.translations: #override\
44 # Shift ~Ctrl <KeyPress> Insert:insert-selection(\
45 # CLIPBOARD, CUT_BUFFER0, PRIMARY) \n\
46 # Shift Ctrl <KeyPress> Insert:insert-selection(\
47 # PRIMARY, CUT_BUFFER0, CLIPBOARD) \n\
48 # ~Ctrl ~Meta<BtnUp>: select-end(PRIMARY,CUT_BUFFER0,CLIPBOARD)
50 # and to run a clipboard manager application such as xclipboard
51 # (whose invocation you may want to put in your X session startup
52 # file). (<Shift-Ctrl-Insert> inserts the PRIMARY selection as does
53 # the middle mouse button). (without xclipboard, the clipboard
54 # content is lost whenever the text is no more selected).
58 # add to your ~/.zshrc:
59 # . /path/to/this-file
62 # and if you want to be able to toggle on/off the mouse support:
63 # bindkey -M emacs '\em' zle-toggle-mouse
64 # # <Esc>m to toggle the mouse in emacs mode
65 # bindkey -M vicmd M zle-toggle-mouse
66 # # M for vi (cmd) mode
68 # clicking on the button 1:
69 # moves the cursor to the pointed location
70 # clicking on the button 2:
71 # inserts zsh cutbuffer at pointed location. If $DISPLAY is set and
72 # either the xsel(1) or xclip(1) command is available, then it's the
73 # content of the X clipboard instead that is pasted (and stored into
75 # clicking on the button 3:
76 # stores the text between the cursor and the pointed localion
77 # into zsh cutbuffer. Additionaly, if $DISPLAY is set and either the
78 # xclip(1) or xsel(1) command is available, that text is put on the
81 # If xsel or xlip is available, and $DISPLAY is set (and you're in a
82 # xterm-like terminal (even though that feature is terminal
83 # independant)), all the keys (actually widgets) that deal with zsh
84 # cut buffer have been modified so that the X CLIPBOARD selection is
85 # used. So <Ctrl-U>, <Ctrl-W>... will put the killed region on the X
86 # clipboard. vi mode "p" or emacs "<Ctrl-Y>" will paste the X CLIPBOARD
87 # selection. Only the keys that delete one character are not affected
88 # (<Del>, <Backspace>, <x>). Additionnaly, the primary selection (what
89 # is mouse highlighted and that you paste with the middle button) is put
90 # on the clipboard (and so made available to zsh) when you press
91 # <Meta-Insert> or <Ctrl-Insert> or <Ctrl-X>X (emacs mode) or X (vicmd
92 # mode). (note that your terminal may already do that by default, also
93 # note that your terminal may paste the primary selection and not the
94 # clipboard on <Shift-Insert>, you may change that if you find it
95 # confusing (see above))
97 # for GPM, you may change the list of modifiers (Shift, Alt...) that
98 # need to be on for the event to be accepted (see below).
100 # kterm: same as for xterm, but replace XTerm with KTerm in the resource
102 # hanterm: same as for xterm, but replace XTerm with Hanterm in the
103 # resource customization.
104 # Eterm: the paste(clipboard) actions don't seem to work, future
105 # versions of mouse.zsh may include support for X cutbuffers or revert
106 # back to PRIMARY selection to provide a better support for Eterm.
107 # gnome-terminal: you may want to bind some keys to Edit->{copy,paste}
108 # multi-gnome-terminal: selection looks mostly bogus to me
109 # rxvt,aterm,[ckgt]aterm,mlterm,pterm: no support for clipboard.
110 # GNUstep terminal: no mouse support but support for clipboard via menu
111 # KDE x-terminal-emulator: works OK except mouse button3 that is mapped
112 # to the context menu. Use Ctrl-Insert to put the selection on the
114 # dtterm: no mouse support but the selection works OK.
117 # - the GPM support was not much tested (was tested with gpm 1.19.6 on
118 # a linux 2.6.9, AMD Athlon)
119 # - mouse positionning doesn't work properly in "vared" if a prompt
120 # was provided (vared -p <prompt>)
123 # - write proper documentation
124 # - customization through zstyles.
127 # Stephane Chazelas <Stephane_Chazelas@yahoo.fr>
130 # v1.4 2005-03-01: <Ctrl-W><Ctrl-W> puts both words on the cut buffer
131 # support for CUT_BUFFER0 via xprop.
132 # v1.3 2005-02-28: support for more X terminals, tidy-up, separate
133 # mouse support from clipboard support
134 # v1.2 2005-02-24: support for vi-mode. X clipboard mirroring zsh cut buffer
135 # when possible. Bug fixes.
136 # v1.1 2005-02-20: support for X selection through xsel or xclip
137 # v1.0 2004-11-18: initial release
143 if [[ -n $WIDGET ]]; then
144 # error message if zle active
147 # on stderr otherwise
152 # SELECTION/CLIPBOARD FUNCTIONS
154 set-x-clipboard() { return 0; }
155 get-x-clipboard() { return 1; }
158 # find a command to read from/write to the X selections
159 if whence xsel > /dev/null 2>&1; then
160 x_selection_tool="xsel -p"
161 x_clipboard_tool="xsel -b"
162 elif whence xclip > /dev/null 2>&1; then
163 x_selection_tool="xclip -sel p"
164 x_clipboard_tool="xclip -sel c"
169 (( $+DISPLAY )) || return 1
171 r=$('$x_clipboard_tool' -o < /dev/null 2> /dev/null && print .)
173 if [[ -n $r && $r != $CUTBUFFER ]]; then
174 killring=("$CUTBUFFER" "${(@)killring[1,-2]}")
180 print -rn -- "$1" | '$x_clipboard_tool' -i 2> /dev/null
182 push-x-cut_buffer0() {
183 # retrieve the CUT_BUFFER0 property via xprop and store it on the
184 # CLIPBOARD selection
185 (( $+DISPLAY )) || return 1
187 r=$(xprop -root -notype 8s \$0 CUT_BUFFER0 2> /dev/null) || return 1
190 r=${r//\'\''/\\\'\''}
191 eval print -rn -- \$\'\''$r\'\'' | '$x_clipboard_tool' -i 2> /dev/null
194 # puts the PRIMARY selection onto the CLIPBOARD
195 # failing that call push-x-cut_buffer0
196 (( $+DISPLAY )) || return 1
198 if r=$('$x_selection_tool' -o < /dev/null 2> /dev/null && print .) &&
201 print -rn -- $r | '$x_clipboard_tool' -i 2> /dev/null
207 # redefine the copying widgets so that they update the clipboard.
208 for w in copy-region-as-kill vi-delete vi-yank vi-change vi-change-whole-line vi-change-eol; do
212 set-x-clipboard $CUTBUFFER
217 # that's a bit more complicated for those ones as we have to
218 # re-implement the special behavior that does that if you call several
219 # of those widgets in sequence, the text on the clipboard is the
220 # whole text cut, not just the text cut by the latest widget.
221 for w in ${widgets[(I).*kill-*]}; do
222 if [[ $w = *backward* ]]; then
230 local slw=$LASTWIDGET
234 (( $sbl == $#BUFFER )) && return
235 if [[ $slw = (.|)(backward-|)kill-* ]]; then
236 killring=("${(@)killring[2,-1]}")
239 set-x-clipboard $CUTBUFFER
244 zle -N push-x-selection
245 zle -N push-x-cut_buffer0
247 # put the current selection on the clipboard upon <Ctrl-Insert>
248 # <Meta-Insert> <Ctrl-X>X or X:
249 if (( $+terminfo[kSI] )); then
250 bindkey -M emacs "$terminfo[kSI]" push-x-selection
251 bindkey -M viins "$terminfo[kSI]" push-x-selection
252 bindkey -M vicmd "$terminfo[kSI]" push-x-selection
254 if (( $+terminfo[kich1] )); then
255 # <Meta-Insert> according to terminfo
256 bindkey -M emacs "\e$terminfo[kich1]" push-x-selection
257 bindkey -M viins "\e$terminfo[kich1]" push-x-selection
258 bindkey -M vicmd "\e$terminfo[kich1]" push-x-selection
260 # hardcode ^[[2;3~ which is sent by <Meta-Insert> on xterm
261 bindkey -M emacs '\e[2;3~' push-x-selection
262 bindkey -M viins '\e[2;3~' push-x-selection
263 bindkey -M vicmd '\e[2;3~' push-x-selection
264 # hardcode ^[^[[2;5~ which is sent by <Meta-Insert> on some terminals
265 bindkey -M emacs '\e\e[2~' push-x-selection
266 bindkey -M viins '\e\e[2~' push-x-selection
267 bindkey -M vicmd '\e\e[2~' push-x-selection
269 # hardcode ^[[2;5~ which is sent by <Ctrl-Insert> on xterm
270 # some terminals have already such a feature builtin (gnome/KDE
271 # terminals), others have no distinguishable character sequence sent
273 bindkey -M emacs '\e[2;5~' push-x-selection
274 bindkey -M viins '\e[2;5~' push-x-selection
275 bindkey -M vicmd '\e[2;5~' push-x-selection
277 # for terminal without an insert key:
278 bindkey -M vicmd X push-x-selection
279 bindkey -M emacs '^XX' push-x-selection
281 # the convoluted stuff below is to work around two problems:
282 # 1- we can't just redefine the widgets as then yank-pop would
284 # 2- we can't just rebind the keys to <Ctrl-Insert><other-key> as
285 # then we'll loose the numeric argument
286 propagate-numeric() {
287 # next key (\e[0-dum) is mapped to <Ctrl-Insert>, plus the
288 # targeted widget with NUMERIC restored.
291 bindkey -M vicmd -s '\e[0-dum' $'\e[1-dum'$NUMERIC${KEYS/x/};;
293 bindkey -M $KEYMAP -s '\e[0-dum' $'\e[1-dum'${NUMERIC//(#m)?/$'\e'$MATCH}${KEYS/x/};;
296 zle -N get-x-clipboard
297 zle -N propagate-numeric
298 bindkey -M emacs '\e[1-dum' get-x-clipboard
299 bindkey -M vicmd '\e[1-dum' get-x-clipboard
300 bindkey -M emacs '\e[2-dum' yank
301 bindkey -M emacs '\e[2-xdum' propagate-numeric
302 bindkey -M emacs -s '^Y' $'\e[2-xdum\e[0-dum'
303 bindkey -M vicmd '\e[3-dum' vi-put-before
304 bindkey -M vicmd '\e[3-xdum' propagate-numeric
305 bindkey -M vicmd -s 'P' $'\e[3-xdum\e[0-dum'
306 bindkey -M vicmd '\e[4-dum' vi-put-after
307 bindkey -M vicmd '\e[4-xdum' propagate-numeric
308 bindkey -M vicmd -s 'p' $'\e[4-xdum\e[0-dum'
314 zle-update-mouse-driver() {
315 # default is no mouse support
316 [[ -n $ZLE_USE_MOUSE ]] && zle-error 'Sorry: mouse not supported'
321 if [[ $TERM = *[xeEk]term* ||
325 ($TERM = *linux* && -S /dev/gpmctl)
328 set-status() { return $1; }
330 handle-mouse-event() {
336 return 0;; # Process on press, discard release
337 # mlterm sends 3 on mouse-wheel-up but also on every button
338 # release, so it's unusable
340 # eterm, rxvt, gnome/KDE terminal mouse wheel
341 zle up-line-or-history
344 # mlterm or eterm, rxvt, gnome/KDE terminal mouse wheel
345 zle down-line-or-history
348 local mx=$2 my=$3 last_status=$4
352 print -n '\e[6n' # query cursor position
354 local match mbegin mend buf=
356 while read -k i && buf+=$i && [[ $buf != *\[([0-9]##)\;[0-9]##R ]]; do :; done
357 # read response from terminal.
358 # note that we may also get a mouse tracking btn-release event,
359 # which would then be discarded.
361 [[ $buf = (#b)*\[([0-9]##)\;[0-9]##R ]] || return
362 cy=$match[1] # we don't need cx
366 # trying to guess the current prompt
369 if [[ $0 = zcalc ]]; then
370 cur_prompt=${ZCALCPROMPT-'%1v> '}
371 setopt nopromptsubst nopromptbang promptpercent
372 # (ZCALCPROMPT is expanded with (%))
374 # if vared is passed a prompt, we're lost
383 # if promptsubst, then we need first to do the expansions (to
384 # be able to remove the visual effects) and disable further
386 [[ -o promptsubst ]] && cur_prompt=${${(e)cur_prompt}//(#b)([\\\$\`])/\\$match}
388 # restore the exit status in case $PS<n> relies on it
389 set-status $last_status
391 # remove the visual effects and do the prompt expansion
392 cur_prompt=${(S%%)cur_prompt//(#b)(%([BSUbsu]|{*%})|(%[^BSUbsu{}]))/$match[3]}
394 # we're now looping over the whole editing buffer (plus the last
395 # line of the prompt) to compute the (x,y) position of each char. We
396 # store the characters i for which x(i) <= mx < x(i+1) for every
397 # value of y in the pos array. We also get the Y(CURSOR), so that at
398 # the end, we're able to say which pos element is the right one
400 local -a pos # array holding the possible positions of
402 local -i n x=0 y=1 cursor=$((${#cur_prompt}+$CURSOR+1))
405 buf=$cur_prompt$BUFFER
406 for ((i=1; i<=$#buf; i++)); do
407 (( i == cursor )) && Y=$y
413 ($'\t') # tab advance til next tab stop
415 ([$'\0'-$'\037'$'\200'-$'\237'])
422 (( x >= mx )) && : ${pos[y]=$i}
423 (( x >= COLUMNS )) && (( x=0, y++ ))
429 : ${pos[y]=$i} ${Y:=$y}
432 if ((my + Y - cy > y)); then
433 mouse_CURSOR=$#BUFFER
434 elif ((my + Y - cy < 1)); then
437 mouse_CURSOR=$(($pos[my + Y - cy] - ${#cur_prompt} - 1))
442 # Button 1. Move cursor.
447 # Button 2. Insert selection at mouse cursor postion.
449 BUFFER=$BUFFER[1,mouse_CURSOR]$CUTBUFFER$BUFFER[mouse_CURSOR+1,-1]
450 (( CURSOR = $mouse_CURSOR + $#CUTBUFFER ))
454 # Button 3. Copy from cursor to mouse to cutbuffer.
455 killring=("$CUTBUFFER" "${(@)killring[1,-2]}")
456 if (( mouse_CURSOR < CURSOR )); then
457 CUTBUFFER=$BUFFER[mouse_CURSOR+1,CURSOR+1]
459 CUTBUFFER=$BUFFER[CURSOR+1,mouse_CURSOR+1]
461 set-x-clipboard $CUTBUFFER
466 handle-xterm-mouse-event() {
471 # either xterm mouse tracking or binded xterm event
472 # read the event from the terminal
473 read -k bt # mouse button, x, y reported after \e[M
477 if [[ $mx = $'\030' ]]; then
478 # assume event is \E[M<btn>dired-button()(^X\EG<x><y>)
485 # that's a VT200 mouse tracking event
489 handle-mouse-event $bt $mx $my $last_status
492 zle -N handle-xterm-mouse-event
494 if [[ $TERM = *linux* && -S /dev/gpmctl ]]; then
496 if zmodload -i zsh/net/socket; then
498 zle-update-mouse-driver() {
499 if [[ -n $ZLE_USE_MOUSE ]]; then
500 if (( ! $+ZSH_GPM_FD )); then
501 if zsocket -d 9 /dev/gpmctl; then
503 # gpm initialisation:
504 # request single click events with given modifiers
519 # modifiers that need to be on
520 min=$((modifiers[none]))
521 # modifiers that may be on
525 # 1-2: LE short: requested events (btn down = 0x0004)
526 # 3-4: LE short: event passed through (~GPM_HARD=0xFEFF)
527 # 5-6: LE short: min modifiers
528 # 7-8: LE short: max modifiers
530 # 13-16: LE int: virtual console number
532 print -u$ZSH_GPM_FD -n "\4\0\377\376\\$(([##8]min&255
533 ))\\$(([##8]min>>8))\\$(([##8]max&255))\\$(([##8]max>>8
534 ))\\$(([##8]$$&255))\\$(([##8]$$>>8&255))\\$((
535 [##8]$$>>16&255))\\$(( [##8]$$>>24))\\$((
536 [##8]${TTY#/dev/tty}))\0\0\0"
537 zle -F $ZSH_GPM_FD handle-gpm-mouse-event
539 zle-error 'Error: unable to connect to GPM'
544 # ZLE_USE_MOUSE disabled, close GPM connection
545 if (( $+ZSH_GPM_FD )); then
546 eval "exec $ZSH_GPM_FD>&-"
547 # what if $ZSH_GPM_FD > 9 ?
548 zle -F $ZSH_GPM_FD # remove the handler
554 handle-gpm-mouse-event() {
557 if read -u$1 -k28 event; then
559 (( buttons = ##$event[1] ))
560 (( x = ##$event[9] + ##$event[10] << 8 ))
561 (( y = ##$event[11] + ##$event[12] << 8 ))
562 handle-mouse-event $(( (5 - (buttons & -buttons)) / 2 )) $x $y $last_status
563 zle -R # redraw buffer
565 zle -M 'Error: connection to GPM lost'
567 zle-update-mouse-driver
572 # xterm-like mouse support
573 zmodload -i zsh/parameter # needed for $functions
575 zle-update-mouse-driver() {
576 if [[ -n $WIDGET ]]; then
577 if [[ -n $ZLE_USE_MOUSE ]]; then
585 if [[ $functions[precmd] != *ZLE_USE_MOUSE* ]]; then
587 [[ -n $ZLE_USE_MOUSE ]] && print -n '\''\e[?1000h'\'
589 if [[ $functions[preexec] != *ZLE_USE_MOUSE* ]]; then
590 functions[preexec]+='
591 [[ -n $ZLE_USE_MOUSE ]] && print -n '\''\e[?1000l'\'
594 bindkey -M emacs '\e[M' handle-xterm-mouse-event
595 bindkey -M viins '\e[M' handle-xterm-mouse-event
596 bindkey -M vicmd '\e[M' handle-xterm-mouse-event
598 if [[ $TERM = *Eterm* ]]; then
599 # Eterm sends \e[5Mxxxxx on drag events, be want to discard them
600 discard-mouse-drag() {
604 zle -N discard-mouse-drag
605 bindkey -M emacs '\e[5M' discard-mouse-drag
606 bindkey -M viins '\e[5M' discard-mouse-drag
607 bindkey -M vicmd '\e[5M' discard-mouse-drag
614 # If no prefix, toggle state.
615 # If positive prefix, turn on.
616 # If zero or negative prefix, turn off.
618 # Allow this to be used as a normal function, too.
622 if (( $+PREFIX )); then
623 if (( PREFIX > 0 )); then
629 if [[ -n $ZLE_USE_MOUSE ]]; then
635 zle-update-mouse-driver
637 zle -N zle-toggle-mouse