---
title: Neovim input methods, remote clipboard and pane navigation
url: https://doc.liz6.com/en/tools/nvim/06-remote-input
locale: en
area: tools
tags:
- Neovim
- Developer tools
date: 2026-09-15
modified: 2026-09-15
description: Configure Fcitx5 switching, OSC52 copying and both sides of Neovim–Zellij navigation with explicit protocol and version boundaries.
---

# Neovim input methods, remote clipboard and pane navigation

Input methods, the terminal, the multiplexer and Neovim all participate in remote and multilingual editing. When an action stops working after adding SSH or Zellij, inspect that chain one layer at a time.

## Normal mode and input methods

Normal-mode `j` is a motion command. An active composition input method may consume it before Neovim receives it. Switching to a Latin keyboard layout on leaving insert mode avoids that conflict.

For a Linux desktop using Fcitx5, query the current method:

```bash
fcitx5-remote -n
```

After adding `keyboard-us` to the available methods, test switching:

```bash
fcitx5-remote -s keyboard-us
```

Only after this works, save the plugin configuration as `lua/plugins/input.lua`:

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

Check both leaving insert mode and restoring the previous method on entering it. macOS, Windows and IBus need different commands. [im-select.nvim](https://github.com/keaising/im-select.nvim)

A remote Neovim normally cannot control the local desktop's Fcitx5. Installing the command remotely does not forward that operation through SSH; use an appropriate local terminal/input strategy instead.

## Registers and the system clipboard

Internal registers do not require a desktop. `"+y` requests a clipboard provider. Wayland commonly uses wl-clipboard; OSC52 can send copied text back through an SSH terminal connection.

Inspect `:checkhealth vim.provider`, then test ordinary `y` separately from `"+y`. Ordinary copying uses the clipboard automatically only with settings such as `clipboard=unnamedplus`.

OSC52 writing and reading are separate capabilities. Terminal and multiplexer support, permissions and detection all matter. [Clipboard provider](https://neovim.io/doc/user/provider/#clipboard-osc52)

## Send OSC52 and retain a local paste cache

Put this in `lua/config/options.lua` on a Neovim version providing `vim.ui.clipboard.osc52`. It sends copies outward but reads only the most recently copied contents in this Neovim process:

```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"
```

This deliberately does not read newly copied browser content into the cache. Use the local terminal's paste action in insert mode for external text, or configure and verify a provider that supports actual clipboard reads.

Test local without multiplexer, local with Zellij, SSH without multiplexer and SSH inside Zellij. Copy a different marker each time and inspect the local desktop clipboard to locate the failing layer.

## Configure the Neovim side

Save as `lua/plugins/navigation.lua`:

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

This stops at the outer boundary and does not cross Zellij tabs. First verify movement between Neovim windows, then across the editor boundary. [smart-splits](https://github.com/mrjones2014/smart-splits.nvim)

## Configure the Zellij side

Download a compatible WASM release from [vim-zellij-navigator](https://github.com/hiasr/vim-zellij-navigator/releases). Add these bindings to the existing locked mode inside `keybinds`, replacing all four `/absolute/path/` values:

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

This is an inner fragment, not a complete configuration. Merge it with the existing `Ctrl+g` mode switch. Review the plugin source and purpose when granting requested permissions. If using another default mode, place mappings where they actually apply.

Create two Neovim windows in a left pane and a shell on the right. Verify movement within Neovim, into the shell, back into Neovim and stopping at the outer edge. On failure inspect `:verbose nmap <C-l>`, then the Zellij mode, plugin loading and terminal mappings.

Older internal-function overrides are version-specific workarounds. Start with public options and add an adaptation only for a reproduced issue, recording when it should be removed.
