Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): at_edge may now be a user-defined function #87

Merged
merged 3 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ require('smart-splits').setup({
-- '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'
-- 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
-- },
-- 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',
Expand Down
87 changes: 50 additions & 37 deletions lua/smart-splits/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ local function at_right_edge()
return vim.fn.winnr() == vim.fn.winnr('l')
end

---@param direction Direction
---@param direction SmartSplitsDirection
---@return WinPosition
function M.win_position(direction)
if direction == Direction.left or direction == Direction.right then
Expand All @@ -139,7 +139,7 @@ function M.win_position(direction)
return WinPosition.middle
end

---@param direction Direction
---@param direction SmartSplitsDirection
---@return WincmdResizeDirection
local function compute_direction_vertical(direction)
local current_pos = M.win_position(direction)
Expand All @@ -150,7 +150,7 @@ local function compute_direction_vertical(direction)
return direction == Direction.down and WincmdResizeDirection.smaller or WincmdResizeDirection.bigger
end

---@param direction Direction
---@param direction SmartSplitsDirection
---@return WincmdResizeDirection
local function compute_direction_horizontal(direction)
local current_pos = M.win_position(direction)
Expand Down Expand Up @@ -189,7 +189,7 @@ local function compute_direction_horizontal(direction)
return result
end

---@param direction Direction
---@param direction SmartSplitsDirection
---@param amount number
local function resize(direction, amount)
amount = amount or config.default_amount
Expand Down Expand Up @@ -273,16 +273,31 @@ local function resize(direction, amount)
end
end

---@param at_edge_and_moving_to_edge boolean
---@param will_wrap boolean
---@param dir_key DirectionKeys
local function move_to_edge(at_edge_and_moving_to_edge, dir_key)
local function next_win_or_wrap(will_wrap, dir_key)
-- if someone has more than 99999 windows then just LOL
vim.api.nvim_set_current_win(
vim.fn.win_getid(vim.fn.winnr(string.format('%s%s', at_edge_and_moving_to_edge and '99999' or '1', dir_key)))
vim.fn.win_getid(vim.fn.winnr(string.format('%s%s', will_wrap and '99999' or '1', dir_key)))
)
end

---@param direction Direction
---@param direction SmartSplitsDirection
local function split_edge(direction)
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
end

---@param direction SmartSplitsDirection
---@param opts table
local function move_cursor(direction, opts)
-- backwards compatibility, if opts is a boolean, treat it as historical `same_row` argument
Expand Down Expand Up @@ -314,19 +329,32 @@ local function move_cursor(direction, opts)
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 == Direction.left and at_left)
local will_wrap = (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)

if at_edge_and_moving_to_edge then
if will_wrap then
-- if we can move with mux, then we're good
if mux.move_pane(direction, at_edge_and_moving_to_edge, at_edge) then
if mux.move_pane(direction, will_wrap, at_edge) then
return
end

-- otherwise check at_edge behavior
if at_edge == AtEdgeBehavior.stop then
if type(at_edge) == 'function' then
local ctx = { ---@type SmartSplitsContext
mux = mux.get(),
direction = direction,
split = function()
split_edge(direction)
end,
wrap = function()
next_win_or_wrap(will_wrap, DirectionKeysReverse[direction])
end,
}
at_edge(ctx)
return
elseif at_edge == AtEdgeBehavior.stop then
return
elseif at_edge == AtEdgeBehavior.split then
-- if at_edge = 'split' and we're in an ignored buffer, just stop
Expand All @@ -337,32 +365,17 @@ local function move_cursor(direction, opts)
return
end

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
split_edge(direction)
return
else -- at_edge == AtEdgeBehavior.wrap
-- reverse direction and continue
dir_key = DirectionKeysReverse[direction]
end
-- else, at_edge == AtEdgeBehavior.wrap, continue
end

if at_edge_and_moving_to_edge then
dir_key = DirectionKeysReverse[direction]
end

move_to_edge(at_edge_and_moving_to_edge, dir_key)
next_win_or_wrap(will_wrap, dir_key)

if
(direction == Direction.left or direction == Direction.right)
and (same_row or (same_row == nil and config.move_cursor_same_row))
then
if (direction == Direction.left or direction == Direction.right) and same_row then
offset = offset - vim.api.nvim_win_get_position(0)[1]
vim.cmd('normal! ' .. offset .. 'H')
end
Expand All @@ -378,23 +391,23 @@ local function set_eventignore()
vim.o.eventignore = eventignore
end

