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

fix(split): vsplits integration resize inconsistencies #391

Merged
merged 5 commits into from
Sep 9, 2024
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
2 changes: 1 addition & 1 deletion lua/no-neck-pain/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function NoNeckPain.resize(width)
_G.NoNeckPain.config = vim.tbl_deep_extend("keep", { width = width }, _G.NoNeckPain.config)
end

main.init("public_api_resize", false)
main.init("public_api_resize")
end

--- Toggles the config `${side}.enabled` and re-inits the plugin.
Expand Down
166 changes: 79 additions & 87 deletions lua/no-neck-pain/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,8 @@ end

--- Creates side buffers and set the tab state, focuses the `curr` window if required.
---@param scope string: internal identifier for logging purposes.
---@param go_to_curr boolean?: whether we should re-focus the `curr` window.
---@private
function main.init(scope, go_to_curr)
function main.init(scope)
if not state.is_active_tab_registered(state) then
error("called the internal `init` method on a `nil` tab.")
end
Expand All @@ -102,50 +101,45 @@ function main.init(scope, go_to_curr)
state.get_side_id(state, "curr")
)

-- if we do not have side buffers, we must ensure we only trigger a focus if we re-create them
local had_side_buffers = true
if
not state.is_side_enabled_or_valid(state, "left")
or not state.is_side_enabled_or_valid(state, "right")
then
had_side_buffers = false
if state.consume_redraw(state) then
ui.move_sides(string.format("%s:consume_redraw", scope))
end

ui.create_side_buffers()

if state.consume_redraw(state) then
ui.move_sides(string.format("%s:consume_redraw", scope))
end
local current_win = vim.api.nvim_get_current_win()

if
go_to_curr
or (not had_side_buffers and state.check_sides(state, "or", true))
or (
(
state.is_side_the_active_win(state, "left")
or state.is_side_the_active_win(state, "right")
)
) and state.get_previously_focused_win(state) ~= current_win
then
log.debug(scope, "re-routing focus to curr")
log.debug(
scope,
"rerouting focus of %d to %s",
current_win,
state.get_previously_focused_win(state)
)

vim.api.nvim_set_current_win(state.get_side_id(state, "curr"))
end

-- if we still have side buffers open at this point, and we have vsplit opened,
-- there might be width issues so we the resize_win opened vsplits.
if state.check_sides(state, "or", true) and state.get_columns(state) > 1 then
log.debug("resize_win", "have %d columns", state.get_columns(state))

for _, win in pairs(state.get_unregistered_wins(state, scope)) do
ui.resize_win(win, _G.NoNeckPain.config.width, string.format("win:%d", win))
if
vim.api.nvim_win_is_valid(state.get_previously_focused_win(state))
and state.active_tab
== vim.api.nvim_win_get_tabpage(state.get_previously_focused_win(state))
then
vim.api.nvim_set_current_win(state.get_previously_focused_win(state))
end
end

if not had_side_buffers then
ui.resize_win(
state.get_side_id(state, "curr"),
_G.NoNeckPain.config.width,
string.format("win:%d", state.get_side_id(state, "curr"))
)
end
-- if we still have side buffers and something else opened than the `curr`
-- there might be width issues so we the resize_win opened vsplits.
if
state.check_sides(state, "or", true)
and state.get_columns(state) > state.get_nb_sides(state) + 1
then
state.walk_layout(state, scope, vim.fn.winlayout(state.active_tab), false, true)
end

state.save(state)
Expand All @@ -170,7 +164,7 @@ function main.enable(scope)

state.set_side_id(state, vim.api.nvim_get_current_win(), "curr")
state.scan_layout(state, scope)
main.init(scope, true)
main.init(scope)
state.scan_layout(state, scope)

vim.api.nvim_create_autocmd({ "VimResized" }, {
Expand Down Expand Up @@ -290,7 +284,7 @@ function main.enable(scope)

log.debug(s, "re-routing to %d", wins[1])

return main.init(s, true)
return main.init(s)
end

if
Expand Down Expand Up @@ -327,66 +321,64 @@ function main.enable(scope)
desc = "keeps track of the state after closing windows and deleting buffers",
})

