How to "copy to clipboard" in vim of Bash on Windows?

20,118

Solution 1

Since neither "*y nor "+y (...) work, an alternative is the following:

Add this to your .vimrc and create a file called ~/.vimbuffer

" copy (write) highlighted text to .vimbuffer
vmap <C-c> y:new ~/.vimbuffer<CR>VGp:x<CR> \| :!cat ~/.vimbuffer \| clip.exe <CR><CR>
" paste from buffer
map <C-v> :r ~/.vimbuffer<CR>

Higlight any text using visual or visual-block and press ctrl-c. Paste copied text outside your terminal using the usual method or paste copied text to any vim pane using ctrl-v in normal or visual mode.

Ctrl-c yanks the selected text, overwrites ~/.vimbuffer with the selected text, runs a UNIX command to pipe out the data from ~/.vimbuffer to clip.exe.

Any further improvement (e.g.: making command silent) is much appreciated!

Edit: command can now copy strings with any length, not just whole lines. Old command:

vmap <C-c> :w! ~/.vimbuffer \| !cat ~/.vimbuffer \| clip.exe <CR><CR>

Edit2: @Tropilio below has a much cleaner approach down in the comments using system events. I've been using that for a year now.

Solution 2

To copy to the clipboard from within vim in Bash on Windows 10, hold Shift and drag the cursor over text. then press enter

A selection should look like this before pressing enter:

Selection should look like this before pressing ENTER

EDIT: This only works with text that fits on the screen all at once; it cannot be used to copy blocks of text that span many 'screens'

Solution 3

So there's a lot of answers, even on cross-network questions like this one on Super User, and this one on Vi and Vim, and unfortunately I'm not satisfied with any of them, so here's a different answer.

Neovim

If we're using Neovim, it's easy. We can follow their FAQ under How to use the Windows clipboard from WSL. The rest of this section is just context.

By setting set clipboard=unnamedplus as if we were on a regular Linux system under X11, we tell Neovim to use the system registers as our default clipboard when yanking and pasting. Based on where Neovim is running, it chooses an appropriate provider to interact with these system registers. For v0.4.3, how that provider is chosen can be found at provider#clipboard#Executable.

While this solution requires installing an external binary win32yank.exe, I'm personally very satisfied with it as both clip.exe and powershell.exe [Set|Get]-Clipboard pale in user experience to it. For example, clip.exe only supports copying, but fails to address the pasting issue. And while the Powershell Get-Clipboard cmdlet allows for us to paste into Vim from our Windows clipboard, it'll retain Windows line-endings, so there will be nasty ^Ms everywhere after pasting. In addition, it's a super small binary and only has to be installed within our WSL distro; nothing on the Windows side.

curl -sLo /tmp/win32yank.zip https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x64.zip
unzip -p /tmp/win32yank.zip win32yank.exe > /tmp/win32yank.exe
chmod +x /tmp/win32yank.exe
sudo mv /tmp/win32yank.exe /usr/local/bin/

Vim

If we're just using regular Vim, we can still use win32yank.exe, but we have to manually implement what Neovim would normally do for us. That is, the following:

set clipboard=unnamed

autocmd TextYankPost * call system('win32yank.exe -i --crlf', @")

function! Paste(mode)
    let @" = system('win32yank.exe -o --lf')
    return a:mode
endfunction

map <expr> p Paste('p')
map <expr> P Paste('P')

First set the clipboard to the default register (@") as the system registers might not even be enabled if Vim was compiled without clipboard support like on my default Ubuntu WSL distro. Copies use an autocmd to pass the default register to win32yank, and pastes intercept the paste to assign the system clipboard to the default register.

This works but it's noticeably jittery if performing several successive yanks and pastes quickly as we have to shell out every time. We can improve the yank by debouncing it:

autocmd TextYankPost * call YankDebounced()

function! Yank(timer)
    call system('win32yank.exe -i --crlf', @")
    redraw!
endfunction

let g:yank_debounce_time_ms = 500
let g:yank_debounce_timer_id = -1

function! YankDebounced()
    let l:now = localtime()
    call timer_stop(g:yank_debounce_timer_id)
    let g:yank_debounce_timer_id = timer_start(g:yank_debounce_time_ms, 'Yank')
endfunction

But this will require Vim 8, and if you have to upgrade, I'd recommend to just go with Neovim instead. Trying to implement proper caching and debouncing would be a headache!

Solution 4

If you don't want to set up an X server, this method will allow you to copy selected text to your clipboard using the clip.exe program which comes shipped with Windows.

The code snippets below can be placed in your .vimrc file.

First create a helper function which will return the currently selected text as a string. We can use this function to pipe highlighted text into clip.exe:

func! GetSelectedText()
    normal gv"xy
    let result = getreg("x")
    return result
endfunc

The system function will allow us to call native programs and pipe parameters into them. The snippet below sets up two key mappings; the first line will allow Ctrl+C to be used for copying text and the second will allow Ctrl+X for cutting text:

noremap <C-C> :call system('clip.exe', GetSelectedText())<CR>
noremap <C-X> :call system('clip.exe', GetSelectedText())<CR>gvx

After saving the changes to your .vimrc file you should be good to go.

If you are going to be using your .vimrc on multiple systems, I suggest wrapping these mappings in an if statement to check if clip.exe actually exists:

if !has("clipboard") && executable("clip.exe")
    noremap <C-C> :call system('clip.exe', GetSelectedText())<CR>
    noremap <C-X> :call system('clip.exe', GetSelectedText())<CR>gvx
endif

The above will also allow Vim to ignore those key bindings if it has direct access to the system clipboard.

You might also want to add keybindings for when vim does have access to the clipboard, like so:

if has("clipboard")
    vnoremap <C-X> "+x
    vnoremap <S-Del> "+x

    vnoremap <C-C> "+y
    vnoremap <C-Insert> "+y

    map <C-V>       "+gP
    map <S-Insert>      "+gP

    cmap <C-V>      <C-R>+
    cmap <S-Insert>     <C-R>+
endif

Which will come in handy if you are using Vim natively on Windows or are using vim-gtk on a Linux system.

Solution 5

If you only care about copying complete lines, you can use Vim's ability to pipe the file contents to an external program. So you can do

:w !clip.exe

to pipe into clip.exe. If you need specific lines, say 2 through 10, you can just do

:2,10w !clip.exe

See Piping buffer to external command in Vim

Share:
20,118
nbystndr
Author by

nbystndr

Talk is cheap. Show me the code.

Updated on November 06, 2021

Comments

  • nbystndr
    nbystndr over 2 years

    I used to use "+y to copy text to system's clipboard, but it doesn't work in vim of Bash on Windows.