8

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.

1 Answer 1

10

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.

3
  • 2
    This is actually very cool! Nice to see a new user making efforts and actually writing a nice answer. However, I tried your solution and the yank and kill failed to copy the line into clipboard, although the paste works... I wonder why.
    – Quasímodo
    Commented Jun 30, 2020 at 12:30
  • 2
    Thanks! It seems like the bindings that use two consecutive keypresses are affected by the readline setting keyseq-timeout. So if you have set keyseq-timeout 0 or just a low value, you will have trouble with those. Default is set keyseq-timeout 500, meaning 500 ms.
    – ummg
    Commented Jun 30, 2020 at 12:45
  • 2
    That is it, I have 50 ms. After increasing the timeout, it worked. Outstanding, I wish I could upvote twice.
    – Quasímodo
    Commented Jun 30, 2020 at 12:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .