Neovim の入力法、遠隔クリップボード、ペイン移動

4 分で読了
このページの目次

多言語編集と遠隔作業では、入力法、端末、復用器、Neovim が連携する。SSH や Zellij を通すと失敗する場合、この経路を一層ずつ比較する。

普通モードと入力法

普通モードの j は移動命令だが、入力法の変換中には候補処理に取られる場合がある。挿入モードを抜ける際にラテン入力へ戻すと競合を減らせる。

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" },
    },
  },
}

挿入終了時の切り替えと再挿入時の復元を確認する。Windows、macOS、IBus は別コマンドとなる。im-select.nvim

SSH 先の Neovim は通常ローカルの Fcitx5 を直接制御できない。接続先へのコマンド導入だけでは転送されないため、ローカルの端末や入力法の方針を使う。

レジスタとクリップボード

内部レジスタにはデスクトップが不要だが、"+y は provider を使う。Wayland では wl-clipboard、遠隔では OSC52 で端末接続を通してコピーを戻せる場合がある。

:checkhealth vim.provider を見て通常の y と "+y を別に試す。clipboard=unnamedplus などを設定して初めて通常コピーもシステム側へ接続される。

OSC52 の書き込みと読み取りは別能力で、端末の許可や復用器の転送・検出にも依存する。clipboard provider

OSC52 送信とプロセス内キャッシュ

vim.ui.clipboard.osc52 を提供する版の lua/config/options.lua に置く。コピーは外へ送り、読み取りはこのプロセスで直近にコピーした内容だけを返す。

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"

ブラウザで新しくコピーした内容はこのキャッシュへ自動で入らない。外部内容は挿入モードでローカル端末の貼り付けを使うか、実際の読み取りが可能な 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 の releaseから対応 WASM を取得し、既存 keybinds 内の locked へ統合する。四つの絶対パスを実際の保存先へ置き換える。

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";
        }
    }
}

これは内部断片であり、ファイル全体ではない。Ctrl+g のモード切り替えを残す。権限要求時は出所と用途を確認し、別モードを常用する場合はそのモードへ設定する。

左ペインに二窓の Neovim、右にシェルを置き、内側移動、境界越え、戻り、最外側停止を順に確認する。失敗時は :verbose nmap <C-l>、Zellij モード、プラグイン読み込み、端末割り当てを見る。

内部関数の古い上書きは版依存の回避策である。公開設定で始め、再現した問題にだけ適合を追加し、除去条件を記録する。