如何在Windows的Bash中的vim中实现“复制到剪贴板”功能?

36

我曾经使用"+y来复制文本到系统剪切板,但在Bash on Windows的vim中无法使用。


1
你不能这样做。如果你想在Windows上使用剪贴板支持,请使用GVim。 - romainl
在 Git Bash 中,"* 寄存器对于 vim 来说非常有用。例如,要复制当前行:"*yy(然后按 Ctrl-V 粘贴到你的浏览器 URL 字段中)。或者反过来:(例如,在复制了一个 URL 后),将其粘贴到当前的 vim 文件中:"*p - Ralph
1
我不明白为什么这个问题被关闭了。当然,vim是一个主要由程序员使用的软件工具。它是因为这个问题与Windows有关吗? - Cornelius Roemer
11个回答

33

既不能使用 "*y 也不能使用 "+y (...),可以考虑以下替代方法:

将以下内容添加到你的 .vimrc 文件中,并创建一个名为 ~/.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>

使用可视或者可视块选择文本,按下Ctrl-c复制文本。

然后使用通常的方法将所复制的文本粘贴到终端外部,或者在正常或可视模式下通过Ctrl-v将所复制的文本粘贴到任何vim面板中。

Ctrl-c命令将所选文本转存到~/.vimbuffer文件中,然后运行一个UNIX命令将数据从~/.vimbuffer文件输出到clip.exe程序中剪切板上。

如有进一步改进(例如:使命令无声),欢迎提出建议。

编辑: 现在该命令可以复制任意长度的字符串,而不仅仅是整行。 旧命令:

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

编辑2: @Tropilio在下面的评论区提出了一种更简洁的方法,使用系统事件。我已经使用这种方法一年了。


由于某些原因,它对我不起作用。它在选择的开头插入了两个字符 :|(冒号和竖杠字符),并且没有将内容复制到Windows剪贴板中。当.vimbuffer中有内容时,我在vim中输入 :!cat ~/.vimbuffer \| clip.exe <cr>,会出现以下错误:cat: '|' : no such file or directorycat: clip.exe: No such file or directory - user1135541
这可能是因为您正在vim内使用该命令。如果您只想将“~/.vimbuffer”的内容复制到剪贴板中,请使用 :!cat ~/.vimbuffer | clip.exe <cr> - 您不必从vim中转义|字符(使用\),只需在.vimrc文件中转义。如果仍然无法工作,请告诉我。 - davidanderle
2
我可以确认这在最新的Windows 10上的WSL上运行正常。Microsoft Windows [版本10.0.17134.286] - Padge
谢谢,这个可行!但是出现了一个问题,每当我使用 <C-c> 命令时,屏幕会闪烁几次... - Tropilio
8
我现在采用不同的方法,或许对你也有用。我使用一个自动命令,每次当我复制文本时就会自动触发它: autocmd TextYankPost * call system('echo '.shellescape(join(v:event.regcontents, "\<CR>")).' | clip.exe') 这不会引起任何闪烁或延迟。 - Tropilio
显示剩余2条评论

28

在Windows 10的Bash中,从vim中复制到剪贴板的方法是:按住Shift键并拖动光标选择文本,然后按enter

在按enter之前,选择应该看起来像这样:

在按 ENTER 键之前,选择的外观应如图所示

注意:此方法仅适用于一次屏幕内可容纳的文本内容;无法用于跨越多屏的文本块的复制。


2
这将复制控制台中的文本,而不是vim本身。 - Ignacio Vazquez-Abrams
正确,但我不知道有没有其他方法。(如果您想使用捆绑在WSL中的标准vim) - ifconfig
1
我的(非Windows)系统上的vim帮助声称“*”是正确的寄存器使用方法。 - Ignacio Vazquez-Abrams
1
没错,但需要以不同的方式安装vim,使用vim-gnome而不是仅仅的vim - ifconfig
我猜他们决定让在WSL下运行的vim根本不需要与Windows集成。 - Ignacio Vazquez-Abrams

12

因此,即使是跨网络的问题(如Super User上的这个问题Vi and Vim上的这个问题),都有很多答案。不幸的是,我对它们都不满意,所以来了一个不同的答案。

