Seamlessly navigate between Herdr panes and Vim/Neovim splits; a reimplementation of christoomey/vim-tmux-navigator in Herdr.
Ctrl-h/j/k/l move across Vim/Neovim splits and Herdr panes as if they were one grid: editor windows get first chance, and Herdr takes over when the cursor hits an edge.
It ships as two halves, both in this repo:
- Vim/Neovim plugin at the repo root (
plugin/,autoload/,lua/,doc/), native Lua and classic Vimscript adapters. - Herdr-side helper in
helper/, a small Rust binary (vim-herdr-navigator) driven from Herdr keybindings.
- Vim 8.2+ or Neovim 0.8+ (Neovim 0.10+ recommended)
- Herdr
No Rust toolchain needed: the installer downloads a prebuilt helper binary and only falls back to cargo build on platforms without one (macOS arm64/x86_64 and Linux arm64/x86_64 are prebuilt).
Setup is two steps:
- Install the Vim/Neovim plugin; its build hook installs the helper binary too.
- Add the Herdr keybindings that drive the helper.
The install.sh build hook downloads the helper release matching the plugin checkout (so the two never drift apart), places it in the plugin's bin/, and copies it into ~/.local/bin so Herdr finds it on PATH. It re-runs on every plugin update, keeping the helper in lockstep.
{
"AVGVSTVS96/vim-herdr-navigator",
lazy = false,
build = "./install.sh",
opts = {},
}Zero config: opts = {} uses the defaults, and the plugin finds the hook-installed helper automatically. See Options to customize.
vim.pack.add({ "https://github.com/AVGVSTVS96/vim-herdr-navigator" })
-- run the helper installer once after the plugin is downloaded:
-- :!cd <plugin dir> && ./install.sh
-- setup() is auto-called on load; call it explicitly only to pass options:
-- require("vim-herdr-navigator").setup({})Any Vim plugin manager that adds the repo root to runtimepath works, for example vim-plug:
Plug 'AVGVSTVS96/vim-herdr-navigator', { 'do': './install.sh' }Classic Vim is configured with g:vim_herdr_navigator_* variables; Neovim can use require("vim-herdr-navigator").setup({ ... }). Both auto-setup on load by default.
If you'd rather manage the helper binary yourself, skip the build hook and use any of these; the plugin falls back to vim-herdr-navigator on PATH:
# shell installer (prebuilt binary, no Rust needed) -> ~/.local/bin
curl -LsSf https://github.com/AVGVSTVS96/vim-herdr-navigator/releases/latest/download/vim-herdr-navigator-installer.sh | sh
# cargo-binstall (prebuilt binary via cargo)
cargo binstall --git https://github.com/AVGVSTVS96/vim-herdr-navigator vim-herdr-navigator
# build from source with cargo
cargo install --git https://github.com/AVGVSTVS96/vim-herdr-navigator --package vim-herdr-navigatorVerify with:
vim-herdr-navigator --version
vim-herdr-navigator doctorinstall.sh knobs: VIM_HERDR_NAVIGATOR_NO_LOCAL_BIN=1 skips the ~/.local/bin copy (point your Herdr keybindings at <plugin dir>/bin/vim-herdr-navigator instead), and VIM_HERDR_NAVIGATOR_FORCE_BUILD=1 always builds from source.
Finally, bind the keys in Herdr so it can hand them to the helper.
Set the following keybindings in your Herdr config (~/.config/herdr/config.toml):
[[keys.command]]
key = "ctrl+h"
type = "shell"
command = "vim-herdr-navigator dispatch left"
description = "vim-aware pane left"
[[keys.command]]
key = "ctrl+j"
type = "shell"
command = "vim-herdr-navigator dispatch down"
description = "vim-aware pane down"
[[keys.command]]
key = "ctrl+k"
type = "shell"
command = "vim-herdr-navigator dispatch up"
description = "vim-aware pane up"
[[keys.command]]
key = "ctrl+l"
type = "shell"
command = "vim-herdr-navigator dispatch right"
description = "vim-aware pane right"Alternatively, the config command generates a ready-to-paste snippet for your convenience:
vim-herdr-navigator config | pbcopyPaste the output into your Herdr config, then restart or reload config in Herdr.
Check installation with vim-herdr-navigator doctor in your shell, :checkhealth vim-herdr-navigator in vim, and :help vim-herdr-navigator for the full reference, then see Behavior and Options to customize.
Inside Vim/Neovim:
Ctrl-h/j/k/lfirst tries normal Vim window navigation withwincmd h/j/k/l.- If the current editor window did not change (an edge), the plugin calls
vim-herdr-navigator focus <direction>. - The helper focuses the neighboring Herdr pane.
From a non-editor Herdr pane, Herdr keybindings call vim-herdr-navigator dispatch <direction>, which decides whether to move Herdr focus or send ctrl+h/j/k/l into Vim/Neovim.
This mirrors vim-tmux-navigator: Vim windows get first chance, the multiplexer gets focus at an edge.
The plugin only acts inside a Herdr session (HERDR_ENV=1 or HERDR_SOCKET_PATH set); elsewhere it stays inert.
By default (set_keymaps = true) the plugin owns <C-h/j/k/l>. It reasserts them after LazyVim installs its own window maps on User VeryLazy. To use your own mappings, set set_keymaps = false and map the commands yourself.
In Neovim, known floating pickers/explorers get extra handling: left navigation from a focused picker float goes straight to Herdr instead of bouncing focus back inside Neovim. Which filetypes count is configurable via picker_filetype_patterns; defaults cover Snacks picker/explorer, Telescope, mini.pick, fzf/fzf-lua, neo-tree, NvimTree, netrw, and oil.
Pickers install their own buffer-local <C-h/j/k/l>, so the Neovim adapter reasserts its maps buffer-locally in those buffers too; it owns these keys there as well. If you need a picker to keep one of them, narrow picker_filetype_patterns or change keymaps. In fzf/fzf-lua terminal buffers the terminal-mode mapping passes the key through to the picker.
Normal and terminal mode:
<C-h>: left<C-j>: down<C-k>: up<C-l>: right
Add or change keys via the keymaps option, e.g. left = { "<C-h>", "<A-Left>" }, and mirror any additions with matching [[keys.command]] blocks in the Herdr config so both halves agree.
:HerdrNavigateLeft:HerdrNavigateDown:HerdrNavigateUp:HerdrNavigateRight
Use these if you set set_keymaps = false and want custom mappings.
Neovim defaults shown:
require("vim-herdr-navigator").setup({
helper = "vim-herdr-navigator", -- name or path of the helper command (~ is expanded); at this
-- default the hook-installed <plugin>/bin binary is preferred over PATH
set_keymaps = true, -- false: manage keys yourself via :HerdrNavigate* commands
save_on_switch = 0, -- 0 never, 1 :update, 2 :wall (save before leaving Neovim)
picker_filetype_patterns = { -- filetypes treated as floating pickers
"^snacks_picker",
"^Telescope",
"^minipick$",
"^fzf$",
"^fzf%-lua$",
"^neo%-tree$",
"^NvimTree$",
"^netrw$",
"^oil$",
},
keymaps = { -- keys per direction; add your own, e.g. { "<C-h>", "<A-Left>" }
left = { "<C-h>" },
down = { "<C-j>" },
up = { "<C-k>" },
right = { "<C-l>" },
},
})To call Neovim setup() yourself at a specific time, disable auto-setup before the plugin loads:
vim.g.vim_herdr_navigator_auto_setup = falseClassic Vim uses matching globals before the plugin loads:
let g:vim_herdr_navigator_helper = 'vim-herdr-navigator'
let g:vim_herdr_navigator_set_keymaps = 1
let g:vim_herdr_navigator_save_on_switch = 0
let g:vim_herdr_navigator_auto_setup = 1The helper reads a few optional variables. Set them in your shell rc (e.g. ~/.zshrc) so the Herdr-spawned keybinding process inherits them, then restart Herdr.
| Variable | Default | Effect |
|---|---|---|
VIM_HERDR_NAVIGATOR_PATTERN |
(unset) | Extra regex OR-ed into the built-in Vim-like detection: the Herdr counterpart to tmux's @vim_navigator_pattern. Extends, never narrows, the set; case-insensitive, unanchored. |
VIM_HERDR_NAVIGATOR_ZOOM |
preserve |
unzoom un-maximizes the pane you move out of before focusing; preserve keeps Herdr's native zoom. |
VIM_HERDR_NAVIGATOR_ENTRY_MARKERS |
(off) | Set to 1 to land on the split nearest the entered edge. See Entry markers. |
# Also keep nav keys inside ssh (treat it as "Vim-like"):
export VIM_HERDR_NAVIGATOR_PATTERN='(view|l?n?vim?x?|fzf|ssh)'See helper/README.md for the full reference and design notes.
When you move from another Herdr pane into a Vim/Neovim pane, an entry marker lets the plugin land you on the split nearest the edge you entered from, instead of wherever the cursor last was. It's off by default.
Enable it with a single environment variable; export it in your shell rc and restart Herdr:
export VIM_HERDR_NAVIGATOR_ENTRY_MARKERS=1That one switch covers both halves: the helper writes the markers, and this plugin (which inherits Herdr's environment) reads them; there's no separate setup() option to keep in sync.
Point the plugin at a local checkout and at a local helper build. Adjust the paths to wherever you cloned the repos:
{
dir = "~/projects/vim-herdr-navigator", -- local clone of this repo
name = "vim-herdr-navigator",
lazy = false,
opts = {
-- point at the helper's release build during development
-- (run `cargo build --release` at the repo root first)
helper = "~/projects/vim-herdr-navigator/target/release/vim-herdr-navigator",
},
}Run the dependency-free editor smoke tests and the Rust helper checks:
tests/run.sh # Neovim
tests/run-vim.sh # Vim
cargo fmt --all --check
cargo clippy --all-targets -- -D warnings
cargo test --allThe smoke tests load the plugin and exercise setup()/commands/navigation. The Neovim smoke test also runs :checkhealth.
C-\(previous-pane toggle) is not ported; Herdr does not yet expose last-pane to the CLI.- Copy mode: navigation keys are unavailable while a pane is in Herdr's copy mode. Exit copy mode to navigate.
- No edge wrapping: at the outer edge of the grid a directional key is a no-op, matching typical multiplexer behavior.
See helper/README.md for details.
A reimplementation of christoomey/vim-tmux-navigator in Herdr. Thanks to Chris Toomey, and Mislav Marohnic for their work on the original plugin.