diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index c3337e2..ef682cb 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -167,9 +167,11 @@ function N.enable(scope) then S.refreshTabs(S, S.activeTab) D.log(p.event, "tab %d is now inactive", S.activeTab) - else - D.log(p.event, "tab %d left", S.activeTab) + + return end + + D.log(p.event, "tab %d left", S.activeTab) end) end, group = augroupName, @@ -271,18 +273,20 @@ function N.enable(scope) vim.api.nvim_create_autocmd({ "WinClosed", "BufDelete" }, { callback = function(p) vim.schedule(function() - if E.skip(nil) or not S.hasVSplits(S) or W.stateWinsActive() then + vim.print(E.skip(nil)) + vim.print(not S.hasVSplits(S)) + if E.skip(nil) or not S.hasVSplits(S) then return end - S.refreshVSplits(S) - -- we keep track if curr have been closed because if it's the case, -- the focus will be on a side buffer which is wrong local haveCloseCurr = false -- if curr is not valid anymore, we focus the first valid split and remove it from the state if not vim.api.nvim_win_is_valid(S.getSideID(S, "curr")) then + D.log(p.event, "curr has been closed") + -- if neither curr and splits are remaining valids, we just disable if not S.hasVSplits(S) then return N.disable(p.event) @@ -290,17 +294,12 @@ function N.enable(scope) haveCloseCurr = true - for _, split in pairs(S.tabs[S.activeTab].wins.splits) do - if vim.api.nvim_win_is_valid(split.id) then - S.setSideID(S, split.id, "curr") - S.refreshSplits(S) - break - end - end + -- TODO: find an other win if curr is closed end - -- we only restore focus on curr if there's no split left - N.init(p.event, haveCloseCurr or not S.hasVSplits(S)) + if S.checkSides(S, "or", true) then + N.init(p.event, haveCloseCurr or not S.hasVSplits(S)) + end end) end, group = augroupName, diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index b7004e3..e9e69f9 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -126,6 +126,14 @@ function State:reopenIntegration() end end +---Gets all integrations. +--- +---@return table: the integration infos. +---@private +function State:getIntegrations() + return self.tabs[self.activeTab].wins.integrations +end + ---Gets the integration with the given `win` if it's already registered. --- ---@param id integer: the integration to search for. @@ -518,39 +526,54 @@ function State:increaseVSplits(nb) self.tabs[self.activeTab].wins.vsplits = self.tabs[self.activeTab].wins.vsplits + nb end -function State:iterateOverLayout(parent, curr) - local vsplit = false - parent = parent or false - +---Recursively iterates over the `winlayout` until it has computed every column present in the UI. +--- +---When we find a `row`, we set `vsplit` to true, the next element will always be a `table` so once on it -we can increase the `vsplits` counter. +--- +---In order to also compute nested vsplits, we need to keep track how deep we are in the layout, we remove +---that depth from the number of elements in the current `row` in order to avoid counting all parents many times. +--- +---@private +function State:iterateOverLayout(depth, vsplit, curr) for _, group in ipairs(curr) do + -- a row indicates a `vsplit` window container if type(group) == "string" and group == "row" then vsplit = true elseif type(group) == "table" then + local len = #group if vsplit then - local length = parent and #group - 1 or #group - vim.print(length, parent, #group) - self.increaseVSplits(self, length) - end + -- even if we are super deep in the field, len minimal value is always 1. + if len <= depth then + len = depth + 1 + end - self.iterateOverLayout(self, vsplit, group) + -- we remove the depth from the len in order to avoid counting parents multiple times. + self.increaseVSplits(self, len - depth) + + -- reset vsplit as this layer as been computed already, increase depth as we will dug again. + depth = depth + 1 + vsplit = false + end + self.iterateOverLayout(self, depth, vsplit, group) end end end ---Refresh vsplits counter based on the `winlayout`. --- +---@param scope string: the caller of the method. ---@private -function State:refreshVSplits() +function State:refreshVSplits(scope) self.initVSplits(self) local layout = vim.fn.winlayout(self.activeTab) if #layout == 2 and type(layout[1]) == "string" and type(layout[2]) == "number" then self.increaseVSplits(self, 1) else - self.iterateOverLayout(self, nil, layout) + self.iterateOverLayout(self, 0, false, layout) end - D.log("refreshVSplits", "computed %d", self.tabs[self.activeTab].wins.vsplits) + D.log(scope, "computed %d", self.tabs[self.activeTab].wins.vsplits) end return State diff --git a/lua/no-neck-pain/wins.lua b/lua/no-neck-pain/wins.lua index 6dc6dba..e996f45 100644 --- a/lua/no-neck-pain/wins.lua +++ b/lua/no-neck-pain/wins.lua @@ -109,7 +109,7 @@ end function W.createSideBuffers(skipIntegrations) -- before creating side buffers, we determine if we should consider externals S.refreshIntegrations(S, "createSideBuffers") - S.refreshVSplits(S) + S.refreshVSplits(S, "createSideBuffers") local wins = { left = { cmd = "topleft vnew", padding = 0 }, @@ -190,6 +190,7 @@ end ---@return number: the width of the side window. ---@private function W.getPadding(side) + local scope = string.format("W.getPadding:%s", side) local uis = vim.api.nvim_list_uis() if uis[1] == nil then @@ -200,85 +201,63 @@ function W.getPadding(side) local width = uis[1].width + D.log(scope, "screen width %d", width) + -- if the available screen size is lower than the config width, -- we don't have to create side buffers. if _G.NoNeckPain.config.width >= width then - D.log("W.getPadding", "[%s] - ui %s - no space left to create side buffers", side, width) + D.log(scope, "no space left to create side buffers", side, width) return 0 end - local tab = S.getTab(S) + local columns = S.getVSplits(S) + + if _G.NoNeckPain.config.buffers.left.enabled then + columns = columns - 1 + end + + if _G.NoNeckPain.config.buffers.right.enabled then + columns = columns - 1 + end + + if columns < 1 then + columns = 1 + end -- we need to see if there's enough space left to have side buffers - local occupied = _G.NoNeckPain.config.width * tab.wins.vsplits + local occupied = _G.NoNeckPain.config.width * columns + + D.log(scope, "have %d vsplits", columns) -- if there's no space left according to the config width, -- then we don't have to create side buffers. if occupied >= width then - D.log(side, "%d vsplits - no space left to create side buffers", tab.wins.vsplits) + D.log(scope, "%d occupied - no space left to create side", occupied) return 0 end - D.log( - side, - "%d currently with %d vsplits - computing integrations width", - occupied, - tab.wins.vsplits - ) + D.log(scope, "%d/%d with vsplits, computing integrations", occupied, width) -- now we need to determine how much we should substract from the remaining padding -- if there's side integrations open. - local paddingToSubstract = 0 - - for name, tree in pairs(tab.wins.integrations) do + for name, tree in pairs(S.getIntegrations(S)) do if tree.id ~= nil and (not S.wantsSides(S) or side == _G.NoNeckPain.config.integrations[name].position) then - D.log( - "W.getPadding", - "[%s] - have an external open: %s with width %d", - side, - name, - tree.width - ) - - -- TODO: do not store width, get it at runtime instead. - paddingToSubstract = paddingToSubstract + tree.width - end - end - - return math.floor( - (width - paddingToSubstract - (_G.NoNeckPain.config.width * (tab.wins.vsplits - 2))) / 2 - ) -end + D.log(scope, "%s opened with width %d", name, tree.width) ----Determine if the tab wins are still active and valid. ---- ----@return boolean: whether all windows are active and valid or not. ----@private -function W.stateWinsActive() - if not S.isActiveTabValid(S) then - return false + occupied = occupied + tree.width + end end - local tab = S.getTabSafe(S) - - if tab == nil then - return false - end + local final = math.floor((width - occupied) / 2) - if tab.wins.main ~= nil then - for _, side in pairs(tab.wins.main) do - if not vim.api.nvim_win_is_valid(side) then - return false - end - end - end + D.log(scope, "%d/%d with integrations - final %d", occupied, width, final) - return true + return final end return W