diff --git a/README.md b/README.md index 809f737..1b2d0cd 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,14 @@ require('smart-splits').setup({ ignored_buftypes = { 'NvimTree' }, -- the default number of lines/columns to resize by at a time default_amount = 3, - -- whether to wrap to opposite side when cursor is at an edge - -- e.g. by default, moving left at the left edge will jump - -- to the rightmost window, and vice versa, same for up/down. - -- NOTE: `wrap_at_edge = false` is not supported on Kitty terminal + -- 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 + -- NOTE: `at_edge = 'wrap'` is not supported on Kitty terminal -- multiplexer, as there is no way to determine layout via the CLI - wrap_at_edge = true, + at_edge = 'wrap', -- 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. @@ -127,6 +129,10 @@ require('smart-splits').setup({ -- this functionality is only supported on tmux and Wezterm due to kitty -- not having a way to check if a pane is zoomed 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, }) ``` @@ -134,7 +140,7 @@ require('smart-splits').setup({ The hook table allows you to define callbacks for the `on_enter` and `on_leave` events of the resize mode. -##### Examples: +##### Examples Integration with [bufresize.nvim](https://github.com/kwkarlwang/bufresize.nvim): @@ -233,7 +239,7 @@ vim.keymap.set('n', 'k', require('smart-splits').swap_buf_up) vim.keymap.set('n', 'l', require('smart-splits').swap_buf_right) ``` -### Lua API: +### Lua API ```lua -- resizing splits @@ -247,13 +253,13 @@ require('smart-splits').resize_down(amount) require('smart-splits').resize_left(amount) require('smart-splits').resize_right(amount) -- moving between splits --- pass same_row as a boolean to override the default --- for the move_cursor_same_row config option. +-- You can override config.at_edge and +-- config.move_cursor_same_row via opts -- See Configuration. -require('smart-splits').move_cursor_up() +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(same_row) -require('smart-splits').move_cursor_right(same_row) +require('smart-splits').move_cursor_left() +require('smart-splits').move_cursor_right() -- Swapping buffers directionally with the window to the specified direction require('smart-splits').swap_buf_up() require('smart-splits').swap_buf_down() @@ -381,7 +387,7 @@ return { #### Kitty -> **Note** `config.wrap_at_edge = false` is not supoprted in Kitty terminal multiplexer due to inability to determine +> **Note** `config.at_edge = 'wrap'` is not supoprted in Kitty terminal multiplexer due to inability to determine > pane layout from CLI. Add the following snippet to `~/.config/kitty/kitty.conf`, adjusting the keymaps and resize amount as desired. diff --git a/lua/smart-splits/api.lua b/lua/smart-splits/api.lua index 6913830..bfa7b43 100644 --- a/lua/smart-splits/api.lua +++ b/lua/smart-splits/api.lua @@ -1,30 +1,45 @@ +local types = require('smart-splits.types') +local Direction = types.Direction +local AtEdgeBehavior = types.AtEdgeBehavior + local M = {} local config = require('smart-splits.config') local mux = require('smart-splits.mux') -local win_pos = { +---@enum WinPosition +local WinPosition = { start = 0, middle = 1, last = 2, } -local dir_keys = { +---@enum DirectionKeys +local DirectionKeys = { left = 'h', right = 'l', up = 'k', down = 'j', } -local dir_keys_reverse = { +---@enum DirectionKeysReverse +local DirectionKeysReverse = { left = 'l', right = 'h', up = 'j', down = 'k', } +---@enum WincmdResizeDirection +local WincmdResizeDirection = { + bigger = '+', + smaller = '-', +} + local is_resizing = false +---@param winnr number|nil window ID, defaults to current window +---@return boolean local function is_full_height(winnr) -- for vertical height account for tabline, status line, and cmd line local window_height = vim.o.lines - 1 - vim.o.cmdheight @@ -34,13 +49,17 @@ local function is_full_height(winnr) return vim.api.nvim_win_get_height(winnr or 0) == window_height end +---@param winnr number|nil window ID, defaults to current window +---@return boolean local function is_full_width(winnr) return vim.api.nvim_win_get_width(winnr or 0) == vim.o.columns end +---@param direction DirectionKeys +---@param skip_ignore_lists boolean|nil defaults to false local function next_window(direction, skip_ignore_lists) local cur_win = vim.api.nvim_get_current_win() - if direction == dir_keys.down or direction == dir_keys.up then + if direction == DirectionKeys.down or direction == DirectionKeys.up then vim.cmd('wincmd ' .. direction) if not skip_ignore_lists @@ -71,130 +90,150 @@ local function next_window(direction, skip_ignore_lists) return view end +---@return boolean local function at_top_edge() return vim.fn.winnr() == vim.fn.winnr('k') end +---@return boolean local function at_bottom_edge() return vim.fn.winnr() == vim.fn.winnr('j') end +---@return boolean local function at_left_edge() return vim.fn.winnr() == vim.fn.winnr('h') end +---@return boolean local function at_right_edge() return vim.fn.winnr() == vim.fn.winnr('l') end +---@param direction Direction +---@return WinPosition function M.win_position(direction) - if direction == 'left' or direction == 'right' then + if direction == Direction.left or direction == Direction.right then if at_left_edge() then - return win_pos.start + return WinPosition.start end if at_right_edge() then - return win_pos.last + return WinPosition.last end - return win_pos.middle + return WinPosition.middle end if at_top_edge() then - return win_pos.start + return WinPosition.start end if at_bottom_edge() then - return win_pos.last + return WinPosition.last end - return win_pos.middle + return WinPosition.middle end +---@param direction Direction +---@return WincmdResizeDirection local function compute_direction_vertical(direction) local current_pos = M.win_position(direction) - if current_pos == win_pos.start or current_pos == win_pos.middle then - return direction == 'down' and '+' or '-' + if current_pos == WinPosition.start or current_pos == WinPosition.middle then + return direction == Direction.down and WincmdResizeDirection.bigger or WincmdResizeDirection.smaller end - return direction == 'down' and '-' or '+' + return direction == Direction.down and WincmdResizeDirection.smaller or WincmdResizeDirection.bigger end +---@param direction Direction +---@return WincmdResizeDirection local function compute_direction_horizontal(direction) local current_pos = M.win_position(direction) local result - if current_pos == win_pos.start or current_pos == win_pos.middle then - result = direction == 'right' and '+' or '-' + if current_pos == WinPosition.start or current_pos == WinPosition.middle then + result = direction == Direction.right and WincmdResizeDirection.bigger or WincmdResizeDirection.smaller else - result = direction == 'right' and '-' or '+' + result = direction == Direction.right and WincmdResizeDirection.smaller or WincmdResizeDirection.bigger end local at_left = at_left_edge() local at_right = at_right_edge() -- special case - check if there is an ignored window to the left - if direction == 'right' and result == '+' and at_left and at_right then + if direction == Direction.right and result == WincmdResizeDirection.bigger and at_left and at_right then local cur_win = vim.api.nvim_get_current_win() - next_window(dir_keys.left, true) + next_window(DirectionKeys.left, true) if vim.tbl_contains(config.ignored_buftypes, vim.bo.buftype) or vim.tbl_contains(config.ignored_filetypes, vim.bo.filetype) then vim.api.nvim_set_current_win(cur_win) - result = '-' + result = WincmdResizeDirection.smaller end - elseif direction == 'left' and result == '-' and at_left and at_right then + elseif direction == Direction.left and result == WincmdResizeDirection.smaller and at_left and at_right then local cur_win = vim.api.nvim_get_current_win() - next_window(dir_keys.left, true) + next_window(DirectionKeys.left, true) if vim.tbl_contains(config.ignored_buftypes, vim.bo.buftype) or vim.tbl_contains(config.ignored_filetypes, vim.bo.filetype) then vim.api.nvim_set_current_win(cur_win) - result = '+' + result = WincmdResizeDirection.bigger end end return result end +---@param direction Direction +---@param amount number local function resize(direction, amount) amount = amount or config.default_amount -- if a full width window and horizontall resize check if we can resize with multiplexer - if (direction == 'left' or direction == 'right') and is_full_width() and mux.resize_pane(direction, amount) then + if + (direction == Direction.left or direction == Direction.right) + and is_full_width() + and mux.resize_pane(direction, amount) + then return end -- if a full height window and vertical resize check if we can resize with multiplexer - if (direction == 'down' or direction == 'up') and is_full_height() and mux.resize_pane(direction, amount) then + if + (direction == Direction.down or direction == Direction.up) + and is_full_height() + and mux.resize_pane(direction, amount) + then return end - if direction == 'down' or direction == 'up' then + if direction == Direction.down or direction == Direction.up then -- vertically local plus_minus = compute_direction_vertical(direction) local cur_win_pos = vim.api.nvim_win_get_position(0) vim.cmd(string.format('resize %s%s', plus_minus, amount)) - if M.win_position(direction) ~= win_pos.middle then + if M.win_position(direction) ~= WinPosition.middle then return end local new_win_pos = vim.api.nvim_win_get_position(0) local adjustment_plus_minus - if cur_win_pos[1] < new_win_pos[1] and plus_minus == '-' then - adjustment_plus_minus = '+' - elseif cur_win_pos[1] > new_win_pos[1] and plus_minus == '+' then - adjustment_plus_minus = '-' + if cur_win_pos[1] < new_win_pos[1] and plus_minus == WincmdResizeDirection.smaller then + adjustment_plus_minus = WincmdResizeDirection.bigger + elseif cur_win_pos[1] > new_win_pos[1] and plus_minus == WincmdResizeDirection.bigger then + adjustment_plus_minus = WincmdResizeDirection.smaller end if at_bottom_edge() then - if plus_minus == '+' then + if plus_minus == WincmdResizeDirection.bigger then vim.cmd(string.format('resize -%s', amount)) - next_window(dir_keys.down) + next_window(DirectionKeys.down) vim.cmd(string.format('resize -%s', amount)) else vim.cmd(string.format('resize +%s', amount)) - next_window(dir_keys.down) + next_window(DirectionKeys.down) vim.cmd(string.format('resize +%s', amount)) end return @@ -202,35 +241,37 @@ local function resize(direction, amount) if adjustment_plus_minus ~= nil then vim.cmd(string.format('resize %s%s', adjustment_plus_minus, amount)) - next_window(dir_keys.up) + next_window(DirectionKeys.up) vim.cmd(string.format('resize %s%s', adjustment_plus_minus, amount)) - next_window(dir_keys.down) + next_window(DirectionKeys.down) end else -- horizontally local plus_minus = compute_direction_horizontal(direction) local cur_win_pos = vim.api.nvim_win_get_position(0) vim.cmd(string.format('vertical resize %s%s', plus_minus, amount)) - if M.win_position(direction) ~= win_pos.middle then + if M.win_position(direction) ~= WinPosition.middle then return end local new_win_pos = vim.api.nvim_win_get_position(0) local adjustment_plus_minus - if cur_win_pos[2] < new_win_pos[2] and plus_minus == '-' then - adjustment_plus_minus = '+' - elseif cur_win_pos[2] > new_win_pos[2] and plus_minus == '+' then - adjustment_plus_minus = '-' + if cur_win_pos[2] < new_win_pos[2] and plus_minus == WincmdResizeDirection.smaller then + adjustment_plus_minus = WincmdResizeDirection.bigger + elseif cur_win_pos[2] > new_win_pos[2] and plus_minus == WincmdResizeDirection.bigger then + adjustment_plus_minus = WincmdResizeDirection.smaller end if adjustment_plus_minus ~= nil then vim.cmd(string.format('vertical resize %s%s', adjustment_plus_minus, amount)) - next_window(dir_keys.right) + next_window(DirectionKeys.right) vim.cmd(string.format('vertical resize %s%s', adjustment_plus_minus, amount)) - next_window(dir_keys.left) + next_window(DirectionKeys.left) end end end +---@param at_edge_and_moving_to_edge boolean +---@param dir_key DirectionKeys local function move_to_edge(at_edge_and_moving_to_edge, dir_key) -- if someone has more than 99999 windows then just LOL vim.api.nvim_set_current_win( @@ -238,9 +279,31 @@ local function move_to_edge(at_edge_and_moving_to_edge, dir_key) ) end -local function move_cursor(direction, same_row) +---@param direction Direction +---@param opts table +local function move_cursor(direction, opts) + -- backwards compatibility, if opts is a boolean, treat it as historical `same_row` argument + local same_row = config.move_cursor_same_row + local at_edge = config.at_edge + if type(opts) == 'boolean' then + same_row = opts + vim.deprecate( + string.format('smartsplits.move_cursor_%s(boolean)', direction), + string.format("smartsplits.move_cursor_%s({ same_row = boolean, at_edge = 'wrap'|'split'|'stop' })", direction), + 'smart-splits.nvim' + ) + elseif type(opts) == 'table' then + if opts.same_row ~= nil then + same_row = opts.same_row + end + + if opts.at_edge ~= nil then + at_edge = opts.at_edge + end + end + local offset = vim.fn.winline() + vim.api.nvim_win_get_position(0)[1] - local dir_key = dir_keys[direction] + local dir_key = DirectionKeys[direction] local at_right = at_right_edge() local at_left = at_left_edge() @@ -248,37 +311,46 @@ local function move_cursor(direction, same_row) local at_bottom = at_bottom_edge() -- are we at an edge and attempting to move in the direction of the edge we're already at? - local at_edge_and_moving_to_edge = (direction == 'left' and at_left) - or (direction == 'right' and at_right) - or (direction == 'up' and at_top) - or (direction == 'down' and at_bottom) + local at_edge_and_moving_to_edge = (direction == Direction.left and at_left) + or (direction == Direction.right and at_right) + or (direction == Direction.up and at_top) + or (direction == Direction.down and at_bottom) local at_any_edge = at_right or at_left or at_top or at_bottom -- if at the edge, and moving towards the edge, check if we can move with multiplexer - if at_any_edge and at_edge_and_moving_to_edge and mux.move_pane(direction, at_edge_and_moving_to_edge) then + if at_any_edge and at_edge_and_moving_to_edge and mux.move_pane(direction, at_edge_and_moving_to_edge, at_edge) then return end - if config.wrap_at_edge == false then - if - (at_right and direction == 'right') - or (at_left and direction == 'left') - or (at_top and direction == 'up') - or (at_bottom and direction == 'down') - then + if at_edge_and_moving_to_edge then + if at_edge == AtEdgeBehavior.stop then + return + elseif at_edge == AtEdgeBehavior.split then + if direction == Direction.left or direction == Direction.right then + vim.cmd('vsp') + if vim.opt.splitright and direction == Direction.left then + vim.cmd('wincmd h') + end + else + vim.cmd('sp') + if vim.opt.splitbelow and direction == Direction.up then + vim.cmd('wincmd k') + end + end return end + -- else, at_edge == AtEdgeBehavior.wrap, continue end if at_edge_and_moving_to_edge then - dir_key = dir_keys_reverse[direction] + dir_key = DirectionKeysReverse[direction] end move_to_edge(at_edge_and_moving_to_edge, dir_key) if - (direction == 'left' or direction == 'right') + (direction == Direction.left or direction == Direction.right) and (same_row or (same_row == nil and config.move_cursor_same_row)) then offset = offset - vim.api.nvim_win_get_position(0)[1] @@ -296,18 +368,20 @@ local function set_eventignore() vim.o.eventignore = eventignore end +---@param direction Direction +---@param opts table local function swap_bufs(direction, opts) opts = opts or {} local buf_1 = vim.api.nvim_get_current_buf() local win_1 = vim.api.nvim_get_current_win() - local dir_key = dir_keys[direction] - local at_edge_and_moving_to_edge = (direction == 'right' and at_right_edge()) - or (direction == 'left' and at_left_edge()) - or (direction == 'up' and at_top_edge()) - or (direction == 'down' and at_bottom_edge()) + local dir_key = DirectionKeys[direction] + local at_edge_and_moving_to_edge = (direction == Direction.right and at_right_edge()) + or (direction == Direction.left and at_left_edge()) + or (direction == Direction.up and at_top_edge()) + or (direction == Direction.down and at_bottom_edge()) if at_edge_and_moving_to_edge then - dir_key = dir_keys_reverse[direction] + dir_key = DirectionKeysReverse[direction] end move_to_edge(at_edge_and_moving_to_edge, dir_key) @@ -339,19 +413,19 @@ vim.tbl_map(function(direction) -- luacheck:ignore vim.o.eventignore = eventignore_orig end - M[string.format('move_cursor_%s', direction)] = function(same_row) + M[string.format('move_cursor_%s', direction)] = function(opts) is_resizing = false - pcall(move_cursor, direction, same_row) + pcall(move_cursor, direction, opts) end M[string.format('swap_buf_%s', direction)] = function(opts) is_resizing = false swap_bufs(direction, opts) end end, { - 'left', - 'right', - 'up', - 'down', + Direction.left, + Direction.right, + Direction.up, + Direction.down, }) return M diff --git a/lua/smart-splits/commands.lua b/lua/smart-splits/commands.lua index 57ab7a3..4d0917d 100644 --- a/lua/smart-splits/commands.lua +++ b/lua/smart-splits/commands.lua @@ -1,3 +1,5 @@ +local Direction = require('smart-splits.types').Direction + local function resize_handler(direction) return function(args) local amount @@ -21,13 +23,14 @@ end return { -- resize - { 'SmartResizeLeft', resize_handler('left'), { desc = 'smart-splits: resize left', nargs = '*' } }, - { 'SmartResizeRight', resize_handler('right'), { desc = 'smart-splits: resize right', nargs = '*' } }, - { 'SmartResizeUp', resize_handler('up'), { desc = 'smart-splits: resize up', nargs = '*' } }, - { 'SmartResizeDown', resize_handler('down'), { desc = 'smart-splits: resize down', nargs = '*' } }, + { 'SmartResizeLeft', resize_handler(Direction.left), { desc = 'smart-splits: resize left', nargs = '*' } }, + { 'SmartResizeRight', resize_handler(Direction.right), { desc = 'smart-splits: resize right', nargs = '*' } }, + { 'SmartResizeUp', resize_handler(Direction.up), { desc = 'smart-splits: resize up', nargs = '*' } }, + { 'SmartResizeDown', resize_handler(Direction.down), { desc = 'smart-splits: resize down', nargs = '*' } }, -- move - { 'SmartCursorMoveLeft', move_handler('left'), { desc = 'smart-splits: move cursor left', nargs = '*' } }, - { 'SmartCursorMoveRight', move_handler('right'), { desc = 'smart-splits: move cursor right', nargs = '*' } }, + { 'SmartCursorMoveLeft', move_handler(Direction.left), { desc = 'smart-splits: move cursor left', nargs = '*' } }, + { 'SmartCursorMoveRight', move_handler(Direction.right), { desc = 'smart-splits: move cursor right', nargs = '*' } }, + -- same_row does not apply to up/down { 'SmartCursorMoveUp', require('smart-splits').move_cursor_up, { desc = 'smart-splits: move cursor up' } }, { 'SmartCursorMoveDown', require('smart-splits').move_cursor_down, { desc = 'smart-splits: move cursor down' } }, -- resize mode diff --git a/lua/smart-splits/config.lua b/lua/smart-splits/config.lua index 83cbc9d..58b3acd 100644 --- a/lua/smart-splits/config.lua +++ b/lua/smart-splits/config.lua @@ -1,3 +1,33 @@ +local types = require('smart-splits.types') +local AtEdgeBehavior = types.AtEdgeBehavior +local Multiplexer = types.Multiplexer + +---@class SmartResizeModeHooks +---@field on_enter fun()|nil +---@field on_leave fun()|nil + +---@class SmartResizeModeConfig +---@field quit_key string +---@field resize_keys string[] +---@field silent boolean +---@field hooks SmartResizeModeHooks + +---@class SmartSplitsConfig +---@field ignored_buftypes string[] +---@field ignored_filetypes string[] +---@field default_amount number +---@field at_edge AtEdgeBehavior +---@field move_cursor_same_row boolean +---@field cursor_follows_swapped_bufs boolean +---@field resize_mode SmartResizeModeConfig +---@field ignored_events string[] +---@field multiplexer_integration MultiplexerType|false +---@field disable_multiplexer_nav_when_zoomed boolean +---@field kitty_password string|nil +---@field setup fun(cfg:table) +---@field set_default_multiplexer fun() + +---@type SmartSplitsConfig local config = { ignored_buftypes = { 'nofile', @@ -8,7 +38,7 @@ local config = { 'NvimTree', }, default_amount = 3, - wrap_at_edge = true, + at_edge = AtEdgeBehavior.wrap, move_cursor_same_row = false, cursor_follows_swapped_bufs = false, resize_mode = { @@ -24,10 +54,12 @@ local config = { 'BufEnter', 'WinEnter', }, - multiplexer_integration = nil, + multiplexer_integration = nil, ---@diagnostic disable-line this gets computed during startup unless disabled by user disable_multiplexer_nav_when_zoomed = true, + kitty_password = nil, } +---@type SmartSplitsConfig local M = setmetatable({}, { __index = function(_, key) return config[key] @@ -44,21 +76,27 @@ function M.set_default_multiplexer() end if vim.env.TERM_PROGRAM == 'tmux' then - config.multiplexer_integration = 'tmux' + config.multiplexer_integration = Multiplexer.tmux elseif vim.env.TERM_PROGRAM == 'WezTerm' then - config.multiplexer_integration = 'wezterm' - -- Kitty doesn't use $TERM_PROGRAM + config.multiplexer_integration = Multiplexer.wezterm + -- Kitty doesn't use $TERM_PROGRAM, and also requires remote control enabled anyway elseif vim.env.KITTY_LISTEN_ON ~= nil then - config.multiplexer_integration = 'kitty' + config.multiplexer_integration = Multiplexer.kitty end end function M.setup(new_config) config = vim.tbl_deep_extend('force', config, new_config or {}) + -- check deprecated settings + if config.tmux_integration then - vim.deprecate('config.tmux_integration = true', "config.multiplexer_integration = 'tmux'", 'smart-splits.nvim') - config.multiplexer_integration = 'tmux' + vim.deprecate( + 'config.tmux_integration = true', + "config.multiplexer_integration = 'tmux'|'wezterm'|'kitty'", + 'smart-splits.nvim' + ) + config.multiplexer_integration = Multiplexer.tmux elseif config.tmux_integration == false then config.multiplexer_integration = false end @@ -71,6 +109,11 @@ function M.setup(new_config) ) config.disable_multiplexer_nav_when_zoomed = true end + + if config.wrap_at_edge == false or config.wrap_at_edge == true then + config.at_edge = config.wrap_at_edge == true and AtEdgeBehavior.wrap or AtEdgeBehavior.stop + vim.deprecate('config.wrap_at_edge', "config.at_edge = 'wrap'|'split'|'stop'", 'smart-splits.nvim') + end end return M diff --git a/lua/smart-splits/init.lua b/lua/smart-splits/init.lua index 3c88737..34f277c 100644 --- a/lua/smart-splits/init.lua +++ b/lua/smart-splits/init.lua @@ -1,3 +1,5 @@ +local Direction = require('smart-splits.types').Direction + local M = {} function M.setup(config) @@ -12,10 +14,10 @@ vim.tbl_map(function(direction) M[move_key] = require('smart-splits.api')[move_key] M[swap_buf_key] = require('smart-splits.api')[swap_buf_key] end, { - 'left', - 'right', - 'up', - 'down', + Direction.left, + Direction.right, + Direction.up, + Direction.down, }) M.start_resize_mode = require('smart-splits.resize-mode').start_resize_mode diff --git a/lua/smart-splits/mux/init.lua b/lua/smart-splits/mux/init.lua index 602f972..c241457 100644 --- a/lua/smart-splits/mux/init.lua +++ b/lua/smart-splits/mux/init.lua @@ -1,10 +1,13 @@ local config = require('smart-splits.config') +local types = require('smart-splits.types') +local Direction = types.Direction +local AtEdgeBehavior = types.AtEdgeBehavior local directions_reverse = { - left = 'right', - right = 'left', - up = 'down', - down = 'up', + [Direction.left] = Direction.right, + [Direction.right] = Direction.left, + [Direction.up] = Direction.down, + [Direction.down] = Direction.up, } local function move_multiplexer_inner(direction, multiplexer) @@ -38,16 +41,8 @@ local M = {} ---Get the currently configured multiplexer ---@return Multiplexer|nil function M.get() - local mux = config.multiplexer_integration - if mux == 'tmux' then - return require('smart-splits.mux.tmux') - elseif mux == 'wezterm' then - return require('smart-splits.mux.wezterm') - elseif mux == 'kitty' then - return require('smart-splits.mux.kitty') - else - return nil - end + local ok, mux = pcall(require, string.format('smart-splits.mux.%s', config.multiplexer_integration)) + return ok and mux or nil end ---Check if any multiplexer is enabled @@ -59,14 +54,16 @@ end ---Try moving with multiplexer ---@param direction Direction direction to move ---@param will_wrap boolean whether to wrap around edge +---@param at_edge AtEdgeBehavior behavior at edge ---@return boolean whether we moved with multiplexer or not -function M.move_pane(direction, will_wrap) +function M.move_pane(direction, will_wrap, at_edge) + at_edge = at_edge or config.at_edge local multiplexer = M.get() if not multiplexer or not multiplexer.is_in_session() then return false end - if config.wrap_at_edge == false and multiplexer.current_pane_at_edge(direction) then + if at_edge ~= AtEdgeBehavior.wrap and multiplexer.current_pane_at_edge(direction) then return false end diff --git a/lua/smart-splits/mux/kitty.lua b/lua/smart-splits/mux/kitty.lua index 59b3920..e284484 100644 --- a/lua/smart-splits/mux/kitty.lua +++ b/lua/smart-splits/mux/kitty.lua @@ -1,10 +1,11 @@ +local Direction = require('smart-splits.types').Direction local utils = require('smart-splits.utils') local dir_keys_kitty = { - left = 'left', - right = 'right', - up = 'top', - down = 'bottom', + [Direction.left] = 'left', + [Direction.right] = 'right', + [Direction.up] = 'top', + [Direction.down] = 'bottom', } local function kitty_exec(args) diff --git a/lua/smart-splits/mux/tmux.lua b/lua/smart-splits/mux/tmux.lua index 2cb99cf..499054e 100644 --- a/lua/smart-splits/mux/tmux.lua +++ b/lua/smart-splits/mux/tmux.lua @@ -1,8 +1,12 @@ +local types = require('smart-splits.types') +local Direction = types.Direction +local AtEdgeBehavior = types.AtEdgeBehavior + local dir_keys_tmux = { - left = 'L', - right = 'R', - up = 'U', - down = 'D', + [Direction.left] = 'L', + [Direction.right] = 'R', + [Direction.up] = 'U', + [Direction.down] = 'D', } local function get_socket_path() @@ -35,22 +39,20 @@ function M.current_pane_at_edge(direction) return false end - direction = dir_keys_tmux[direction] - - local wrap_at_edge = require('smart-splits.config').wrap_at_edge + local wrap_at_edge = require('smart-splits.config').at_edge == AtEdgeBehavior.wrap local edge local op - if direction == 'U' then + if direction == Direction.up then edge = 'top' op = wrap_at_edge and '<=' or '<' - elseif direction == 'D' then + elseif direction == Direction.down then edge = 'bottom' op = wrap_at_edge and '>' or '>=' - elseif direction == 'L' then + elseif direction == Direction.left then edge = 'left' op = wrap_at_edge and '<=' or '<' - elseif direction == 'R' then + elseif direction == Direction.right then edge = 'right' op = wrap_at_edge and '>' or '>=' else diff --git a/lua/smart-splits/mux/wezterm.lua b/lua/smart-splits/mux/wezterm.lua index d5ff5b3..a255a8d 100644 --- a/lua/smart-splits/mux/wezterm.lua +++ b/lua/smart-splits/mux/wezterm.lua @@ -1,8 +1,10 @@ +local Direction = require('smart-splits.types').Direction + local dir_keys_wezterm = { - left = 'Left', - right = 'Right', - down = 'Down', - up = 'Up', + [Direction.left] = 'Left', + [Direction.right] = 'Right', + [Direction.down] = 'Down', + [Direction.up] = 'Up', } local function wezterm_exec(cmd) diff --git a/lua/smart-splits/types.lua b/lua/smart-splits/types.lua index 03738f1..679d53f 100644 --- a/lua/smart-splits/types.lua +++ b/lua/smart-splits/types.lua @@ -1,5 +1,3 @@ ----@alias Direction 'left'|'right'|'up'|'down' - ---@class Multiplexer ---@field current_pane_id fun():number|nil ---@field current_pane_at_edge fun(direction:Direction):boolean @@ -7,3 +5,40 @@ ---@field current_pane_is_zoomed fun():boolean ---@field next_pane fun(direction:Direction):boolean ---@field resize_pane fun(direction:Direction, amount:number):boolean + +---@alias Direction 'left'|'right'|'up'|'down' + +---@alias AtEdgeBehavior 'split'|'wrap'|'stop' + +---@alias MultiplexerType 'tmux'|'wezterm'|'kitty' + +local M = { + Direction = { + ---@type Direction + left = 'left', + ---@type Direction + right = 'right', + ---@type Direction + up = 'up', + ---@type Direction + down = 'down', + }, + AtEdgeBehavior = { + ---@type AtEdgeBehavior + split = 'split', + ---@type AtEdgeBehavior + wrap = 'wrap', + ---@type AtEdgeBehavior + stop = 'stop', + }, + Multiplexer = { + ---@type MultiplexerType + tmux = 'tmux', + ---@type MultiplexerType + wezterm = 'wezterm', + ---@type MultiplexerType + kitty = 'kitty', + }, +} + +return M