---@param direction Direction
---@param direction SmartSplitsDirection
---@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 = DirectionKeys[direction]
local at_edge_and_moving_to_edge = (direction == Direction.right and at_right_edge())
local will_wrap = (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
if will_wrap then
dir_key = DirectionKeysReverse[direction]
end

move_to_edge(at_edge_and_moving_to_edge, dir_key)
next_win_or_wrap(will_wrap, dir_key)
local buf_2 = vim.api.nvim_get_current_buf()
local win_2 = vim.api.nvim_get_current_win()

Expand Down
4 changes: 2 additions & 2 deletions lua/smart-splits/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ local Multiplexer = types.Multiplexer
---@field ignored_buftypes string[]
---@field ignored_filetypes string[]
---@field default_amount number
---@field at_edge AtEdgeBehavior
---@field at_edge SmartSplitsAtEdgeBehavior
---@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 multiplexer_integration SmartSplitsMultiplexerType|false
---@field disable_multiplexer_nav_when_zoomed boolean
---@field kitty_password string|nil
---@field setup fun(cfg:table)
Expand Down
16 changes: 12 additions & 4 deletions lua/smart-splits/mux/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,16 @@ end
local M = {}

---Get the currently configured multiplexer
---@return Multiplexer|nil
---@return SmartSplitsMultiplexer|nil
function M.get()
if
config.multiplexer_integration == nil
or config.multiplexer_integration == false
or #tostring(config.multiplexer_integration or '') == 0
then
return nil
end

local ok, mux = pcall(require, string.format('smart-splits.mux.%s', config.multiplexer_integration))
return ok and mux or nil
end
Expand All @@ -52,9 +60,9 @@ function M.is_enabled()
end

---Try moving with multiplexer
---@param direction Direction direction to move
---@param direction SmartSplitsDirection direction to move
---@param will_wrap boolean whether to wrap around edge
---@param at_edge AtEdgeBehavior behavior at edge
---@param at_edge SmartSplitsAtEdgeBehavior behavior at edge
---@return boolean whether we moved with multiplexer or not
function M.move_pane(direction, will_wrap, at_edge)
at_edge = at_edge or config.at_edge
Expand All @@ -80,7 +88,7 @@ function M.move_pane(direction, will_wrap, at_edge)
end

---Try resizing with multiplexer
---@param direction Direction direction to resize
---@param direction SmartSplitsDirection direction to resize
---@param amount number amount to resize
---@return boolean whether we resized with multiplexer or not
function M.resize_pane(direction, amount)
Expand Down
4 changes: 3 additions & 1 deletion lua/smart-splits/mux/kitty.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ local function kitty_exec(args)
return vim.fn.system(arguments)
end

---@type Multiplexer
---@type SmartSplitsMultiplexer
local M = {}

M.type = 'kitty'

function M.current_pane_id()
local output = kitty_exec({ 'ls' })
local kitty_info = vim.json.decode(output)
Expand Down
4 changes: 3 additions & 1 deletion lua/smart-splits/mux/tmux.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ local function tmux_exec(cmd, as_list)
return vim.fn.system(cmd_str)
end

---@type Multiplexer
---@type SmartSplitsMultiplexer
local M = {}

M.type = 'tmux'

function M.current_pane_at_edge(direction)
if not M.is_in_session() then
return false
Expand Down
4 changes: 3 additions & 1 deletion lua/smart-splits/mux/wezterm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ local function current_pane_info()
return nil
end

---@type Multiplexer
---@type SmartSplitsMultiplexer
local M = {}

M.type = 'wezterm'

function M.current_pane_id()
local current_pane = current_pane_info()
-- uses API that requires newest version of Wezterm
Expand Down
40 changes: 23 additions & 17 deletions lua/smart-splits/types.lua
Original file line number Diff line number Diff line change
@@ -1,42 +1,48 @@
---@class Multiplexer
---@class SmartSplitsMultiplexer
---@field current_pane_id fun():number|nil
---@field current_pane_at_edge fun(direction:Direction):boolean
---@field current_pane_at_edge fun(direction:SmartSplitsDirection):boolean
---@field is_in_session fun():boolean
---@field current_pane_is_zoomed fun():boolean
---@field next_pane fun(direction:Direction):boolean
---@field resize_pane fun(direction:Direction, amount:number):boolean
---@field next_pane fun(direction:SmartSplitsDirection):boolean
---@field resize_pane fun(direction:SmartSplitsDirection, amount:number):boolean
---@field type SmartSplitsMultiplexerType

---@alias Direction 'left'|'right'|'up'|'down'
---@alias SmartSplitsDirection 'left'|'right'|'up'|'down'

---@alias AtEdgeBehavior 'split'|'wrap'|'stop'
---@alias SmartSplitsAtEdgeBehavior 'split'|'wrap'|'stop'|function

---@alias MultiplexerType 'tmux'|'wezterm'|'kitty'
---@alias SmartSplitsMultiplexerType 'tmux'|'wezterm'|'kitty'

---@class SmartSplitsContext
---@field mux SmartSplitsMultiplexer|nil Multiplexer API, if one is currently in use
---@field direction SmartSplitsDirection Which direction you're moving (also indicates edge your cursor is currently at)
---@field split fun() Utility function to split the window into the current direction

local M = {
Direction = {
---@type Direction
---@type SmartSplitsDirection
left = 'left',
---@type Direction
---@type SmartSplitsDirection
right = 'right',
---@type Direction
---@type SmartSplitsDirection
up = 'up',
---@type Direction
---@type SmartSplitsDirection
down = 'down',
},
AtEdgeBehavior = {
---@type AtEdgeBehavior
---@type SmartSplitsAtEdgeBehavior
split = 'split',
---@type AtEdgeBehavior
---@type SmartSplitsAtEdgeBehavior
wrap = 'wrap',
---@type AtEdgeBehavior
---@type SmartSplitsAtEdgeBehavior
stop = 'stop',
},
Multiplexer = {
---@type MultiplexerType
---@type SmartSplitsMultiplexerType
tmux = 'tmux',
---@type MultiplexerType
---@type SmartSplitsMultiplexerType
wezterm = 'wezterm',
---@type MultiplexerType
---@type SmartSplitsMultiplexerType
kitty = 'kitty',
},
}
Expand Down