Zsh zle shift selection
Asked Answered
G

7

26

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

Greyback answered 23/3, 2011 at 15:46 Comment(8)
Which OS ? Which terminal ? Also, this might be better asked on SuperUser as it isn't programming related (but I guess programmers are more likely to use a CLI ;-)Orthodontics
Well hopefully I am looking an answer not platform or terminal dependent. Then you are right for superUser but the answer is potentially a zle script so ...Greyback
No idea what zle is, but on Unix a "selection" is a feature implemented by each terminal. It's not a basic functionality and for example on "real" terminals like text mode consoles or hardware terminal there is no support for selection (as far as I'm aware).Orthodontics
In zsh "selection" or marks are handled by zle (the zsh line editor) for what I now.Greyback
@Ugo How do you do selection? I do not know any default widgets neither for selections nor for marks, so it is likely handled by terminal.Begley
@Begley set-mark-command zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#SEC134Greyback
In fact you can select some text in your terminal (with your mouse) but it is different from selection for commandline editing.Greyback
@Ugo I knew the second fact, but I was not aware of set-mark-command, so the data I can provide won't be much helpful. By using shift you meant <S-Right> and <S-Left> combos? You can use <C-v> to get what terminal actually emit when you press <S-{Arrow}> and bind this to something, but it will be terminal-specific. If you want highligted selection and it is absent (I do not actually know), you should look at $zle_highlighting and xclip. It is not impossible to emulate selection in zsh, but I never bothered to do it and know nobody who did bother.Begley
B
22
shift-arrow() {
  ((REGION_ACTIVE)) || zle set-mark-command
  zle $1
}
shift-left()  shift-arrow backward-char
shift-right() shift-arrow forward-char
shift-up()    shift-arrow up-line-or-history
shift-down()  shift-arrow down-line-or-history
zle -N shift-left
zle -N shift-right
zle -N shift-up
zle -N shift-down

bindkey $terminfo[kLFT] shift-left
bindkey $terminfo[kRIT] shift-right
bindkey $terminfo[kri]  shift-up
bindkey $terminfo[kind] shift-down

That assumes your terminal sends a different escape sequence upon Shift-Arrows from the one sent upon Arrow and that your terminfo database is properly populated with corresponding kLFT and kRIT capabilities, and that you're using emacs style key binding.

Or, to factorize the code a bit:

shift-arrow() {
  ((REGION_ACTIVE)) || zle set-mark-command
  zle $1
}
for key  kcap seq        widget (
    left  LFT $'\e[1;2D' backward-char
    right RIT $'\e[1;2C' forward-char
    up    ri  $'\e[1;2A' up-line-or-history
    down  ind $'\e[1;2B' down-line-or-history
  ) {
  functions[shift-$key]="shift-arrow $widget"
  zle -N shift-$key
  bindkey ${terminfo[k$kcap]-$seq} shift-$key
}

Above, hardcoded sequences for cases where the terminfo database doesn't have the information (using xterm sequences).

Bradleybradly answered 30/8, 2012 at 9:18 Comment(0)
S
42

Expanding on Stéphane'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
}

# restore backward-delete-char for Backspace in the incremental
# search keymap so it keeps working there:
bindkey -M isearch '^?' backward-delete-char

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.

