Neovim:中文输入、远程剪贴板与窗口协作

本页目录

中文编辑与远程工作涉及多个进程:桌面输入法、终端、复用器和 Neovim。某个动作在本地有效,经过 SSH 或 Zellij 后失效,通常需要沿着这条路径逐层检查。

普通模式与输入法

普通模式中的 j 是移动命令。若输入法仍在组合中文,按键可能先进入候选框,Neovim 收不到原本的字符。因此,模式切换时恢复拉丁输入法可以减少误操作。

在使用 Fcitx5 的 Linux 桌面上,先单独查询当前输入法:

fcitx5-remote -n

确认 keyboard-us 已加入可用输入法后,手动测试:

fcitx5-remote -s keyboard-us

命令行切换正常后,再在 lua/plugins/input.lua 配置插件:

return {
  {
    "keaising/im-select.nvim",
    event = "VeryLazy",
    opts = {
      default_im_select = "keyboard-us",
      default_command = "fcitx5-remote",
      set_previous_events = { "InsertEnter" },
    },
  },
}

退出插入模式时检查是否恢复键盘输入,再进入插入模式检查是否恢复之前的输入法。macOS、Windows 和 IBus 的命令不同,不能复制这份 Fcitx5 配置后期待跨平台自动成立。im-select.nvim

SSH 远端运行的 Neovim 通常无法直接控制本地桌面的 Fcitx5。远端安装同名命令不会让它跨越 SSH 自动操作本地会话;此时可使用本地终端/输入法的切换策略。

剪贴板与寄存器

Neovim 的内部寄存器不依赖桌面。"+y 则请求通过 clipboard provider 把内容交给系统剪贴板。Wayland 本地常用 wl-copy / wl-paste,SSH 场景可以让 OSC52 序列沿终端连接返回本地终端。

先查看 :checkhealth vim.provider,再分别测试普通 y 和 "+y。只有设置了 clipboard=unnamedplus 等选项,普通复制才会自动连接到相应剪贴板寄存器。

OSC52 的复制与读取是两种能力。终端允许写剪贴板,不代表允许读取;多路复用器也可能影响转发和自动检测。Neovim clipboard

只发送 OSC52,粘贴保留本次复制缓存

下面放入 lua/config/options.lua,适用于提供 vim.ui.clipboard.osc52 的 Neovim 版本。它把复制发送到终端,读取时只返回本 Neovim 进程最近复制的内容:

local osc52 = require("vim.ui.clipboard.osc52")
local clipboard_cache = { { "" }, "v" }

local function copy_to(register)
  local send = osc52.copy(register)
  return function(lines, regtype)
    clipboard_cache = { vim.deepcopy(lines), regtype }
    send(lines, regtype)
  end
end

local function paste_cached()
  return vim.deepcopy(clipboard_cache)
end

vim.g.clipboard = {
  name = "OSC52 with local cache",
  copy = { ["+"] = copy_to("+"), ["*"] = copy_to("*") },
  paste = { ["+"] = paste_cached, ["*"] = paste_cached },
}
vim.opt.clipboard = "unnamedplus"

这是有意选择的行为:从浏览器复制的新内容不会自动出现在这个缓存中。外部内容可通过本地终端的粘贴动作送进插入模式;如果需要 "+p 真正读取系统剪贴板,应使用支持读取的 provider 并验证终端权限。

验证顺序是:本地无复用器 → 本地 Zellij → SSH 无复用器 → SSH 内 Zellij。每次复制一段不同的标记文字,检查本地桌面得到哪一段;这样可以定位在哪一层丢失。

Neovim 一侧的方向导航

在 lua/plugins/navigation.lua 中加入:

return {
  {
    "mrjones2014/smart-splits.nvim",
    opts = { at_edge = "stop", zellij_move_focus_or_tab = false },
    keys = {
      { "<C-h>", function() require("smart-splits").move_cursor_left() end, desc = "Move left" },
      { "<C-j>", function() require("smart-splits").move_cursor_down() end, desc = "Move down" },
      { "<C-k>", function() require("smart-splits").move_cursor_up() end, desc = "Move up" },
      { "<C-l>", function() require("smart-splits").move_cursor_right() end, desc = "Move right" },
    },
  },
}

这里选择到整个可移动区域边缘时停止,且不跨 Zellij 标签。先在两个 Neovim 窗口之间测试,再验证越过编辑器边界的动作。smart-splits

Zellij 一侧的配合

从 vim-zellij-navigator 发布页取得兼容版本的 WASM 插件,保存在自己的插件目录。下面四条绑定加入现有 keybinds 下的 locked 模式;将四处 /absolute/path/ 改成实际绝对路径:

locked {
    bind "Ctrl h" {
        MessagePlugin "file:/absolute/path/vim-zellij-navigator.wasm" {
            name "move_focus"; payload "left"; move_mod "ctrl";
        }
    }
    bind "Ctrl j" {
        MessagePlugin "file:/absolute/path/vim-zellij-navigator.wasm" {
            name "move_focus"; payload "down"; move_mod "ctrl";
        }
    }
    bind "Ctrl k" {
        MessagePlugin "file:/absolute/path/vim-zellij-navigator.wasm" {
            name "move_focus"; payload "up"; move_mod "ctrl";
        }
    }
    bind "Ctrl l" {
        MessagePlugin "file:/absolute/path/vim-zellij-navigator.wasm" {
            name "move_focus"; payload "right"; move_mod "ctrl";
        }
    }
}

这是 keybinds 内部的片段,应与已有 Ctrl+g 模式切换合并,不应覆盖整个文件。插件首次请求权限时,核对插件来源与用途。若使用其他常驻模式,将绑定放到实际使用的模式,避免 locked 模式下的键位根本没有生效。

在左侧面板打开有两个窗口的 Neovim,右侧放普通 Shell。依次验证编辑器内向右、越过边界向右、从 Shell 向左返回,以及最外侧无目标时停止。若失败,先看 :verbose nmap <C-l>,再查 Zellij 模式、插件加载和终端映射。

旧环境的内部函数覆盖属于版本相关补丁。新配置先使用公开选项,只有能复现具体缺陷时才考虑临时适配,并记录移除条件。