Loading repository data…
Loading repository data…
mrjones2014 / repository
🧠 Smart, seamless, directional navigation and resizing of Neovim + terminal multiplexer splits. Supports Zellij, Tmux, Wezterm, and Kitty. Think about splits in terms of "up/down/left/right".
smart-splits.nvim🧠 Smarter and more intuitive split pane management that uses a mental model of left/right/up/down instead of wider/narrower/taller/shorter for resizing. Supports seamless navigation between Neovim and terminal multiplexer split panes. See Multiplexer Integrations.
Table of Contents
smart-splits.nvim now supports semantic versioning via git tags. See Releases
for a full list of versions and their changelogs, starting from 1.0.0.
With Packer.nvim:
use('mrjones2014/smart-splits.nvim')
-- or use a specific version
use({ 'mrjones2014/smart-splits.nvim', tag = 'v1.0.0' })
-- to use Kitty multiplexer support, run the post install hook
use({ 'mrjones2014/smart-splits.nvim', run = './kitty/install-kittens.bash' })
With Lazy.nvim:
{ 'mrjones2014/smart-splits.nvim' }
-- or use a specific version, or a range of versions using lazy.nvim's version API
{ 'mrjones2014/smart-splits.nvim', version = '>=1.0.0' }
-- to use Kitty multiplexer support, run the post install hook
{ 'mrjones2014/smart-splits.nvim', build = './kitty/install-kittens.bash' }
You can set ignored buftypes or filetypes which will be ignored when
figuring out if your cursor is currently at an edge split for resizing.
This is useful in order to ignore "sidebar" type buffers while resizing,
such as nvim-tree.lua
which tries to maintain its own width unless manually resized. Note that
nothing is ignored when moving between splits, only when resizing.
[!NOTE] smart-splits.nvim does not map any keys on it's own. See Usage.
Defaults are shown below:
require('smart-splits').setup({
-- Ignored buffer types (only while resizing)
ignored_buftypes = {
'nofile',
'quickfix',
'prompt',
},
-- Ignored filetypes (only while resizing)
ignored_filetypes = { 'NvimTree' },
-- the default number of lines/columns to resize by at a time
default_amount = 3,
-- Desired behavior when your cursor is at an edge and you
-- are moving towards that same edge:
-- 'wrap' => Wrap to opposite side
-- 'split' => Create a new split in the desired direction
-- 'stop' => Do nothing
-- function => You handle the behavior yourself
-- NOTE: If using a function, the function will be called with
-- a context object with the following fields:
-- {
-- mux = {
-- type:'tmux'|'wezterm'|'kitty'|'zellij'
-- current_pane_id():number,
-- is_in_session(): boolean
-- current_pane_is_zoomed():boolean,
-- -- following methods return a boolean to indicate success or failure
-- current_pane_at_edge(direction:'left'|'right'|'up'|'down'):boolean
-- next_pane(direction:'left'|'right'|'up'|'down'):boolean
-- resize_pane(direction:'left'|'right'|'up'|'down'):boolean
-- split_pane(direction:'left'|'right'|'up'|'down',size:number|nil):boolean
-- },
-- direction = 'left'|'right'|'up'|'down',
-- split(), -- utility function to split current Neovim pane in the current direction
-- wrap(), -- utility function to wrap to opposite Neovim pane
-- }
-- NOTE: `at_edge = 'wrap'` is not supported on Kitty terminal
-- multiplexer, as there is no way to determine layout via the CLI
at_edge = 'wrap',
-- Desired behavior when the current window is floating:
-- 'previous' => Focus previous Vim window and perform action
-- 'mux' => Always forward action to multiplexer
float_win_behavior = 'previous',
-- when moving cursor between splits left or right,
-- place the cursor on the same row of the *screen*
-- regardless of line numbers. False by default.
-- Can be overridden via function parameter, see Usage.
move_cursor_same_row = false,
-- whether the cursor should follow the buffer when swapping
-- buffers by default; it can also be controlled by passing
-- `{ move_cursor = true }` or `{ move_cursor = false }`
-- when calling the Lua function.
cursor_follows_swapped_bufs = false,
-- ignore these autocmd events (via :h eventignore) while processing
-- smart-splits.nvim computations, which involve visiting different
-- buffers and windows. These events will be ignored during processing,
-- and un-ignored on completed. This only applies to resize events,
-- not cursor movement events.
ignored_events = {
'BufEnter',
'WinEnter',
},
-- enable or disable a multiplexer integration;
-- automatically determined, unless explicitly disabled or set,
-- by checking the $TERM_PROGRAM environment variable,
-- and the $KITTY_LISTEN_ON environment variable for Kitty.
-- You can also set this value by setting `vim.g.smart_splits_multiplexer_integration`
-- before the plugin is loaded (e.g. for lazy environments).
multiplexer_integration = nil,
-- disable multiplexer navigation if current multiplexer pane is zoomed
-- NOTE: This does not work on Zellij as there is no way to determine the
-- pane zoom state outside of the Zellij Plugin API, which does not apply here
disable_multiplexer_nav_when_zoomed = true,
-- Supply a Kitty remote control password if needed,
-- or you can also set vim.g.smart_splits_kitty_password
-- see https://sw.kovidgoyal.net/kitty/conf/#opt-kitty.remote_control_password
kitty_password = nil,
-- In Zellij, set this to true if you would like to move to the next *tab*
-- when the current pane is at the edge of the zellij tab/window
zellij_move_focus_or_tab = false,
-- default logging level, one of: 'trace'|'debug'|'info'|'warn'|'error'|'fatal'
log_level = 'info',
})
[!NOTE] The recommended mappings use the Alt/Meta key. In some terminals, such as Alacritty and Ghostty, on macOS you will need to set a configuration option for it to treat the macOS Option key as Alt.
See: https://ghostty.org/docs/config/reference#macos-option-as-alt
See: https://alacritty.org/config-alacritty.html#s20
-- recommended mappings
-- resizing splits
-- these keymaps will also accept a range,
-- for example `10<A-h>` will `resize_left` by `(10 * config.default_amount)`
vim.keymap.set('n', '<A-h>', require('smart-splits').resize_left)
vim.keymap.set('n', '<A-j>', require('smart-splits').resize_down)
vim.keymap.set('n', '<A-k>', require('smart-splits').resize_up)
vim.keymap.set('n', '<A-l>', require('smart-splits').resize_right)
-- moving between splits
vim.keymap.set('n', '<C-h>', require('smart-splits').move_cursor_left)
vim.keymap.set('n', '<C-j>', require('smart-splits').move_cursor_down)
vim.keymap.set('n', '<C-k>', require('smart-splits').move_cursor_up)
vim.keymap.set('n', '<C-l>', require('smart-splits').move_cursor_right)
vim.keymap.set('n', '<C-\\>', require('smart-splits').move_cursor_previous)
-- swapping buffers between windows
vim.keymap.set('n', '<leader><leader>h', require('smart-splits').swap_buf_left)
vim.keymap.set('n', '<leader><leader>j', require('smart-splits').swap_buf_down)
vim.keymap.set('n', '<leader><leader>k', require('smart-splits').swap_buf_up)
vim.keymap.set('n', '<leader><leader>l', require('smart-splits').swap_buf_right)
-- resizing splits
-- amount defaults to 3 if not specified
-- use absolute values, no + or -
-- the functions also check for a range,
-- so for example if you bind `<A-h>` to `resize_left`,
-- then `10<A-h>` will `resize_left` by `(10 * config.default_amount)`
require('smart-splits').resize_up(amount)
require('smart-splits').resize_down(amount)
require('smart-splits').resize_left(amount)
require('smart-splits').resize_right(amount)
-- moving between splits
-- You can override config.at_edge and
-- config.move_cursor_same_row via opts
-- See Configuration.
require('smart-splits').move_cursor_up({ same_row = boolean, at_edge = 'wrap' | 'split' | 'stop' })
require('smart-splits').move_cursor_down()
require('smart-splits').move_cursor_left()
require('smart-splits').move_cursor_right()
require('smart-splits').move_cursor_previous()
-- Swapping buffers directionally with the window to the specified direction
require('smart-splits').swap_buf_up()
require('smart-splits').swap_buf_down()
require('smart-splits').swap_buf_left()
require('smart-splits').swap_buf_right()
-- the buffer swap functions can also take an `opts` table to override the
-- default behavior of whether or not the cursor follows the buffer
require('smart-splits').swap_buf_right({ move_cursor = true })
smart-splits.nvim can also enable seamless navigation between Neovim splits and tmux, zellij, wezterm, or kitty panes.
You will need to set up keymaps in your tmux, wezterm, or kitty configs to match the Neovim keymaps.
You can also set the desired multiplexer integration in lazy environments before the plugin is loaded by setting
vim.g.smart_splits_multiplexer_integration. The values are the same as described in Configuration.
You can use the package manager TPM to configure your Tmux setup:
[!NOTE] It is recommended to not lazy load
smart-splits.nvimwhen using this integration. It depends on the plugin setting the@pane-is-vimtmux variable, which won't happen until the plugin is loaded.Currently, jumping to the last viewed pane is not supported. Feel free to submit a PR for it!
set -g @plugin 'mrjones2014/smart-splits.nvim'
# Optional configurations with their default values if omitted:
set -g @smart-splits_no_wrap '' # to disable wrapping. (any value disables wrapping)
set -g @smart-splits_move_left_key 'C-h' # key-mapping for navigation.
set -g @smart-splits_move_down_key 'C-j' # --"--
set -g @smart-splits_move_up_key 'C-k' # --"--
set -g @smart-splits_move_right_key 'C-l' # --"--
set -g @smart-splits_resize_left_key 'M-h' # key-mapping for resizing.
set -g @smart-splits_resize_down_key 'M-j' # --"--
set -g @smart-splits_resize_up_key 'M-k' # --"--
set -g @smart-splits_resize_right_key 'M-l' # --"--
set -g @smart-splits_resize_step_size '3' # change the step-size for resizing.
Alternatively, add the following snippet to your ~/.tmux.conf/~/.config/tmux/tmux.conf file (customizing the keys and resize amount if desired):
# '@pane-is-vim' is a pane-local option that is set by the plugin on load,
# and unset when Neovim exits or suspends; note that this means you'll probably
# not want to lazy-load smart-splits.nvim, as the variable won't be set until
# the plugin is loaded
# Smart pane switching with awareness of Neovim splits.
bind-key -n C-h if -F "#{@pane-is-vim}" 'send-keys C-h' 'select-pane -L'
bind-key -n C-j if -F "#{@pane-is-vim}" 'send-keys C-j' 'select-pane -D'
bind-key -n C-k if -F "#{@pane-is-vim}" 'send-keys C-k' 'select-pane -U'
bind-key -n C-l if -F "#{@pane-is-vim}" 'send-keys C-l' 'select-pane -R'
# Alternatively, if you want to disable wrapping when moving in non-neovim panes, use these bindings
# bind-key -n C-h if -F '#{@pane-is-vim}' { send-keys C-h } { if -F '#{pane_at_left}' '' 'select-pane -L' }
# bind-key -n C-j if -F '#{@pane-is-vim}' { send-keys C-j } { if -F '#{pane_a