Wire up commands that get sent to tmux panes on Neovim events (buffer opened, written, updated, or closed), with an in-editor UI for managing them.
- Neovim >= 0.10, built with LuaJIT (the default for all official builds)
- tmux on
$PATH - nui.nvim
libpcre2-8loadable at runtime, forpath-match(see below). Ships with macOS by default at/usr/lib/libpcre2-8.dylib; on Linux it's commonly already present as a dependency of git/php/nginx/ripgrep, otherwise install via your package manager (e.g.apt install libpcre2-8-0,brew install pcre2).
lazy.nvim:
{
"yourname/sendkeys.nvim",
dependencies = { "MunifTanjim/nui.nvim" },
opts = {},
}opts = {} calls require('sendkeys').setup({}). If you don't call setup()
yourself, the plugin calls it with defaults on VimEnter so autocmds are
still wired up.
require("sendkeys").setup({
-- Directory the sendkeys.json config file lives in.
-- Default: vim.fn.stdpath("config")
config_dir = vim.fn.stdpath("config"),
-- Enable debug logging to ~/sendkeys.nvim.log
debug = false,
})| Command | Description |
|---|---|
:SendKeys |
Open the list view (all configured commands) |
:SendKeysNew |
Open the create view directly |
:SendKeysReload |
Reload config from disk and re-wire autocmds |
j/k, arrows — move selection (preview pane updates live)<CR>— open the highlighted command in the Edit Viewa— create a new commande— toggle enabled/disabled on the highlighted commandd— delete the highlighted command (confirms first)q/<Esc>— close
e— toggle enabledn— edit name (prompt)c— edit command (opens a split;:wto save,qto cancel)p— edit path-match regex (opens a split;:wto save,qto cancel)h— pick the nvim-hook (open / write / update / close buffer)t— pick a tmux pane (lists live panes, or enter one manually)s— save (persists to disk and re-wires autocmds immediately)x— delete (existing commands only, confirms first)q/<Esc>— cancel without saving
Each entry has:
- command — the shell command text, sent to tmux literally (see Escaping, below). Supports placeholders for the buffer that triggered the autocmd (see Placeholders, below).
- tmux-pane — a tmux pane id, or a
session:window.panetarget as understood bytmux send-keys -t. - nvim-hook — one of
open buffer,write buffer,update buffer,close buffer, mapped internally toBufReadPost/BufNewFile,BufWritePost,TextChanged, andBufDeleterespectively. - path-match — a real PCRE
pattern matched against the buffer's full path, via a direct LuaJIT FFI
binding to the system
libpcre2-8(not a hand-rolled matcher, and not Vim regex). Leave empty to match every buffer. Example:\.lua$matches only Lua files;(?i)\.ya?ml$matches.yml/.yamlcase-insensitively. - enabled — when off, the command is never sent and no autocmd is wired for it.
Configuration persists as JSON at <config_dir>/sendkeys.json (default
~/.config/nvim/sendkeys.json).
command text may reference the buffer that triggered the autocmd with
{{...}} placeholders (expanded just before sending, using the actual
triggering buffer — not whatever happens to be the current buffer):
| Placeholder | Expands to |
|---|---|
{{file}} |
full path, e.g. /home/me/project/foo.lua |
{{file:tail}} |
filename only, e.g. foo.lua |
{{file:head}} |
containing directory |
{{file:root}} |
full path without extension |
{{file:ext}} |
extension only, e.g. lua |
Example: command = "cat {{file}}" sends cat /home/me/project/foo.lua.
A bare % (Vim's usual current-file placeholder) was considered, but since
command is arbitrary shell text where % shows up constantly on its own
(printf "%s\n", date +%Y-%m-%d, ...), substituting it unconditionally
would silently corrupt unrelated commands. {{...}} doesn't collide with
real-world shell syntax, so it's used instead. Any {{...}} that isn't a
recognized placeholder is left as-is.
Commands are sent via tmux send-keys -t <pane> -l -- <command> (-l =
literal, so words that look like tmux key names — Enter, C-c, etc. — are
typed as plain text, not interpreted), followed by a separate send-keys Enter to run the line. Every tmux invocation goes through
vim.fn.system() with an argument list, so there is no shell in between and
no shell-escaping to worry about.
Set debug = true in setup() to log to ~/sendkeys.nvim.log. Failed sends
always produce an error notification (vim.notify + an echoed error
message), regardless of debug mode.