Neovim

如果我们使用Neovim,那很容易解决。我们可以按照他们在如何从WSL使用Windows剪贴板的FAQ中的说明进行设置。本节其余内容仅为背景信息。

通过设置set clipboard=unnamedplus,就好像我们在X11下的普通Linux系统一样,我们告诉Neovim在拷贝和粘贴时使用系统寄存器作为默认剪贴板。根据Neovim运行的位置,它选择适当的提供程序与这些系统寄存器交互。对于v0.4.3版本,如何选择提供程序可以在provider#clipboard#Executable找到。

虽然这个解决方案需要安装一个外部二进制文件win32yank.exe,但我个人对它非常满意,因为与之相比,clip.exepowershell.exe [Set|Get]-Clipboard在用户体验上都不如它。例如,clip.exe只支持复制,但无法解决粘贴问题。而虽然Powershell的Get-Clipboard命令允许我们从Windows剪贴板中粘贴到Vim中,但它会保留Windows的换行符,因此在粘贴后到处都是讨厌的^M。另外,它是一个超小型的二进制文件,只需在我们的WSL distro中安装即可,不需要在Windows端进行任何操作。

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

如果我们只是使用普通的Vim,我们仍然可以使用win32yank.exe,但我们必须手动实现Neovim通常为我们执行的操作。也就是说,执行以下步骤:

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

