Switching Zellij and Vim panes with ease

I'm trying out Zellij after it announced non-colliding keybindings in version 0.41.0. Currently, I'm using iTerm and MacVim as two separate apps, but I'd like to try running everything in the terminal and switch to NeoVim.

I never got into tmux, but I know there are plugins to have you smoothly switch between tmux and Vim panes like they are from the same application. I want this behavior in Zellij too without having to switch Zellij modes.

As I got started with Zellij, I found a couple of different projects with different ideas on how to accomplish this.

For the default Zellij colliding keybindings, zellij-autolock looks like the way to go. It automatically switches between the "normal" and "locked" modes when entering and leaving Vim. But, as the author pointed out, it is not tested with non-colliding keybindings.

I couldn't find a solution that worked for me using the Zellij non-colliding keybindings in combination with Vim, while also allowing other applications that use the ctrl+hjkl keybindings to work, but please let me know if I missed one!

What I ended up doing to make it work for me is the following:

Install a Neovim Zellij plugin

Add the zellij-nav.nvim plugin * to your Vim config that will send commands to Zellij when you try to press one of the ctrl+hjkl keybindings and are on the edge of a Vim window.

*: There may be other plugins that work, too; this is the one that worked for me.

I'm using vim-plug for package management. There are other Vim package managers listed on the plugin's README.

" ~/.vimrc

call plug#begin('~/.vim/plugged')

" Other plugins here

Plug 'swaits/zellij-nav.nvim'

call plug#end()

lua require("zellij-nav").setup()

nnoremap <c-h> <cmd>ZellijNavigateLeft<cr>
nnoremap <c-j> <cmd>ZellijNavigateDown<cr>
nnoremap <c-k> <cmd>ZellijNavigateUp<cr>
nnoremap <c-l> <cmd>ZellijNavigateRight<cr>

Then quit Vim, restart it, and run the :PlugInstall command.

Install the Zellij plugin

Next up, we'll install a Zellij plugin. I started using the vim-zellij-navigator. It works great for switching between Vim and Zellij.

However, I ran into issues with other applications like fzf, triggered by running fzf or pressing ctrl-r/ctrl-t. When the fzf prompt is open, pressing ctrl-j or ctrl-k now does nothing. Instead, the Zellij plugin captures the key presses and switches panes or tabs, but the fzf prompt stays unchanged.

I forked the plugin and made some changes to support these scenarios. Don't worry I submitted a Pull Request to merge them upstream.

Until those are merged and released, you'll need to build the plugin manually.

  1. Install Rust via rustup.
  2. Clone the repository fork:

    git clone https://github.com/tombruijn/vim-zellij-navigator.git
    
  3. Compile the plugin.

    # Open the project directory
    cd vim-zellij-navigator
    # Install the latest Rust version (at time of writing)
    rustup override set 1.82
    # Install Rust WASM target
    rustup target add wasm32-wasi
    # Compile the project
    cargo build --release
    
  4. Copy the plugin to the Zellij plugin directory.

    # Create the plugins directory if it does not exist yet
    mkdir -p ~/.config/zellij/plugins
    # Copy the plugin to the plugins directory
    cp ~/target/wasm32-wasi/release/vim-zellij-navigator.wasm ~/.config/zellij/plugins
    

Finally, configure Zellij to load the plugin and configure keybindings to call the plugin.

My configuration is down below, which is complete for my use case.

After configuring Zellij, restart it. The first time you press any of the keybindings you've configured to call the plugin (indicated with MessagePlugin), you'll get a prompt from the plugin asking permission to do a couple things. Press `y' to approve the permissions.

// ~/.config/zellij/config.kdl

plugins {
    vim-zellij-navigator location="file:~/.config/zellij/plugins/vim-zellij-navigator.wasm"
}

// I use the locked mode as the default mode.
// By default this is set to "normal".
default_mode "locked"

keybinds clear-defaults=true {
    locked { // Change this to "normal" if your default mode is "normal"
        // Switch panes with `Ctrl+hjkl`
        bind "Ctrl h" {
            MessagePlugin "vim-zellij-navigator" {
                name "move_focus_or_tab";
                payload "left";
            };
        }
        bind "Ctrl j" {
            MessagePlugin "vim-zellij-navigator" {
                name "move_focus";
                payload "down";
            };
        }
        bind "Ctrl k" {
            MessagePlugin "vim-zellij-navigator" {
                name "move_focus";
                payload "up";
            };
        }
        bind "Ctrl l" {
            MessagePlugin "vim-zellij-navigator" {
                name "move_focus_or_tab";
                payload "right";
            };
        }

        // Disable the plugin when any of these keybindings are used and still press that key too
        bind "Ctrl r" { // `Ctrl+r` is for forward shell history
            WriteChars "\u{0012}"; // Passthrough `Ctrl+r`
            MessagePlugin "vim-zellij-navigator" {
              payload "disable";
            };
        }
        bind "Ctrl s" { // `Ctrl+s` is for backward shell history
            WriteChars "\u{0013}"; // Passthrough `Ctrl+s`
            MessagePlugin "vim-zellij-navigator" {
              payload "disable";
            };
        }
        bind "Ctrl t" { // `Ctrl+s` is for backward shell history
            WriteChars "\u{0014}"; // Passthrough `Ctrl+t`
            MessagePlugin "vim-zellij-navigator" {
              payload "disable";
            };
        }
        // Reenable the plugin when any of these keybindings are used and still press that key too
        bind "ESC" {
            WriteChars "\u{001B}"; // Passthrough `ESC`
            MessagePlugin "vim-zellij-navigator" {
              payload "enable";
            };
        }
        bind "Enter" {
            WriteChars "\u{000D}"; // Passthrough `Enter`
            MessagePlugin "vim-zellij-navigator" {
              payload "enable";
            };
        }
        bind "Ctrl c" {
            WriteChars "\u{0003}"; // Passthrough `Ctrl+c`
            MessagePlugin "vim-zellij-navigator" {
              payload "enable";
            };
        }
        // Toggle the plugin on or off manually
        bind "Ctrl Alt a" {
            MessagePlugin "vim-zellij-navigator" {
                payload "toggle";
            };
        }
    }
}