if _G.NoNeckPain.config.autocmds.skipEnteringNoNeckPainBuffer then
vim.api.nvim_create_autocmd({ "WinLeave" }, {
callback = function(p)
vim.schedule(function()
p.event = string.format("%s:skip_entering", p.event)
if
not state.is_active_tab_registered(state)
or event.skip()
or state.get_scratchPad(state)
then
return log.debug(p.event, "skip")
end
vim.api.nvim_create_autocmd({ "WinLeave" }, {
callback = function(p)
vim.schedule(function()
p.event = string.format("%s:skip_entering", p.event)
if not state.is_active_tab_registered(state) or event.skip() then
return log.debug(p.event, "skip")
end

local current_side = vim.api.nvim_get_current_win()
local other_side = state.get_side_id(state, "right")
local left_id = state.get_side_id(state, "left")
local right_id = state.get_side_id(state, "right")

if current_side == left_id then
other_side = right_id
elseif current_side == right_id then
other_side = left_id
else
state.set_previously_focused_win(state, vim.api.nvim_get_current_win())
return
end
if not _G.NoNeckPain.config.autocmds.skipEnteringNoNeckPainBuffer then
state.set_previously_focused_win(state, vim.api.nvim_get_current_win())
return
end

-- we need to know if the user navigates from ltr or rtl
-- so we keep track of the encounter of prev,curr to determine
-- the next valid window to focus
if state.get_scratchPad(state) then
return log.debug(p.event, "skip because scratchpad is enabled")
end

local wins = vim.api.nvim_list_wins()
local idx
local current_side = vim.api.nvim_get_current_win()
local other_side = state.get_side_id(state, "right")
local left_id = state.get_side_id(state, "left")
local right_id = state.get_side_id(state, "right")

if current_side == left_id then
other_side = right_id
elseif current_side == right_id then
other_side = left_id
else
state.set_previously_focused_win(state, vim.api.nvim_get_current_win())
return
end

for i = 1, #wins do
if api.is_side_id(current_side, wins[i]) then
idx = api.find_next_side_idx(i - 1, -1, wins, current_side, other_side)
break
elseif api.is_side_id(state.get_previously_focused_win(state), wins[i]) then
idx = api.find_next_side_idx(i + 1, 1, wins, current_side, other_side)
break
end
end
-- we need to know if the user navigates from ltr or rtl
-- so we keep track of the encounter of prev,curr to determine
-- the next valid window to focus

if idx then
vim.api.nvim_set_current_win(wins[idx])
local wins = vim.api.nvim_list_wins()
local idx

return log.debug(
p.event,
"rerouted focus of %d to %d",
current_side,
wins[idx]
)
for i = 1, #wins do
if api.is_side_id(current_side, wins[i]) then
idx = api.find_next_side_idx(i - 1, -1, wins, current_side, other_side)
break
elseif api.is_side_id(state.get_previously_focused_win(state), wins[i]) then
idx = api.find_next_side_idx(i + 1, 1, wins, current_side, other_side)
break
end
end)
end,
group = augroup_name,
desc = "Entering a no-neck-pain side buffer skips to the next available buffer",
})
end
end

if idx then
vim.api.nvim_set_current_win(wins[idx])

return log.debug(p.event, "rerouted focus of %d to %d", current_side, wins[idx])
end
end)
end,
group = augroup_name,
desc = "Keeps track of the last focused win, and re-route if necessary",
})

state.save(state)
end
Expand Down
57 changes: 48 additions & 9 deletions lua/no-neck-pain/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,18 @@ function state:check_sides(condition, expected)
and self.is_side_enabled_and_valid(self, "right") == expected
end

--- Returns the number of enabled and valid sides.
---
---@return number
---@private
function state:get_nb_sides()
if self.is_side_enabled(self, "left") and self.is_side_enabled(self, "right") then
return 2
end

return 1
end

--- Gets wins that are not relative or main wins.
---
---@param scope string: caller of the method.
Expand All @@ -348,7 +360,6 @@ end
function state:get_unregistered_wins(scope)
return vim.tbl_filter(function(win)
return not api.is_relative_window(win)
and win ~= self.get_side_id(self, "curr")
and win ~= self.get_side_id(self, "left")
and win ~= self.get_side_id(self, "right")
and not self.is_supported_integration(self, scope, win)
Expand All @@ -358,6 +369,20 @@ end
----- layout =======================================================
---@private

--- Resizes a window if it's valid.
---
---@param id number: the id of the window.
---@param width number: the width to apply to the window.
---@param side "left"|"right"|"curr"|"unregistered": the side of the window being resized, used for logging only.
---@private
function state:resize_win(id, width, side)
log.debug(side, "resizing %d with padding %d", id, width)

if vim.api.nvim_win_is_valid(id) then
vim.api.nvim_win_set_width(id, width)
end
end

--- Gets the columns count in the current layout.
---
---@return table: the columns window IDs.
Expand Down Expand Up @@ -411,8 +436,9 @@ end
---@param scope string: the caller of the method.
---@param tree table: the tree to walk in.
---@param has_col_parent boolean: whether or not the previous walked tree was a column.
---@param resize_only boolean?: walks the layout in order to find columns, but only resize 1 window of each column
---@private
function state:walk_layout(scope, tree, has_col_parent)
function state:walk_layout(scope, tree, has_col_parent, resize_only)
-- col -- represents a vertical association of window, e.g. { { "leaf", int }, { "col", { ... } }, { "row", { ...} } }
-- row -- represents an horizontal association of window, e.g { { "leaf", int }, { "col", { ... } }, { "row", { ...} } }
-- leaf -- represents a window, e.g. { "leaf", int }
Expand All @@ -425,16 +451,29 @@ function state:walk_layout(scope, tree, has_col_parent)
for idx, leaf in ipairs(tree) do
if leaf == "row" then
local leafs = tree[idx + 1]
-- if on a row we were on a col, then it means one iteam of the row must be of the same width as a col one
if has_col_parent and vim.tbl_count(leafs) > 1 then
table.remove(leafs, 1)
if not resize_only then
-- if on a row we were on a col, then it means one iteam of the row must be of the same width as a col one
if has_col_parent and vim.tbl_count(leafs) > 1 then
table.remove(leafs, 1)
end
self.set_layout_windows(self, scope, leafs)
else
for _, sub_leaf in ipairs(leafs) do
local id = sub_leaf[2]
if
sub_leaf[1] == "leaf"
and self.get_side_id(self, "left") ~= id
and self.get_side_id(self, "right") ~= id
then
self.resize_win(self, id, _G.NoNeckPain.config.width, "unregistered")
end
end
end
self.set_layout_windows(self, scope, leafs)
self.walk_layout(self, scope, tree[idx + 1], false)
self.walk_layout(self, scope, tree[idx + 1], false, resize_only)
elseif leaf == "col" then
self.walk_layout(self, scope, tree[idx + 1], true)
self.walk_layout(self, scope, tree[idx + 1], true, resize_only)
elseif type(leaf) == "table" and type(leaf[1]) == "string" then
self.walk_layout(self, scope, leaf, has_col_parent)
self.walk_layout(self, scope, leaf, has_col_parent, resize_only)
end
end
end
Expand Down
Loading