Symbiosis answered 17/6, 2015 at 18:17 Comment(8)
Where can I find more documentation on shome, csleft, etc? Google is not friendly here.Uric
How can I swap out ctrl for alt here? Also, how can I make these compatible with ubuntu and xterm as well?Uric
Unfortunately rebinding backspace in this way breaks typing backspace during bck-i-search.Equal
@VladimirPanteleev True - I can't figure out a good workaround - it seems to have special behavior (e.g. backward-delete-char which is the default binding for BS doens't act the same when called from a function in the bck-i-search context). You can still use CTRL+H as backspace; I guess one needs to decide whether BS key should work in this context or on the normal CLI.Symbiosis
this helps (why is this not default?) But having alt/option instead of ctrl would help even moreGoeger
The second number in those \E[1;6D codes determines the modifier: base 1, +1 for Shift, +2 for Alt/Option/Meta, +4 for Ctrl. So 6 means Shift+Ctrl (1+1+4). Alt is 3, Shift+Alt is 4.Jeffjeffcoat
Also combined this code with this answer: copy/cut/paste clipboard functions And if using WSL, change 'xclip' to 'clip.exe' for proper functioning.Mantelet
amazing stuff!! Exactly what I was looking for. I also added the following to the table to select backward and forward word using Option+Shift on macOS osleft x $'\E[1;4D' select backward-word osright x $'\E[1;4C' select forward-wordSlackjawed
B
22
shift-arrow() {
  ((REGION_ACTIVE)) || zle set-mark-command
  zle $1
}
shift-left()  shift-arrow backward-char
shift-right() shift-arrow forward-char
shift-up()    shift-arrow up-line-or-history
shift-down()  shift-arrow down-line-or-history
zle -N shift-left
zle -N shift-right
zle -N shift-up
zle -N shift-down

bindkey $terminfo[kLFT] shift-left
bindkey $terminfo[kRIT] shift-right
bindkey $terminfo[kri]  shift-up
bindkey $terminfo[kind] shift-down

That assumes your terminal sends a different escape sequence upon Shift-Arrows from the one sent upon Arrow and that your terminfo database is properly populated with corresponding kLFT and kRIT capabilities, and that you're using emacs style key binding.

Or, to factorize the code a bit:

shift-arrow() {
  ((REGION_ACTIVE)) || zle set-mark-command
  zle $1
}
for key  kcap seq        widget (
    left  LFT $'\e[1;2D' backward-char
    right RIT $'\e[1;2C' forward-char
    up    ri  $'\e[1;2A' up-line-or-history
    down  ind $'\e[1;2B' down-line-or-history
  ) {
  functions[shift-$key]="shift-arrow $widget"
  zle -N shift-$key
  bindkey ${terminfo[k$kcap]-$seq} shift-$key
}

Above, hardcoded sequences for cases where the terminfo database doesn't have the information (using xterm sequences).

Bradleybradly answered 30/8, 2012 at 9:18 Comment(0)
S
13

Expanded on Jamie Treworgy's answer.

Includes the following functionality:

  • cmd+a: select entire command-line prompt text
  • cmd+x: cut (copy & delete) current command-line selection to clipboard
  • cmd+c: copy current command-line selection to clipboard
  • cmd+v: pastes clipboard selection
  • ctrl+u: delete backwards till beginning of line
  • cmd+z: undo
  • cmd+shift+z: redo
  • shift select:
    • shift-left: select character to the left
    • shift-right: select character to the right
    • shift-up: select line upwards
    • shift-down: select live downwards
    • cmd-shift-left: select till beginning of line
    • cmd-shift-right: select till end of line
    • alt-shift-left: select word to the left
    • alt-shift-right: select word to the right
    • ctrl-shift-left: select till beginning of line
    • ctrl-shift-right: select till end of line
    • ctrl-shift-a: select till beginning of line
    • ctrl-shift-e: select till end of line
  • unselect: works as expected, on left/right, alt-left/right, cmd/ctrl-left/right, esc+esc.
  • delete selection: works as expected on Delete, ctrl+d, backspace
  • delete selection & insert character: works as expected for all visible ASCII characters and whitespace
  • delete selection & insert clipboard: works as expected

.zshrc
# for my own convenience I explicitly set the signals
#   that my terminal sends to the shell as variables.
#   you might have different signals. you can see what
#   signal each of your keys sends by running `$> cat`
#   and pressing keys (you'll be able to see most keys)
#   also some of the signals sent might be set in your 
#   terminal emulator application/program
#   configurations/preferences. finally some terminals
#   have a feature that shows you what signals are sent
#   per key press.
#
# for context, at the time of writing these variables are
#   set for the kitty terminal program, i.e these signals  
#   are mostly ones sent by default by this terminal.
export KEY_ALT_F='ƒ'
export KEY_ALT_B='∫'
export KEY_ALT_D='∂'
export KEY_CTRL_U=$'\x15' # ^U
export KEY_CMD_BACKSPACE=$'^[b'   # arbitrary; added via kitty config (send_text)
export KEY_CMD_Z=^[[122;9u
export KEY_SHIFT_CMD_Z=^[[122;10u
export KEY_CTRL_R=$'\x12' # ^R
export KEY_CMD_C=^[[99;9u
export KEY_CMD_X=^[[120;9u
export KEY_CMD_V=^[[118;9u
export KEY_CMD_A=^[[97;9u
export KEY_CTRL_L=$'\x0c' # ^L
export KEY_LEFT=${terminfo[kcub1]:-$'^[[D'}
export KEY_RIGHT=${terminfo[kcuf1]:-$'^[[C'}
export KEY_SHIFT_UP=${terminfo[kri]:-$'^[[1;2A'}
export KEY_SHIFT_DOWN=${terminfo[kind]:-$'^[[1;2B'}
export KEY_SHIFT_RIGHT=${terminfo[kRIT]:-$'^[[1;2C'}
export KEY_SHIFT_LEFT=${terminfo[kLFT]:-$'^[[1;2D'}
export KEY_ALT_LEFT=$'^[[1;3D'
export KEY_ALT_RIGHT=$'^[[1;3C'
export KEY_SHIFT_ALT_LEFT=$'^[[1;4D'
export KEY_SHIFT_ALT_RIGHT=$'^[[1;4C'
export KEY_CMD_LEFT=$'^[[1;9D'
export KEY_CMD_RIGHT=$'^[[1;9C'
export KEY_SHIFT_CMD_LEFT=$'^[[1;10D'
export KEY_SHIFT_CMD_RIGHT=$'^[[1;10C'
export KEY_CTRL_A=$'\x01' # ^A
export KEY_CTRL_E=$'\x05' # ^E
export KEY_SHIFT_CTRL_A=$'^[[97;6u'
export KEY_SHIFT_CTRL_E=$'^[[101;6u'
export KEY_SHIFT_CTRL_LEFT=$'^[[1;6D'
export KEY_SHIFT_CTRL_RIGHT=$'^[[1;6C'
export KEY_CTRL_D=$'\x04' # ^D

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

# copy selected terminal text to clipboard
zle -N widget::copy-selection
function widget::copy-selection {
    if ((REGION_ACTIVE)); then
        zle copy-region-as-kill
        printf "%s" $CUTBUFFER | pbcopy
    fi
}

# cut selected terminal text to clipboard
zle -N widget::cut-selection
function widget::cut-selection() {
    if ((REGION_ACTIVE)) then
        zle kill-region
        printf "%s" $CUTBUFFER | pbcopy
    fi
}

# paste clipboard contents
zle -N widget::paste
function widget::paste() {
    ((REGION_ACTIVE)) && zle kill-region
    RBUFFER="$(pbpaste)${RBUFFER}"
    CURSOR=$(( CURSOR + $(echo -n "$(pbpaste)" | wc -m | bc) ))
}

# select entire prompt
zle -N widget::select-all
function widget::select-all() {
    local buflen=$(echo -n "$BUFFER" | wc -m | bc)
    CURSOR=$buflen   # if this is messing up try: CURSOR=9999999
    zle set-mark-command
    while [[ $CURSOR > 0 ]]; do
        zle beginning-of-line
    done
}

# scrolls the screen up, in effect clearing it
zle -N widget::scroll-and-clear-screen
function widget::scroll-and-clear-screen() {
    printf "\n%.0s" {1..$LINES}
    zle clear-screen
}

function widget::util-select() {
    ((REGION_ACTIVE)) || zle set-mark-command
    local widget_name=$1
    shift
    zle $widget_name -- $@
}

function widget::util-unselect() {
    REGION_ACTIVE=0
    local widget_name=$1
    shift
    zle $widget_name -- $@
}

function widget::util-delselect() {
    if ((REGION_ACTIVE)) then
        zle kill-region
    else
        local widget_name=$1
        shift
        zle $widget_name -- $@
    fi
}

function widget::util-insertchar() {
    ((REGION_ACTIVE)) && zle kill-region
    RBUFFER="${1}${RBUFFER}"
    zle forward-char
}

#                       |  key sequence                   | command
# --------------------- | ------------------------------- | -------------

bindkey                   $KEY_ALT_F                        forward-word
bindkey                   $KEY_ALT_B                        backward-word
bindkey                   $KEY_ALT_D                        kill-word
bindkey                   $KEY_CTRL_U                       backward-kill-line
bindkey                   $KEY_CMD_BACKSPACE                backward-kill-line
bindkey                   $KEY_CMD_Z                        undo
bindkey                   $KEY_SHIFT_CMD_Z                  redo
bindkey                   $KEY_CTRL_R                       history-incremental-search-backward
bindkey                   $KEY_CMD_C                        widget::copy-selection
bindkey                   $KEY_CMD_X                        widget::cut-selection
bindkey                   $KEY_CMD_V                        widget::paste
bindkey                   $KEY_CMD_A                        widget::select-all
bindkey                   $KEY_CTRL_L                       widget::scroll-and-clear-screen

for keyname        kcap   seq                   mode        widget (

    left           kcub1  $KEY_LEFT             unselect    backward-char
    right          kcuf1  $KEY_RIGHT            unselect    forward-char

    shift-up       kri    $KEY_SHIFT_UP         select      up-line-or-history
    shift-down     kind   $KEY_SHIFT_DOWN       select      down-line-or-history
    shift-right    kRIT   $KEY_SHIFT_RIGHT      select      forward-char
    shift-left     kLFT   $KEY_SHIFT_LEFT       select      backward-char

    alt-right         x   $KEY_ALT_RIGHT        unselect    forward-word
    alt-left          x   $KEY_ALT_LEFT         unselect    backward-word
    shift-alt-right   x   $KEY_SHIFT_ALT_RIGHT  select      forward-word
    shift-alt-left    x   $KEY_SHIFT_ALT_LEFT   select      backward-word

    cmd-right         x   $KEY_CMD_RIGHT        unselect    end-of-line
    cmd-left          x   $KEY_CMD_LEFT         unselect    beginning-of-line
    shift-cmd-right   x   $KEY_SHIFT_CMD_RIGHT  select      end-of-line
    shift-cmd-left    x   $KEY_SHIFT_CMD_LEFT   select      beginning-of-line

    ctrl-e            x   $KEY_CTRL_E           unselect    end-of-line
    ctrl-a            x   $KEY_CTRL_A           unselect    beginning-of-line
    shift-ctrl-e      x   $KEY_SHIFT_CTRL_E     select      end-of-line
    shift-ctrl-a      x   $KEY_SHIFT_CTRL_A     select      beginning-of-line
    shift-ctrl-right  x   $KEY_SHIFT_CTRL_RIGHT select      end-of-line
    shift-ctrl-left   x   $KEY_SHIFT_CTRL_LEFT  select      beginning-of-line

    del               x   $KEY_CTRL_D           delselect   delete-char

    a                 x       'a'               insertchar  'a'
    b                 x       'b'               insertchar  'b'
    c                 x       'c'               insertchar  'c'
    d                 x       'd'               insertchar  'd'
    e                 x       'e'               insertchar  'e'
    f                 x       'f'               insertchar  'f'
    g                 x       'g'               insertchar  'g'
    h                 x       'h'               insertchar  'h'
    i                 x       'i'               insertchar  'i'
    j                 x       'j'               insertchar  'j'
    k                 x       'k'               insertchar  'k'
    l                 x       'l'               insertchar  'l'
    m                 x       'm'               insertchar  'm'
    n                 x       'n'               insertchar  'n'
    o                 x       'o'               insertchar  'o'
    p                 x       'p'               insertchar  'p'
    q                 x       'q'               insertchar  'q'
    r                 x       'r'               insertchar  'r'
    s                 x       's'               insertchar  's'
    t                 x       't'               insertchar  't'
    u                 x       'u'               insertchar  'u'
    v                 x       'v'               insertchar  'v'
    w                 x       'w'               insertchar  'w'
    x                 x       'x'               insertchar  'x'
    y                 x       'y'               insertchar  'y'
    z                 x       'z'               insertchar  'z'
    A                 x       'A'               insertchar  'A'
    B                 x       'B'               insertchar  'B'
    C                 x       'C'               insertchar  'C'
    D                 x       'D'               insertchar  'D'
    E                 x       'E'               insertchar  'E'
    F                 x       'F'               insertchar  'F'
    G                 x       'G'               insertchar  'G'
    H                 x       'H'               insertchar  'H'
    I                 x       'I'               insertchar  'I'
    J                 x       'J'               insertchar  'J'
    K                 x       'K'               insertchar  'K'
    L                 x       'L'               insertchar  'L'
    M                 x       'M'               insertchar  'M'
    N                 x       'N'               insertchar  'N'
    O                 x       'O'               insertchar  'O'
    P                 x       'P'               insertchar  'P'
    Q                 x       'Q'               insertchar  'Q'
    R                 x       'R'               insertchar  'R'
    S                 x       'S'               insertchar  'S'
    T                 x       'T'               insertchar  'T'
    U                 x       'U'               insertchar  'U'
    V                 x       'V'               insertchar  'V'
    W                 x       'W'               insertchar  'W'
    X                 x       'X'               insertchar  'X'
    Y                 x       'Y'               insertchar  'Y'
    Z                 x       'Z'               insertchar  'Z'
    0                 x       '0'               insertchar  '0'
    1                 x       '1'               insertchar  '1'
    2                 x       '2'               insertchar  '2'
    3                 x       '3'               insertchar  '3'
    4                 x       '4'               insertchar  '4'
    5                 x       '5'               insertchar  '5'
    6                 x       '6'               insertchar  '6'
    7                 x       '7'               insertchar  '7'
    8                 x       '8'               insertchar  '8'
    9                 x       '9'               insertchar  '9'

    exclamation-mark      x  '!'                insertchar  '!'
    hash-sign             x  '\#'               insertchar  '\#'
    dollar-sign           x  '$'                insertchar  '$'
    percent-sign          x  '%'                insertchar  '%'
    ampersand-sign        x  '\&'               insertchar  '\&'
    star                  x  '\*'               insertchar  '\*'
    plus                  x  '+'                insertchar  '+'
    comma                 x  ','                insertchar  ','
    dot                   x  '.'                insertchar  '.'
    forwardslash          x  '\\'               insertchar  '\\'
    backslash             x  '/'                insertchar  '/'
    colon                 x  ':'                insertchar  ':'
    semi-colon            x  '\;'               insertchar  '\;'
    left-angle-bracket    x  '\<'               insertchar  '\<'
    right-angle-bracket   x  '\>'               insertchar  '\>'
    equal-sign            x  '='                insertchar  '='
    question-mark         x  '\?'               insertchar  '\?'
    left-square-bracket   x  '['                insertchar  '['
    right-square-bracket  x  ']'                insertchar  ']'
    hat-sign              x  '^'                insertchar  '^'
    underscore            x  '_'                insertchar  '_'
    left-brace            x  '{'                insertchar  '{'
    right-brace           x  '\}'               insertchar  '\}'
    left-parenthesis      x  '\('               insertchar  '\('
    right-parenthesis     x  '\)'               insertchar  '\)'
    pipe                  x  '\|'               insertchar  '\|'
    tilde                 x  '\~'               insertchar  '\~'
    at-sign               x  '@'                insertchar  '@'
    dash                  x  '\-'               insertchar  '\-'
    double-quote          x  '\"'               insertchar  '\"'
    single-quote          x  "\'"               insertchar  "\'"
    backtick              x  '\`'               insertchar  '\`'
    whitespace            x  '\ '               insertchar  '\ '
) {
    eval "function widget::key-$keyname() {
        widget::util-$mode $widget \$@
    }"
    zle -N widget::key-$keyname
    bindkey $seq widget::key-$keyname
}

# suggested by "e.nikolov", fixes autosuggest completion being 
# overriden by keybindings: to have [zsh] autosuggest [plugin
# feature] complete visible suggestions, you can assign an array
# of shell functions to the `ZSH_AUTOSUGGEST_ACCEPT_WIDGETS` 
# variable. when these functions are triggered, they will also 
# complete any visible suggestion. Example:
export ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(
    widget::key-right
    widget::key-shift-right
    widget::key-cmd-right
    widget::key-shift-cmd-right
)

Bonus

Some people might also find these useful, though I didn't include them above, just add them to the for-loop array:

  1. Tab

        tab                   x  $'\x09'           insertchar  '\   '
    
    • note that tab completion will fail
  2. Other

        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
    
        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
    
        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
    

OLD but maybe still useful for some

Note

Certain keyboard key sequences were first configured on the terminal application (in my case iTerm2) to send the shell program specific signals. The bindings used in the above code are:

➤ iTerm2
   ➤ Preferences
     ➤ Keys
       ➤ Key Bindings:
Key Sequence Keybinding
cmd+shift+left Send Escape Sequence: a
cmd+shift+right Send Escape Sequence: e
ctrl+shift+a Send Escape Sequence: a
ctrl+shift+e Send Escape Sequence: e
cmd+left Send Hex Codes: \x01
cmd+right Send Hex Codes: \x05
cmd+a Send Escape Sequence: å
cmd+c Send Escape Sequence: ç
cmd+v Send Escape Sequence:
cmd+x Send Escape Sequence:
cmd+z Send Escape Sequence: Ω
cmd+shift+z Send Escape Sequence: ¸

This step binds terminal keys to shell signals, i.e tells the terminal program/application (iTerm2) what signals to send the shell program (zsh) upon pressing certain keyboard key sequences. Depending on your terminal program, and preference, you can bind your keys however you'd like, or use the default signals.

keyboard  -->  cmd+z  -->  iTerm2  -->  ^[Ω  -->  zsh  -->  undo (widget)

Then the script above binds the received signals to shell functions, called widgets. I.e we tell the shell program, to run a specified widget upon receiving a specified signal (key sequence).

So on your command-line, upon pressing keyboard key sequences, the terminal sends the appropriate signal to the shell, and the shell calls the corresponding widget (function). The functions we tell the shell to bind to are in this case functions that edit the command-line itself, as though it was a file.

The widgets defined above use zsh's builtin zle (zsh line editor) module API. For more information you can see the official documentation: ZSH ➤ 18 Zsh Line Editor, and official guide: ZSH ➤ Chapter 4: The Z-Shell Line Editor.

  • A neat trick to see what signals are received by the shell is running cat and then pressing keys:

    ❯ cat
    ^[[1;2D^[[1;2C^[Ω^[≈^[ç
    

    That was the output for after having pressed: shift-left, shift-right, cmd+z, cmd+x, and cmd+c.

    Certain keys might not appear. In this case, check your terminal's configurations, the key might be binded to some terminal functionality (e.g. cmd+n might open up a new terminal pane, cmd+t might open up a new terminal tab).

    Also see terminfo(5), another way of finding certain keys.


Known Issues & Issue Fixes

  • if you're on iTerm2, changing what cmd+v is binded to might make pasting on anything other than the command-line act different, and requires remapping on that specific program (e.g. in the search prompts for programs like less). if you want to avoid this, then don't change the mapping of iTerm2's cmd+v, and comment-out/remove the widget::paste.

  • esc might clash with oh-my-zsh's sudo plugin and give weird behavior. You can comment-out/remove the esc line in the array, or suggest a fix.

  • right clashes with zsh-autosuggestion, i.e it won't accept the suggestion. You can comment-out/remove right from the array, or suggest a fix. This is probably possible, I just currently don't know how, and spent enough time for now trying.

    I tried many things, I think the closest thing to working might of been something like:

    function widget::key-right() {
        REGION_ACTIVE=0
        zle autosuggest-accept
        zle forward-char
    }
    zle -N widget::key-right
    bindkey $'\eOC' widget::key-right
    

    But to no avail. It doesn't complete the suggestion. You could however always create a new keybinding for it:

    bindkey $'\e\'' autosuggest-accept
    

    I got autosuggestion-accept from the Github repo: zsh-users/zsh-autosuggestions.

    To fix right-key clashing with zsh-autosuggestion, and/or other clashes you might have, add to one of your shell initialization files (e.g. .zshrc) the following: export ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(<shell-function> ...) (suggested by @e.nikolov). see above for an example.

Satinwood answered 30/8, 2021 at 16:39 Comment(2)
The autosuggest worked when I did this: export ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=widget::key-rightLandgrabber
It should probably be export ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(widget::key-right widget::key-shift-right widget::key-shift-cmd-right widget::key-cmd-right). If it's not an array, there are some other compatibility issues.Landgrabber
F
6

All solutions on this page are either incomplete or too invasive, so they negatively interfere with other plugins (e.g. zsh-autosuggestions or zsh-syntax-highlighting). I have therefore come up with a different approach that works significantly better.

https://github.com/jirutka/zsh-shift-select/

This plugin does not override any existing widgets and binds only shifted keys. It creates a new shift-select keymap that is automatically activated when the shift selection is invoked (using any of the defined shifted keys) and deactivated (the current keymap switches back to main) on any key that is not defined in the shift-select keymap. Thanks to this approach, it does not interfere with other plugins (for example, it works with zsh-autosuggestions without any change).

Farm answered 13/3, 2022 at 0:41 Comment(0)
M
3

For Windows and WSL users.

This is combination of other authors answers with respect to them, modified to work in WSL with Window Terminal. Behaviour as in standard PowerShell: ctrl+shift+arrows select, ctrl+z,x,c,v,a etc.

# zsh-shift-select https://stackoverflow.com/a/30899296
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
}
# Fix zsh-autosuggestions https://stackoverflow.com/a/30899296
export ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(
  key-right
)
# ctrl+x,c,v https://unix.stackexchange.com/a/634916/424080
function zle-clipboard-cut {
  if ((REGION_ACTIVE)); then
    zle copy-region-as-kill
    print -rn -- $CUTBUFFER | clip.exe
    zle kill-region
  fi
}
zle -N zle-clipboard-cut
function zle-clipboard-copy {
  if ((REGION_ACTIVE)); then
    zle copy-region-as-kill
    print -rn -- $CUTBUFFER | clip.exe
  else
    zle send-break
  fi
}
zle -N zle-clipboard-copy
function zle-clipboard-paste {
  if ((REGION_ACTIVE)); then
    zle kill-region
  fi
  LBUFFER+="$(cat clip.exe)"
}
zle -N zle-clipboard-paste
function zle-pre-cmd {
  stty intr "^@"
}
precmd_functions=("zle-pre-cmd" ${precmd_functions[@]})
function zle-pre-exec {
  stty intr "^C"
}
preexec_functions=("zle-pre-exec" ${preexec_functions[@]})
for key     kcap    seq           widget              arg (
    cx      _       $'^X'         zle-clipboard-cut   _
    cc      _       $'^C'         zle-clipboard-copy  _
    cv      _       $'^V'         zle-clipboard-paste _
) {
  if [ "${arg}" = "_" ]; then
    eval "key-$key() {
      zle $widget
    }"
  else
    eval "key-$key() {
      zle-$widget $arg \$@
    }"
  fi
  zle -N key-$key
  bindkey ${terminfo[$kcap]-$seq} key-$key
}
# ctrl+a https://mcmap.net/q/513008/-zsh-zle-shift-selection
function widget::select-all() {
  local buflen=$(echo -n "$BUFFER" | wc -m | bc)
  CURSOR=$buflen  
  zle set-mark-command
  while [[ $CURSOR > 0 ]]; do
    zle beginning-of-line
  done
}
zle -N widget::select-all
bindkey '^a' widget::select-all
# ctrl+z
bindkey "^Z" undo

Tested on WSL2 "Linux 5.15.79.1-microsoft-standard-WSL2" with Windows Terminal "1.15.3466.0".

Mantelet answered 12/1, 2023 at 21:20 Comment(0)
S
1

Thanks for this answers, i take peace of all and do my script to select and copy just using keyboard.

if someone wanna filter to things be more clean i will appreciate.

its a part of ~/.zshrc file into home user folder.

alias pbcopy="xclip -selection clipboard"

shift-arrow() {


((REGION_ACTIVE)) || zle set-mark-command 
  zle $1
}
for key  kcap seq        widget (
    left  LFT $'\e[1;2D' backward-char
    right RIT $'\e[1;2C' forward-char
    up    ri  $'\e[1;2A' up-line-or-history
    down  ind $'\e[1;2B' down-line-or-history
    super sup $'\ec' widget::copy-selection
  ) {
  functions[shift-$key]="shift-arrow $widget"
  zle -N shift-$key
  bindkey ${terminfo[k$kcap]-$seq} shift-$key
}
 
zle -N widget::copy-selection


# copy selected terminal text to clipboard
function widget::copy-selection {
    if ((REGION_ACTIVE)); then
        zle copy-region-as-kill
            printf "%s" $CUTBUFFER | pbcopy
    fi
}

i used de windows+c button to copy selected characters. I'm using ubuntu 20.04 and configured keyboard option to using win button like meta! After this in preferences of terminator i change paste shortcut to windows+v, jts because a think its be more faster like control+x and COntrol+v

Stellular answered 20/9, 2021 at 3:7 Comment(0)
M
1

Here is my implementation of @8c6b5df0d16ade6c 's answer:

Code

###############
# Keybindings #
###############

# Bind Alt + Delete for forward deleting a word
bindkey -M emacs '^[[3;3~' kill-word

# Bind Delete to delete a letter to the right
bindkey "^[[3~" delete-char

# Ctrl binds
# Bind Ctrl + Delete to delete word to the right
bindkey '^[[3;5~' kill-word

# Bind Ctrl + Backspace to delete word to the left
bindkey '^H' backward-kill-word

# Bind Ctrl + Right Arrow to move to the next word
bindkey '^[[1;5C' forward-word

################
# Shift Select #
################

# for my own convenience I explicitly set the signals
#   that my terminal sends to the shell as variables.
#   you might have different signals. you can see what
#   signal each of your keys sends by running `cat`
#   and pressing keys (you'll be able to see most keys)
#   also some of the signals sent might be set in your 
#   terminal emulator application/program
#   configurations/preferences. finally some terminals
#   have a feature that shows you what signals are sent
#   per key press.
#
# for context, at the time of writing these variables are
#   set for the kitty terminal program, i.e these signals  
#   are mostly ones sent by default by this terminal.
export KEY_ALT_B='^[b'
export KEY_ALT_D='^[d'
export KEY_ALT_F='^[f'

export KEY_CTRL_A='^A'
export KEY_CTRL_E='^E'
export KEY_CTRL_L='^L'
export KEY_CTRL_R='^R'
export KEY_CTRL_U='^U'
export KEY_CTRL_Z='^Z'

export KEY_SHIFT_CTRL_A='^[[27;6;65~'
export KEY_SHIFT_CTRL_C='^[[27;6;67~'
export KEY_SHIFT_CTRL_E='^[[27;6;69~'
export KEY_SHIFT_CTRL_V='^[[27;6;86~'
export KEY_SHIFT_CTRL_X='^[[27;6;88~'
export KEY_SHIFT_CTRL_Z='^[[27;6;90~'

export KEY_LEFT='^[[D'
export KEY_RIGHT='^[[C'
export KEY_SHIFT_UP='^[[1;2A'
export KEY_SHIFT_DOWN='^[[1;2B'
export KEY_SHIFT_RIGHT='^[[1;2C'
export KEY_SHIFT_LEFT='^[[1;2D'
export KEY_ALT_LEFT='^[[1;3D' 
export KEY_ALT_RIGHT='^[[1;3C'
export KEY_SHIFT_ALT_LEFT='^[[1;4D'   
export KEY_SHIFT_ALT_RIGHT='^[[1;6C'
export KEY_CTRL_LEFT='^[[1;5D'
export KEY_CTRL_RIGHT='^[[1;5C'
export KEY_SHIFT_CTRL_LEFT='^[[1;6D'
export KEY_SHIFT_CTRL_RIGHT='^[[1;6C'

export KEY_END='^[[F;'
export KEY_HOME='^[[H'
export KEY_SHIFT_END='^[[1;2F'
export KEY_SHIFT_HOME='^[[1;2H'

export KEY_DELETE='^[[3~'
export KEY_BACKSPACE='^?'
export KEY_CTRL_BACKSPACE='^H'

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

# copy selected terminal text to clipboard
zle -N widget::copy-selection
function widget::copy-selection {
    if ((REGION_ACTIVE)); then
        zle copy-region-as-kill
        if test "$XDG_SESSION_TYPE" = "x11"; then
            printf "%s" $CUTBUFFER | xclip
        elif test "$XDG_SESSION_TYPE" = "wayland"; then
            printf "%s" $CUTBUFFER | wl-copy
        fi
    fi
}

# cut selected terminal text to clipboard
zle -N widget::cut-selection
function widget::cut-selection() {
    if ((REGION_ACTIVE)) then
        zle kill-region
        if test "$XDG_SESSION_TYPE" = "x11"; then
            printf "%s" $CUTBUFFER | xclip
        elif test "$XDG_SESSION_TYPE" = "wayland"; then
            printf "%s" $CUTBUFFER | wl-copy
        fi
    fi
}

# paste clipboard contents
zle -N widget::paste
function widget::paste() {
    ((REGION_ACTIVE)) && zle kill-region
    # change to use xclip/wl-copy
    if test "$XDG_SESSION_TYPE" = "x11"; then
        RBUFFER="$(xclip -o -selection clipboard)${RBUFFER}"
        CURSOR=$(( CURSOR + $(echo -n "$(xclip -o -selection clipboard)" | wc -m | bc) ))
    elif test "$XDG_SESSION_TYPE" = "wayland"; then
        RBUFFER="$(wl-paste)${RBUFFER}"
        CURSOR=$(( CURSOR + $(echo -n "$(wl-paste)" | wc -m | bc) ))
    fi
}

# select entire prompt
zle -N widget::select-all
function widget::select-all() {
    local buflen=$(echo -n "$BUFFER" | wc -m | bc)
    CURSOR=$buflen   # if this is messing up try: CURSOR=9999999
    zle set-mark-command
    while [[ $CURSOR > 0 ]]; do
        zle beginning-of-line
    done
}

# scrolls the screen up, in effect clearing it
zle -N widget::scroll-and-clear-screen
function widget::scroll-and-clear-screen() {
    printf "\n%.0s" {1..$LINES}
    zle clear-screen
}

function widget::util-select() {
    ((REGION_ACTIVE)) || zle set-mark-command
    local widget_name=$1
    shift
    zle $widget_name -- $@
    zle copy-region-as-kill
    printf "%s" $CUTBUFFER | wl-copy --primary
    if test "$XDG_SESSION_TYPE" = "x11"; then
        printf "%s" $CUTBUFFER | xclip -selection --primary
    elif test "$XDG_SESSION_TYPE" = "wayland"; then
        printf "%s" $CUTBUFFER | wl-copy --primary
    fi
}

function widget::util-unselect() {
    REGION_ACTIVE=0
    local widget_name=$1
    shift
    zle $widget_name -- $@
}

function widget::util-delselect() {
    if ((REGION_ACTIVE)) then
        zle kill-region
    else
        local widget_name=$1
        shift
        zle $widget_name -- $@
    fi
}

function widget::util-insertchar() {
    ((REGION_ACTIVE)) && zle kill-region
    RBUFFER="${1}${RBUFFER}"
    zle forward-char
}


#                       |  key sequence                   | command
# --------------------- | ------------------------------- | -------------

bindkey                   $KEY_ALT_F                        forward-word
bindkey                   $KEY_ALT_B                        backward-word
bindkey                   $KEY_ALT_D                        kill-word
bindkey                   $KEY_CTRL_U                       backward-kill-line
bindkey                   $KEY_CTRL_BACKSPACE               backward-kill-word
bindkey                   $KEY_CTRL_Z                       undo
bindkey                   $KEY_SHIFT_CTRL_Z                 redo
bindkey                   $KEY_CTRL_R                       history-incremental-search-backward
bindkey                   $KEY_SHIFT_CTRL_C                 widget::copy-selection
bindkey                   $KEY_SHIFT_CTRL_X                 widget::cut-selection
bindkey                   $KEY_SHIFT_CTRL_V                 widget::paste
bindkey                   $KEY_SHIFT_CTRL_A                 widget::select-all
bindkey                   $KEY_CTRL_L                       widget::scroll-and-clear-screen

for keyname        kcap   seq                   mode        widget (

    left           kcub1  $KEY_LEFT             unselect    backward-char
    right          kcuf1  $KEY_RIGHT            unselect    forward-char

    shift-up       kri    $KEY_SHIFT_UP         select      up-line-or-history
    shift-down     kind   $KEY_SHIFT_DOWN       select      down-line-or-history
    shift-right    kRIT   $KEY_SHIFT_RIGHT      select      forward-char
    shift-left     kLFT   $KEY_SHIFT_LEFT       select      backward-char

    alt-right         x   $KEY_ALT_RIGHT        unselect    forward-word
    alt-left          x   $KEY_ALT_LEFT         unselect    backward-word
    shift-alt-right   x   $KEY_SHIFT_ALT_RIGHT  select      forward-word
    shift-alt-left    x   $KEY_SHIFT_ALT_LEFT   select      backward-word

    ctrl-right        x   $KEY_CTRL_RIGHT       unselect    forward-word
    ctrl-left         x   $KEY_CTRL_LEFT        unselect    backward-word
    shift-cmd-right   x   $KEY_SHIFT_CTRL_RIGHT select      end-of-line
    shift-cmd-left    x   $KEY_SHIFT_CTRL_LEFT  select      beginning-of-line

    ctrl-e            x   $KEY_CTRL_E           unselect    end-of-line
    ctrl-a            x   $KEY_CTRL_A           unselect    beginning-of-line
    shift-ctrl-e      x   $KEY_SHIFT_CTRL_E     select      end-of-line
    shift-ctrl-a      x   $KEY_SHIFT_CTRL_A     select      beginning-of-line
    shift-ctrl-right  x   $KEY_SHIFT_CTRL_RIGHT select      forward-word
    shift-ctrl-left   x   $KEY_SHIFT_CTRL_LEFT  select      backward-word

    end            kend   $KEY_END              unselect    end-of-line
    shift-end      kEND   $KEY_SHIFT_END        select      end-of-line

    home           khome  $KEY_HOME             unselect    beginning-of-line
    shift-home     kHOM   $KEY_SHIFT_HOME       select      beginning-of-line

    del               x   $KEY_DELETE           delselect   delete-char
    backspace         x   $KEY_BACKSPACE        delselect   backward-delete-char

    a                 x       'a'               insertchar  'a'
        b                 x       'b'               insertchar  'b'
    c                 x       'c'               insertchar  'c'
    d                 x       'd'               insertchar  'd'
    e                 x       'e'               insertchar  'e'
    f                 x       'f'               insertchar  'f'
    g                 x       'g'               insertchar  'g'
    h                 x       'h'               insertchar  'h'
    i                 x       'i'               insertchar  'i'
    j                 x       'j'               insertchar  'j'
    k                 x       'k'               insertchar  'k'
    l                 x       'l'               insertchar  'l'
    m                 x       'm'               insertchar  'm'
    n                 x       'n'               insertchar  'n'
    o                 x       'o'               insertchar  'o'
    p                 x       'p'               insertchar  'p'
    q                 x       'q'               insertchar  'q'
    r                 x       'r'               insertchar  'r'
    s                 x       's'               insertchar  's'
    t                 x       't'               insertchar  't'
    u                 x       'u'               insertchar  'u'
    v                 x       'v'               insertchar  'v'
    w                 x       'w'               insertchar  'w'
    x                 x       'x'               insertchar  'x'
    y                 x       'y'               insertchar  'y'
    z                 x       'z'               insertchar  'z'
    A                 x       'A'               insertchar  'A'
    B                 x       'B'               insertchar  'B'
    C                 x       'C'               insertchar  'C'
    D                 x       'D'               insertchar  'D'
    E                 x       'E'               insertchar  'E'
    F                 x       'F'               insertchar  'F'
    G                 x       'G'               insertchar  'G'
    H                 x       'H'               insertchar  'H'
    I                 x       'I'               insertchar  'I'
    J                 x       'J'               insertchar  'J'
    K                 x       'K'               insertchar  'K'
    L                 x       'L'               insertchar  'L'
    M                 x       'M'               insertchar  'M'
    N                 x       'N'               insertchar  'N'
    O                 x       'O'               insertchar  'O'
    P                 x       'P'               insertchar  'P'
    Q                 x       'Q'               insertchar  'Q'
    R                 x       'R'               insertchar  'R'
    S                 x       'S'               insertchar  'S'
    T                 x       'T'               insertchar  'T'
    U                 x       'U'               insertchar  'U'
    V                 x       'V'               insertchar  'V'
    W                 x       'W'               insertchar  'W'
    X                 x       'X'               insertchar  'X'
    Y                 x       'Y'               insertchar  'Y'
    Z                 x       'Z'               insertchar  'Z'
    0                 x       '0'               insertchar  '0'
    1                 x       '1'               insertchar  '1'
    2                 x       '2'               insertchar  '2'
    3                 x       '3'               insertchar  '3'
    4                 x       '4'               insertchar  '4'
    5                 x       '5'               insertchar  '5'
    6                 x       '6'               insertchar  '6'
    7                 x       '7'               insertchar  '7'
    8                 x       '8'               insertchar  '8'
    9                 x       '9'               insertchar  '9'

    exclamation-mark      x  '!'                insertchar  '!'
    hash-sign             x  '\#'               insertchar  '\#'
    dollar-sign           x  '$'                insertchar  '$'
    percent-sign          x  '%'                insertchar  '%'
    ampersand-sign        x  '\&'               insertchar  '\&'
    star                  x  '\*'               insertchar  '\*'
    plus                  x  '+'                insertchar  '+'
    comma                 x  ','                insertchar  ','
    dot                   x  '.'                insertchar  '.'
    forwardslash          x  '\\'               insertchar  '\\'
    backslash             x  '/'                insertchar  '/'
    colon                 x  ':'                insertchar  ':'
    semi-colon            x  '\;'               insertchar  '\;'
    left-angle-bracket    x  '\<'               insertchar  '\<'
    right-angle-bracket   x  '\>'               insertchar  '\>'
    equal-sign            x  '='                insertchar  '='
    question-mark         x  '\?'               insertchar  '\?'
    left-square-bracket   x  '['                insertchar  '['
    right-square-bracket  x  ']'                insertchar  ']'
    hat-sign              x  '^'                insertchar  '^'
    underscore            x  '_'                insertchar  '_'
    left-brace            x  '{'                insertchar  '{'
    right-brace           x  '\}'               insertchar  '\}'
    left-parenthesis      x  '\('               insertchar  '\('
    right-parenthesis     x  '\)'               insertchar  '\)'
    pipe                  x  '\|'               insertchar  '\|'
    tilde                 x  '\~'               insertchar  '\~'
    at-sign               x  '@'                insertchar  '@'
    dash                  x  '\-'               insertchar  '\-'
    double-quote          x  '\"'               insertchar  '\"'
    single-quote          x  "\'"               insertchar  "\'"
    backtick              x  '\`'               insertchar  '\`'
    whitespace            x  '\ '               insertchar  '\ '
) {
    eval "function widget::key-$keyname() {
        widget::util-$mode $widget \$@
    }"
    zle -N widget::key-$keyname
    bindkey $seq widget::key-$keyname
}

# Fixes autosuggest completion being overriden by keybindings: 
# to have [zsh] autosuggest [plugin feature] complete visible 
# suggestions, you can assign an array of shell functions to 
# the `ZSH_AUTOSUGGEST_ACCEPT_WIDGETS` variable. When these 
# functions are triggered, they will also complete any visible 
# suggestion. Example:
export ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(
    widget::key-right
    widget::key-shift-right
    widget::key-cmd-right
    widget::key-shift-cmd-right
) 

Use case

This is intended to be used with non-MacOS setups often using Ctrl + Shift instead of Cmd as the mod key(s) for the bind.

Custom escape sequences and example

You will also have to set custom escape sequences for Ctrl + Shift binds in your terminal emulator, because terminal emulators receive the same escape codes for Ctrl + <letter> and Ctrl + Shift + <letter>. Here is an example for the Foot terminal emulator:

[text-bindings]
\x1b[1;2F = Shift+KP_End
\x1b[1;2H = Shift+KP_Home

\x1b[27;6;65~ = Control+Shift+a
\x1b[27;6;66~ = Control+Shift+b
#\x1b[27;6;67~ = Control+Shift+c
\x1b[27;6;68~ = Control+Shift+d
\x1b[27;6;69~ = Control+Shift+e
\x1b[27;6;70~ = Control+Shift+f
\x1b[27;6;71~ = Control+Shift+g
\x1b[27;6;72~ = Control+Shift+h
\x1b[27;6;73~ = Control+Shift+i
\x1b[27;6;74~ = Control+Shift+j
\x1b[27;6;75~ = Control+Shift+k
\x1b[27;6;76~ = Control+Shift+l
\x1b[27;6;77~ = Control+Shift+m
#\x1b[27;6;78~ = Control+Shift+n
#\x1b[27;6;79~ = Control+Shift+o
\x1b[27;6;80~ = Control+Shift+p
\x1b[27;6;81~ = Control+Shift+q
#\x1b[27;6;82~ = Control+Shift+r
\x1b[27;6;83~ = Control+Shift+s
\x1b[27;6;84~ = Control+Shift+t
#\x1b[27;6;85~ = Control+Shift+u
#\x1b[27;6;86~ = Control+Shift+v
\x1b[27;6;87~ = Control+Shift+w
\x1b[27;6;88~ = Control+Shift+x
#\x1b[27;6;89~ = Control+Shift+y
\x1b[27;6;90~ = Control+Shift+z

This of course would go into your foot.ini.

ZSH bindings conflicting with terminal bindings

I actually don't recommend using the foot terminal for this. Instead I recommend that you use the Kitty terminal because of an issue there is with conflicting keybinding.

The best meathod for shift selection in zsh would be to have it integrate with the terminal so that the terminal can preform actions on that selected text. Unfortunately, I do not know how to do this. So the work around is to have ZSH preform actions on the selected text instead. To copy selected text in a terminal emulator, the convention is to use Ctrl + Shift + c. So for consintency, I have that as the copy to clipboard bind in the shift selection implementation. The issue is that the keybinding conflicts with the terminal binding, so the escape code is not sent because the terminal binding supercedes the ZSH binding. Furthurmore, your terminal emulator will probably give you an error if you try to bind to text and another action. At least this is my experience in foot.

In other terminal emulators it is possible that you may be able to bind to text and another action such as copying selected text. If this was true, then it might be possible to set up Ctrl + Shift + c to copy selected text in both the terminal and in ZSH. I am not sure which one gets priority because ZSH selection is seperate from terminal selection, meaning that you can select text in both ZSH the terminal at the same time. Nevertheless that is a possible solution. I am not sure which terminals have or don't have the ability to bind to an escape code and another action with the same bind.

The solution that brings me to recommending Kitty as the terminal to use with this implementation is that Kitty has the ability to change a setting for an instance at runtime. That means that you could easily make it so that the keybinding for copying text in the terminal is turned off when text is selected in ZSH, and the escape code binding is enabled. Then make it so that when text is deselected, the terminal copy binding is turned back on, and the escape code binding is turned back off. This would make shift selection function almost as the selection was properly integrated with the terminal.

To run a command when text is selected, and run a command when text is deselected, you must edit the following chunks of code:

function widget::util-select() {
    ((REGION_ACTIVE)) || zle set-mark-command
    local widget_name=$1
    shift
    zle $widget_name -- $@
    zle copy-region-as-kill
    printf "%s" $CUTBUFFER | wl-copy --primary
}

function widget::util-unselect() {
    REGION_ACTIVE=0
    local widget_name=$1
    shift
    zle $widget_name -- $@
}

function widget::util-delselect() {
    if ((REGION_ACTIVE)) then
        zle kill-region
    else
        local widget_name=$1
        shift
        zle $widget_name -- $@
    fi
}

Ctrl + Shift + c is not the only binding that is effected by this. Any binding set by your terminal can conflict.

Copying selected text

Another thing that my setup does differently is use wl-copy (often provided by the package wl-clipboard) or xclip instead of pbcopy. pbcopy is the MacOS command for copying text. wl-copy is the Linux Wayland equivilent. xclip is the Linux Xorg equivilant. If you are using Xorg replace wl-copy with xclip. I might later implement something that does this replacement automatically based on the running display server protocol. I have implemented something that will use xclip or wl-copy based on the running display server protocol.

This implementation also copies selected text to the primary clipboard as all selection should. This is done with wl-copy but can also be done and with xclip.

Conclusion

Issues

There are three main issues that I have yet to solve in this implementation of ZSH shift selection:

  • Keybindings conflicting with terminal keybindings. I plan on setting this up with Kitty, and editing this answer.
  • wl-copy being Wayland-specific and not portable to Xorg. I plan on implementing xclip commands and having the used copy command be based on the running display server protocol.
  • I haven't mentiond this yet, but this implementation breaks Ctrl + r history search even with a plugin like H-S-MW. I honestly don't know how to fix this, but I suspect that it would be similar to fixing autosuggestions.

What this implementation does differently

Some things that this implementation does differently:

  • Different keybindings
    • The keybindings in this implementation are optimized to be used with Linux systems rather than a MacOS system that uses the Cmd key.
  • Different escape codes
    • Once again, the implementation by @8c6b5df0d16ade6c is optimized for MacOS, and the escape codes are optimized for iTerm.
  • Some other quality of life keybindings
    • The top part of the code block is from my config. This includes most of the keybindings that I use outside of the ones that I use for shift selection. And those are the bindings for moving one word at a time and deleting one word at a time.
  • Diffirent clipboard commands (Support for Xorg and Wayland
  • Copying selected text to primary clipboard

Dependencies

Currently, Wayland because I have not yet added support for xclip, though it should be very easy to do and Xorg. I just added support for xclip.

xclip or wl-copy.

Also, you need to set custom escape sequences in your terminal just like I did in my Foot example.

Resolving keybinding conflictions by changing settings at runtime or possibly setting a keybind to an action and an escape code both at the same time.

Anyways

If you want to make this better, please comment on how you might go about fixing the Ctrl + r issue. You could also comment and tell me how shift selection might be properly integrated into the terminal, where basically the ZSH bindings would control the actual terminal selection instead of zle.

Monition answered 4/5 at 20:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.