Skip to content

Commit

Permalink
fix(split): vsplits integration resize inconsistencies (#391)
Browse files Browse the repository at this point in the history
## 📃 Summary

rework the way we compute the size of side buffers by properly assessing
the number of valid sides, which was causing weird layout when 2 were
actives.

also resize the vsplits based on the layout, rather than guessing which
windows are vsplits, this allows a super consistent ui layout

todo: closing an integration doesn't restore the position of the `curr`
buffer, but the width is correct

closes #389
  • Loading branch information
shortcuts authored Sep 9, 2024
1 parent 6fa9566 commit 09736f4
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 153 deletions.
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

0 comments on commit 09736f4

Please sign in to comment.