As my workflow has changed over time, I no longer use tmux. This means that while I will continue to maintain this plugin, I will no longer implement fixes, features, or maintenance. PRs are always welcome and will be merged upon review.
tmux-copy.mp4
tmux-navigate.mp4
tmux-resize.mp4
- neovim >= 0.5
The plugin and .tmux.conf
scripts are battle tested with
- tmux 3.2a
Install tmux.nvim with e.g. packer.nvim and run the setup
function:
use({
"aserowy/tmux.nvim",
config = function() return require("tmux").setup() end
})
The config step is only necessary to overwrite configuration defaults.
The following defaults are given:
{
copy_sync = {
-- enables copy sync. by default, all registers are synchronized.
-- to control which registers are synced, see the `sync_*` options.
enable = true,
-- ignore specific tmux buffers e.g. buffer0 = true to ignore the
-- first buffer or named_buffer_name = true to ignore a named tmux
-- buffer with name named_buffer_name :)
ignore_buffers = { empty = false },
-- TMUX >= 3.2: all yanks (and deletes) will get redirected to system
-- clipboard by tmux
redirect_to_clipboard = false,
-- offset controls where register sync starts
-- e.g. offset 2 lets registers 0 and 1 untouched
register_offset = 0,
-- overwrites vim.g.clipboard to redirect * and + to the system
-- clipboard using tmux. If you sync your system clipboard without tmux,
-- disable this option!
sync_clipboard = true,
-- synchronizes registers *, +, unnamed, and 0 till 9 with tmux buffers.
sync_registers = true,
-- synchronizes registers when pressing p and P.
sync_registers_keymap_put = true,
-- synchronizes registers when pressing (C-r) and ".
sync_registers_keymap_reg = true,
-- syncs deletes with tmux clipboard as well, it is adviced to
-- do so. Nvim does not allow syncing registers 0 and 1 without
-- overwriting the unnamed register. Thus, ddp would not be possible.
sync_deletes = true,
-- syncs the unnamed register with the first buffer entry from tmux.
sync_unnamed = true,
},
navigation = {
-- cycles to opposite pane while navigating into the border
cycle_navigation = true,
-- enables default keybindings (C-hjkl) for normal mode
enable_default_keybindings = true,
-- prevents unzoom tmux when navigating beyond vim border
persist_zoom = false,
},
resize = {
-- enables default keybindings (A-hjkl) for normal mode
enable_default_keybindings = true,
-- sets resize steps for x axis
resize_step_x = 1,
-- sets resize steps for y axis
resize_step_y = 1,
}
}
Tmux.nvim uses only lua
API. If you are not running the default keybindings, you can bind the following functions to your liking. Besides the bindings in nvim you need to add configuration to tmux.
Copy sync uses tmux buffers as master clipboard for *
, +
, unnamed
, and 0
- 9
registers. The sync does NOT rely on temporary files and works just with the given tmux API. Thus, making it less insecure :). The feature enables a nvim instace overarching copy/paste process! dd in one nvim instance, switch to the second and p your deletes.
If you sync your clipboard not with a standalone tmux, disable sync_clipboard
to ensure nvim handles yanks and deletes alone.
This has some downsites, on really slow machines, calling registers or pasting will eventually produce minimal input lag by syncing registers in advance to ensure the correctness of state. To avoid this, disabling sync_registers
will only redirect the *
and +
registers.
To redirect copies (and deletes) to clipboard, tmux must have the capability to do so. The plugin will just set -w on set-buffer. If your tmux need more configuration check out tmux-yank for an easy setup.
Ignoring buffers must have the form of buffer_name = true
to enable an unsorted list in lua. This enhances the performance of checks - if a buffer is ignored or not - meaningfull.
To enable cycle-free navigation beyond nvim, add the following to your ~/.tmux.conf
:
It is important to note, that your bindings in nvim must match the defined bindings in tmux! Otherwise the pass through will not have the seamless effect!
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?\.?(view|n?vim?x?)(-wrapped)?(diff)?$'"
bind-key -n 'C-h' if-shell "$is_vim" 'send-keys C-h' { if -F '#{pane_at_left}' '' 'select-pane -L' }
bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j' { if -F '#{pane_at_bottom}' '' 'select-pane -D' }
bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k' { if -F '#{pane_at_top}' '' 'select-pane -U' }
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l' { if -F '#{pane_at_right}' '' 'select-pane -R' }
bind-key -T copy-mode-vi 'C-h' if -F '#{pane_at_left}' '' 'select-pane -L'
bind-key -T copy-mode-vi 'C-j' if -F '#{pane_at_bottom}' '' 'select-pane -D'
bind-key -T copy-mode-vi 'C-k' if -F '#{pane_at_top}' '' 'select-pane -U'
bind-key -T copy-mode-vi 'C-l' if -F '#{pane_at_right}' '' 'select-pane -R'
Otherwise you can add:
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?\.?(view|n?vim?x?)(-wrapped)?(diff)?$'"
bind-key -n 'C-h' if-shell "$is_vim" 'send-keys C-h' 'select-pane -L'
bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j' 'select-pane -D'
bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k' 'select-pane -U'
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l' 'select-pane -R'
bind-key -T copy-mode-vi 'C-h' select-pane -L
bind-key -T copy-mode-vi 'C-j' select-pane -D
bind-key -T copy-mode-vi 'C-k' select-pane -U
bind-key -T copy-mode-vi 'C-l' select-pane -R
To run custom bindings in nvim, make sure to set enable_default_keybindings
to false
. The following functions are used to navigate around windows and panes:
{
[[<cmd>lua require("tmux").move_left()<cr>]],
[[<cmd>lua require("tmux").move_bottom()<cr>]],
[[<cmd>lua require("tmux").move_top()<cr>]],
[[<cmd>lua require("tmux").move_right()<cr>]],
}
Add the following bindings to your ~/.tmux.conf
:
It is important to note, that your bindings in nvim must match the defined bindings in tmux! Otherwise the pass through will not have the seamless effect!
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"
bind -n 'M-h' if-shell "$is_vim" 'send-keys M-h' 'resize-pane -L 1'
bind -n 'M-j' if-shell "$is_vim" 'send-keys M-j' 'resize-pane -D 1'
bind -n 'M-k' if-shell "$is_vim" 'send-keys M-k' 'resize-pane -U 1'
bind -n 'M-l' if-shell "$is_vim" 'send-keys M-l' 'resize-pane -R 1'
bind-key -T copy-mode-vi M-h resize-pane -L 1
bind-key -T copy-mode-vi M-j resize-pane -D 1
bind-key -T copy-mode-vi M-k resize-pane -U 1
bind-key -T copy-mode-vi M-l resize-pane -R 1
To run custom bindings in nvim, make sure to not set enable_default_keybindings
to true
. The following functions are used to resize windows:
{
[[<cmd>lua require("tmux").resize_left()<cr>]],
[[<cmd>lua require("tmux").resize_bottom()<cr>]],
[[<cmd>lua require("tmux").resize_top()<cr>]],
[[<cmd>lua require("tmux").resize_right()<cr>]],
}
If you are using tmux plugin manager, you can add the following plugin.
set -g @plugin 'aserowy/tmux.nvim'
run '~/.tmux/plugins/tpm/tpm'
Available options for the plugin and their defaults are:
set -g @plugin 'aserowy/tmux.nvim'
# navigation
set -g @tmux-nvim-navigation true
set -g @tmux-nvim-navigation-cycle true
set -g @tmux-nvim-navigation-keybinding-left 'C-h'
set -g @tmux-nvim-navigation-keybinding-down 'C-j'
set -g @tmux-nvim-navigation-keybinding-up 'C-k'
set -g @tmux-nvim-navigation-keybinding-right 'C-l'
# resize
set -g @tmux-nvim-resize true
set -g @tmux-nvim-resize-step-x 1
set -g @tmux-nvim-resize-step-y 1
set -g @tmux-nvim-resize-keybinding-left 'M-h'
set -g @tmux-nvim-resize-keybinding-down 'M-j'
set -g @tmux-nvim-resize-keybinding-up 'M-k'
set -g @tmux-nvim-resize-keybinding-right 'M-l'
run '~/.tmux/plugins/tpm/tpm'
To enable searching for nvim in subshells, you need to change the 'is_vim' part in the tmux plugin. This will make searching for nested nvim instances result in positive resolutions.
Found by @duongapollo
The configuration in the given issue integrates tmux.nvim with yanky.nvim and which-key.nvim so we get the benefits of all yank-related plugins.
Found by @kiyoon
Contributed code must pass luacheck and be formatted with stylua. Besides formatting, all tests have to pass. Tests are written for busted.
If you are using nix-shell, you can start a nix-shell and run fac
(format and check).
stylua lua/ && luacheck lua/ && busted --verbose