首先将剪贴板设置为默认寄存器(@"),因为如果像我的Ubuntu WSL发行版一样,Vim没有编译剪贴板支持,则系统寄存器可能甚至未启用。复制使用自动命令将默认寄存器传递给win32yank,并且粘贴拦截粘贴操作以将系统剪贴板分配给默认寄存器。

这个方法是有效的,但在快速执行多个连续的复制和粘贴时会明显地出现抖动,因为我们每次都要运行一个外部程序。我们可以通过去抖动来改进复制:

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

但这需要 Vim 8,如果您必须升级,我建议直接选择 Neovim。尝试实现适当的缓存和去抖动会很麻烦!


2
适用于WSL2。这是最好的、最简单的答案,至少对于neovim用户来说。 - Siddharth Pant
1
mv /tmp/win32yank.exe ~/bin 将 .exe 重命名为 bin 并将其放置在 ~ 中。您确定不是指 mv /tmp/win32yank.exe /usr/local/bin/win32yank.exe(适用于 Ubuntu)吗? - run_the_race
“Vim” 部分对我来说可行,使用了 “vim-nox”。这是我尝试的第四个解决方案。(我还没有烦恼去抖动) - Matt Parrilla
请注意,是 p/P 命令将剪贴板内容读入寄存器。这意味着在使用这两个命令之前,您不能直接访问寄存器。特别是,i<C-R>" 不会立即起作用。这也改变了 xp 的行为方式,使其成为就地操作。 - mkjeldsen
1
@71GA,请看一下我在SuperUser上的回答https://superuser.com/questions/1291425/windows-subsystem-linux-make-vim-use-the-clipboard/1557751#1557751。如果复制不起作用,也可以尝试输入win32yank.exe的完整Unix路径。 - AntonOfTheWoods
显示剩余3条评论

7
如果您不想设置X服务器,可以使用随Windows一起提供的clip.exe程序将所选文本复制到剪贴板中。以下代码片段可放置在您的.vimrc文件中。首先创建一个助手函数,它将返回当前选择的文本作为字符串。我们可以使用此函数将突出显示的文本导入clip.exe
func! GetSelectedText()
    normal gv"xy
    let result = getreg("x")
    return result
endfunc
system函数允许我们调用本地程序并将参数传递给它们。以下代码片段设置了两个关键映射;第一行将允许使用Ctrl+C复制文本,第二行将允许使用Ctrl+X剪切文本。
noremap <C-C> :call system('clip.exe', GetSelectedText())<CR>
noremap <C-X> :call system('clip.exe', GetSelectedText())<CR>gvx

在保存对.vimrc文件的更改后,您就可以开始使用了。

如果您打算在多个系统上使用.vimrc,我建议将这些映射语句包装在一个if语句中,以检查clip.exe是否存在:

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

上述方法还可以让Vim在直接访问系统剪贴板时忽略这些按键绑定。
如果Vim可以访问剪贴板,您可能还想添加相应的按键绑定,如下所示:
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

如果您在Windows本地使用Vim,或者在Linux系统上使用vim-gtk,这将非常有用。

1
我认为这是比已接受的更好的解决方案,因为不需要缓冲文件。感谢分享! - rococo

7
如果您只关心复制完整行,可以使用Vim将文件内容传输到外部程序的能力。因此,您可以执行以下操作:
:w !clip.exe 以将其传输到"clip.exe"中。如果您需要特定的行,比如第2行到第10行,只需执行以下操作:
:2,10w !clip.exe

请参见在Vim中将缓冲区传输到外部命令

5
将以下内容添加到 .vimrc 文件中:
autocmd TextYankPost * if v:event.operator ==# 'y' | call system('/mnt/c/Windows/System32/clip.exe', @0) | endif

为方便起见,如果需要,您可以通过ctrl + c进行复制。

vmap <C-c> y

0

如果您想使用WSL中捆绑的vim,那么您只能使用vim本身提供的剪贴板,这意味着您只能在vim内部复制和粘贴。

或者,您可以尝试以下方法:

  1. 安装VcXsrv并运行它。
  2. 在您的WSL Bash中输入export DISPLAY=localhost:0
  3. 运行vim-gtk或vim-gnome(或任何其他GUI应用程序),并尝试在Linux和Windows之间进行剪贴板操作。

根据WSL GitHub问题,这应该可以工作。此外,请参见此处:https://github.com/Microsoft/WSL/issues/892#issuecomment-275873108 - dwettstein

0

结合上述信息,可以在不使用任何键绑定的情况下从任何寄存器复制简单文本到剪贴板(在某些情况下可能更方便)。例如,如果您想将最后一次复制的文本复制到Windows剪贴板中,请输入以下命令(开始输入):

:call system('clip.exe', '

现在按下Ctrl+r,然后再按一个寄存器的+键。它会从指定的寄存器中粘贴文本。接下来,关闭单引号和括号。命令将如下所示:
:call system('clip.exe', 'a text from 0 register')

按下 Enter,完成!


0
vmap <silent> <leader>y :w !clip.exe<CR><CR>
"---------------------- :w !clip.exe
" The core command, thanks to Mitchell's answer.
"---------------------------------- <CR><CR>
" The first <CR> closes the command;
" the second <CR> suppresses "Press ENTER or type command to continue".
"--- :h map-silent
" This fix the flicker caused by external commands and <CR><CR>.
"------------ :h leader
" `:echo mapleader` to view your leader key.

站在巨人的肩膀上,我现在正在使用上述解决方案。

0
以下是映射:
  • 在正常模式下,cycyycY (=剪贴板复制)分别将给定的文本对象、行、光标位置到行尾的剩余部分复制到剪贴板中,
  • 在可视模式下,Y 将所选内容复制到剪贴板中,
与是否在 WSL 中启动 Vim 无关。
nnoremap                    cy                   "+y
nnoremap                    <sid>(mgu)   m`_
onoremap                    <sid>(gu)        g_
nnoremap                    <sid>(ggg)   g``
nmap         <expr>     cyy                  '<sid>(mgu)cy' . v:count1 . '<sid>(gu)' . '<sid>(ggg)'
nmap                            cY                   cy<sid>(gu)

xnoremap Y  "+y

if has('unix') && executable('clip.exe')
    " From :help map-operator
    function! SendToClip(type, ...) abort
        if a:0
            " Visual mode
            normal! gv"+y
        elseif a:type ==# 'line'
            normal! '[V']"+y
        elseif a:type ==# 'char'
            normal! `[v`]"+y
        endif

        call system('clip.exe', @+)
    endfunction

    nnoremap <silent> cy            :set operatorfunc=SendToClip<cr>g@
    xnoremap <silent> Y             :<C-U>call SendToClip(visualmode(),1)<cr>
endif

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接