Skip to content
Scroll to top↑

Clipboard

在Neovim、Tmux和系统剪贴板之间复制粘贴,考虑Mac、Linux及Linux下X11和Wayland的差异(借助pbcopyxclipwl-copy)。Nvim 的Clipboard Provider其实内置有对它们的支持,但是没搞明白怎么同时支持系统剪贴板和tmux,索性笨办法自定义一下:

Neovim

lua
-- clipboard
local function setup_clipboard()
  local has_mac = vim.fn.has('mac') == 1
  local has_wsl = vim.fn.has('wsl') == 1 or vim.fn.executable('clip.exe') == 1
  local has_x11 = vim.env.DISPLAY ~= nil and vim.fn.executable('xclip') == 1
  local has_wayland = vim.env.WAYLAND_DISPLAY ~= nil and vim.fn.executable('wl-copy') == 1
  local has_tmux = vim.env.TMUX ~= nil

  -- GUI 环境:有系统剪贴板工具
  local has_gui_clipboard = has_mac or has_wsl or has_x11 or has_wayland

  -- 情况1:GUI 环境 - 系统剪贴板/tmux/nvim 互通
  if has_gui_clipboard then
    vim.opt.clipboard = 'unnamedplus'
    vim.g.clipboard = {
      name = 'custom',
      copy = {
        ['+'] = function(lines)
          local text = table.concat(lines, '\n')
          -- 复制到系统剪贴板
          if has_mac then
            vim.fn.system('pbcopy', text)
          elseif has_wayland then
            vim.fn.system('wl-copy', text)
          elseif has_x11 then
            vim.fn.system('xclip -i -sel c', text)
          elseif has_wsl then
            vim.fn.system('clip.exe', text)
          end
          -- 同步到 tmux(如果有)
          if has_tmux then vim.fn.system('tmux set-buffer -- ' .. vim.fn.shellescape(text)) end
        end,
        ['*'] = function(lines) vim.g.clipboard.copy['+'](lines) end,
      },
      paste = {
        ['+'] = function()
          local text = ''
          if has_mac then
            text = vim.fn.system('reattach-to-user-namespace pbpaste')
          elseif has_wayland then
            text = vim.fn.system('wl-paste --no-newline')
          elseif has_x11 then
            text = vim.fn.system('xclip -o -sel c')
          elseif has_wsl then
            text = vim.fn.system('powershell.exe -NoProfile -Command "Get-Clipboard"')
            text = text:gsub('\r\n', '\n'):gsub('\r', '')
          end
          return vim.split(text, '\n')
        end,
        ['*'] = function() return vim.g.clipboard.paste['+']() end,
      },
      cache_enabled = 0,
    }
  -- 情况2:终端无 GUI + tmux - tmux/nvim 互通
  elseif has_tmux then
    vim.opt.clipboard = 'unnamedplus'
    vim.g.clipboard = {
      name = 'tmux',
      copy = {
        ['+'] = function(lines)
          local text = table.concat(lines, '\n')
          vim.fn.system('tmux set-buffer -- ' .. vim.fn.shellescape(text))
        end,
        ['*'] = function(lines) vim.g.clipboard.copy['+'](lines) end,
      },
      paste = {
        ['+'] = function()
          local text = vim.fn.system('tmux show-buffer')
          return vim.split(text, '\n')
        end,
        ['*'] = function() return vim.g.clipboard.paste['+']() end,
      },
      cache_enabled = 0,
    }
    -- 情况3:无任何外部工具
    -- 不设置 clipboard,让 nvim 使用默认行为(内部寄存器正常工作)
  end
end

setup_clipboard()

为了体验,可以在复制之后添加以短暂的高亮:

lua
autocmd('TextYankPost', {
  pattern = '*',
  callback = function()
    -- highlight yanked text for 700ms
    vim.highlight.on_yank({ higroup = 'IncSearch', timeout = 700 })
  end,
})

Tmux

bash
# 绑定v键为进入复制模式
bind v copy-mode
bind-key -T copy-mode-vi v send-keys -X begin-selection
unbind -T copy-mode-vi Enter
# y键复制
bind -T copy-mode-vi y send-keys -X copy-selection
# P键粘贴
bind P run "tmux paste-buffer"

if-shell -b '[ "$(uname -s)" == "Darwin" ]' {
  bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"
  bind P run "tmux set-buffer \"\$(reattach-to-user-namespace pbpaste)\"; tmux paste-buffer"
}

if-shell -b '[ "$(uname -s)" == "Linux" ]' {
  if-shell -b '[ "$(echo $XDG_SESSION_TYPE)" == "wayland" ]' {
    bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel 'wl-copy'
    bind P run "tmux set-buffer \"\$(wl-paste)\"; tmux paste-buffer"
  }

  if-shell -b  '[ "$(echo $XDG_SESSION_TYPE)" == "x11" ]' {
    bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel 'xclip -i -sel c'
    bind P run "tmux set-buffer \"\$(xclip -o -sel c)\"; tmux paste-buffer"
  }
}

# WSL clipboard support
if-shell -b 'command -v powershell.exe' {
  bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel 'clip.exe'
  bind P run "tmux set-buffer \"\$(powershell.exe -Command Get-Clipboard)\"; tmux paste-buffer"
}