Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: [tip] mouse support



On Thu, Nov 11, 2004 at 02:40:32PM +0000, Peter Stephenson wrote:
> Stephane Chazelas wrote:
> > I just posted that to comp.unix.shell, I thought it might be of
> > some interest for some of you. Basically, it's cursor
> > positionning with the mouse under xterm like terminals (works
> > with xterm, gnome-terminal and rxvt AFAICS, probably also with
> > putty, not if there are tabs or NLs (or multi-byte characters)
> > in the zle buffer).
> 
> This is very useful.  (Other people should note it's only useful with
> the current zsh editing buffer; there's no way of accessing other
> information on the screen.)

In that new one, I tried to address the tab/newline issue.
It tries to calculate the length of the last line of the prompt,
it may fail if PS1 contains %%s or %{ %{ ... %} %} or if two
consecutive expansions of PS1 may not generate the same thing,
it would be nice to have access at what zsh thinks is the
cursor horizontal position at begining of buffer.

> I've extended it.  Other people may have suggestions, too.  This
> probably requires zsh 4.2 to use the cutbuffer and killring.
> 
> Button 1 is as before.
> 
> Button 2 pastes the zsh cutbuffer at the mouse position.  This
> is identical to yank.  It also emulates normal xterm behaviour
> by pasting at the cursor, not the mouse position.  This seemed
> less surprising but is easy to change or make optional.

I changed for "pasting at the mouse cursor".

> Button 3 copies the region from the cursor to the mouse position
> into the zsh cutbuffer.   This is like copy-region-as-kill but
> doesn't use the zsh mark. (I could set that too, I suppose.)
> Note I'm not 100% confident the limits (i.e. indices into the editing
> buffer) used are the best ones.

Not sure of mines either ;).

> Other additions
> - It takes care to change the existing precmd and preexec rather
>   than overwriting them.  This won't work if there is a "return"
>   earlier in either.  Possibly the new lines should go at the top.

Thanks for that. I didn't know about the $functions hash.

> - There's a function/widget zle-toggle-mouse to switch between normal
>   xterm mouse and zle mouse.  Positive prefix forces zle mouse,
>   negative or zero prefix forces normal xterm mouse.

You can use hold the <Shift> key to have the normal xterm
behavior.

Here's the code. Be careful when you bullet proof it, it may be
full of bugs...

### code begin
set-status() { return $1; }

zle-xterm-mouse() {
  local last_status=$?
  emulate -L zsh
  setopt extendedglob # for (#b)
  local bt mx my cy i buf

  read -k bt # mouse button, x, y reported after \e[M
  read -k mx
  read -k my
  if [[ $bt != "#" ]]; then
    # Process on release, but record the button on press.
    ZLE_MOUSE_BUTTON=$bt
    return 0
  fi

  (( my = #my - 32 ))
  (( mx = #mx - 32 ))

  print -n '\e[6n' # query cursor position
  while read -k i && [[ $i != R ]]; do buf+=$i; done

  local match mbegin mend
  [[ $buf = (#b)??(*)\;* ]] || return
  cy=$match[1]
  # we don't need cx

  local cur_prompt
  if [[ -n $PREBUFFER ]]; then
    cur_prompt=$PS2
    # decide wether we're at the PS2 or PS1 prompt
  else
    cur_prompt=$PS1
  fi
  if [[ -o promptsubst ]]; then
    cur_prompt=${(e)cur_prompt}
  else
    cur_prompt=$cur_prompt
  fi

  # remove visual effects:
  cur_prompt=${(S)cur_prompt//("%{"*"%}"|%[BbEuUsS])/}

  # restore the exit status in case $PS<n> relies on it
  set-status $last_status
  cur_prompt=${(%)cur_prompt}
  cur_prompt=${cur_prompt##*$'\n'}
    
  local -a pos # array holding the possible positions of
               # the mouse pointer
  local -i x=0 y=1 cursor=$((${#cur_prompt}+$CURSOR+1))
  local Y
  buf=$cur_prompt$BUFFER
  for ((i=1; i<=$#buf; i++)); do
    (( i == cursor )) && Y=$y
    case $buf[i] in
      ($'\n') # newline
	: ${pos[y]=$i}
        (( y++, x=0 ));;
      ($'\t') # tab advance til next tab stop
        (( x = x/8*8+8 ));;
      ([$'\0'-$'\037'$'\0200'-$'\0237'])
        # characters like ^M
        (( x += 2 ));;
	# may cause trouble if spanned on two lines but well...
      (*)
	(( x++ ));;
    esac
    (( x >= mx )) && : ${pos[y]=$i}
    (( x >= COLUMNS )) && (( x=0, y++ ))
  done
  : ${pos[y]=$i} ${Y:=$y}

  local mouse_CURSOR
  if ((my + Y - cy > y)); then
    mouse_CURSOR=$#BUFFER
  elif ((my + Y - cy < 1)); then
    mouse_CURSOR=0
  else
    mouse_CURSOR=$(($pos[my + Y - cy] - ${#cur_prompt} - 1))
  fi

  case $ZLE_MOUSE_BUTTON in
    (' ')
      # Button 1.  Move cursor.
      CURSOR=$mouse_CURSOR
    ;;

    ('!')
      # Button 2.  Insert selection at mouse cursor postion.
      BUFFER=$BUFFER[1,mouse_CURSOR]$CUTBUFFER$BUFFER[mouse_CURSOR+1,-1]
      (( CURSOR = $mouse_CURSOR + $#CUTBUFFER ))
    ;;

    ('"')
      # Button 3.  Copy from cursor to mouse to cutbuffer.
      killring=("$CUTBUFFER" "${(@)killring[1,-2]}")
      if (( mouse_CURSOR < CURSOR )); then
	CUTBUFFER=$BUFFER[mouse_CURSOR+1,CURSOR+1]
      else
	CUTBUFFER=$BUFFER[CURSOR+1,mouse_CURSOR+1]
      fi
    ;;
  esac
}
### code ends

-- 
Stéphane



Messages sorted by: Reverse Date, Date, Thread, Author