Zsh zle shift selection

log0 picture log0 · Mar 23, 2011 · Viewed 7.5k times · Source

How to use shift to select part of the commandline (like in many text editors) ?

Answer

Jamie Treworgy picture Jamie Treworgy · Jun 17, 2015

Expanding on Stephane's excellent answer from almost 3 years ago, I added some more bindings to make the behaviour (almost) completely consistent with all of Windows' standard keyboard behaviour:

  • Selection is cleared when using a navigation key (arrow, home, end) WITHOUT shift
  • Backspace and Del delete an active selection
  • Selection is extended to the next/previous word when using Ctrl+Shift+Left/Ctrl+Shift+Right
  • Shift+Home and Shift+End extend the selection to the beginning and end of line respectively. Ctrl+Shift+Home and Ctrl+Shift+End do the same.

Two things that are not exactly the same:

  • Extending a selection to the next word includes trailing space, unlike windows. This could be fixed, but it doesn't bother me.
  • Typing when there is an active selection will not delete it and replace it with the character you typed. This would seem to require a lot more work to remap the entire keyboard. Not worth the trouble to me.

Note that the default mintty behaviour is to bind Shift+End and Shift+Home to access the scroll back buffer. This supercedes the zsh configuration; the keys never get passed through. In order for these to work, you will need to configure a different key (or disable scroll back) in /etc/minttyrc or ~/.minttyrc. See "modifier for scrolling" here - the simplest solution is just set ScrollMod=2 to bind it to Alt instead of Shift.

So everything:

~/.minttyrc

ScrollMod=2

~/.zshrc

r-delregion() {
  if ((REGION_ACTIVE)) then
     zle kill-region
  else 
    local widget_name=$1
    shift
    zle $widget_name -- $@
  fi
}

r-deselect() {
  ((REGION_ACTIVE = 0))
  local widget_name=$1
  shift
  zle $widget_name -- $@
}

r-select() {
  ((REGION_ACTIVE)) || zle set-mark-command
  local widget_name=$1
  shift
  zle $widget_name -- $@
}

for key     kcap   seq        mode   widget (
    sleft   kLFT   $'\e[1;2D' select   backward-char
    sright  kRIT   $'\e[1;2C' select   forward-char
    sup     kri    $'\e[1;2A' select   up-line-or-history
    sdown   kind   $'\e[1;2B' select   down-line-or-history

    send    kEND   $'\E[1;2F' select   end-of-line
    send2   x      $'\E[4;2~' select   end-of-line

    shome   kHOM   $'\E[1;2H' select   beginning-of-line
    shome2  x      $'\E[1;2~' select   beginning-of-line

    left    kcub1  $'\EOD'    deselect backward-char
    right   kcuf1  $'\EOC'    deselect forward-char

    end     kend   $'\EOF'    deselect end-of-line
    end2    x      $'\E4~'    deselect end-of-line

    home    khome  $'\EOH'    deselect beginning-of-line
    home2   x      $'\E1~'    deselect beginning-of-line

    csleft  x      $'\E[1;6D' select   backward-word
    csright x      $'\E[1;6C' select   forward-word
    csend   x      $'\E[1;6F' select   end-of-line
    cshome  x      $'\E[1;6H' select   beginning-of-line

    cleft   x      $'\E[1;5D' deselect backward-word
    cright  x      $'\E[1;5C' deselect forward-word

    del     kdch1   $'\E[3~'  delregion delete-char
    bs      x       $'^?'     delregion backward-delete-char

  ) {
  eval "key-$key() {
    r-$mode $widget \$@
  }"
  zle -N key-$key
  bindkey ${terminfo[$kcap]-$seq} key-$key
}

This covers keycodes from several different keyboard configurations I have used.

Note: the values in the "key" column don't mean anything, they are just used to build a named reference for zle. They could be anything. What is important is the seq, mode and widget columns.

Note 2: You can bind pretty much any keys you want, you just need the key codes used in your console emulator. Open a regular console (without running zsh) and type Ctrl+V and then the key you want. It should emit the code. ^[ means \E.