Bash vi-mode: How to Configure Buffer for Yank and Paste

bashtmuxvi-mode

tldr:
Does anyone know how to configure what buffer is used by vi-mode bash for yanking (copying) and pasting?

Long version:
I have set editing-mode vi in my .inputrc, so that programs using the readline library, namely bash, use vi-like key bindings. Unrelated to this, I have set up both vim and tmux to use the system clipboard for yanking and pasting. I would like to do the same for bash. This might seem unnecessary since I will mostly use bash via tmux, but even then it would be nice to be able to use p (in normal mode) to copy from clipboard instead of C-a P, or something like that (with a tmux prefix). However, I cannot find any info about how to configure this aspect of bash, or even what buffer is used by bash by default for yank and paste. I do not see it when I execute :registers in vim, so it does not seem to be any of the registers that vim sees.

Best Answer

After doing a bit of research it seems like bash uses an internal variable for this, and not any system buffer that is readily available. It is reffered to as the "kill ring" in the manual entries for bash and readline, and the implementation can be read on GitHub, and other places. It might be possible to hijack this mechanism to use the system clipboard instead, but that seems to be a bit too involved for me to tackle at the moment.

I instead settled for the simple workaround below, using the bash builtin bind command, documented in the manual pages for bash (search for bind \[). It covers my usecase pretty well, but it does not cover more advanced killing and yanking with vim motions. Please tell me if you see something that is terrible with my solution, since I am not in the habit of writing bash scripts.

In ~/.bashrc:

# Macros to enable yanking, killing and putting to and from the system clipboard in vi-mode. Only supports yanking and killing the whole line.
paste_from_clipboard () {
  local shift=$1

  local head=${READLINE_LINE:0:READLINE_POINT+shift}
  local tail=${READLINE_LINE:READLINE_POINT+shift}

  local paste=$(xclip -out -selection clipboard)
  local paste_len=${#paste}

  READLINE_LINE=${head}${paste}${tail}
  # Place caret before last char of paste (as in vi)
  let READLINE_POINT+=$paste_len+$shift-1
}

yank_line_to_clipboard () {
  echo $READLINE_LINE | xclip -in -selection clipboard
}

kill_line_to_clipboard () {
  yank_line_to_clipboard
  READLINE_LINE=""
}

bind -m vi-command -x '"P": paste_from_clipboard 0'
bind -m vi-command -x '"p": paste_from_clipboard 1'
bind -m vi-command -x '"yy": yank_line_to_clipboard'
bind -m vi-command -x '"dd": kill_line_to_clipboard'

Edit1: The bindings "yy" and "dd" that use two consecutive keypresses are affected by the keyseq-timeout readline setting. The default value is 500 ms, meaning you will have to type the second character within 500 ms of the first. So if you have set keyseq-timeout to a much lower value, you might have some trouble.

Edit2: Updated paste to more precisely emulate vim behaviour.