From 5a8ebc844babf045f54ac7eeaeeaee58104534c0 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sat, 29 Jun 2024 23:36:40 +0200 Subject: [PATCH 01/17] feat(splits): leverage winlayout --- Makefile | 8 +- lua/no-neck-pain/main.lua | 9 +-- lua/no-neck-pain/state.lua | 154 +++++++++++++++++-------------------- lua/no-neck-pain/wins.lua | 85 ++++++++------------ 4 files changed, 112 insertions(+), 144 deletions(-) diff --git a/Makefile b/Makefile index 86263c5..d7bb657 100644 --- a/Makefile +++ b/Makefile @@ -27,8 +27,11 @@ deps: # bc for mini.nvim before this date ./scripts/reset_deps_at_date.sh ./deps/mini.nvim -deps-latest: - ./scripts/clone_deps.sh 1 || true +deps-lint: + luarocks install argparse --force + luarocks install luafilesystem --force + luarocks install lanes --force + luarocks install luacheck --force test-ci: deps test @@ -39,6 +42,7 @@ documentation-ci: deps documentation lint: stylua . -g '*.lua' -g '!deps/' -g '!nightly/' + luacheck plugin/ lua/ luals-ci: rm -rf .ci/lua-ls/log diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index d706223..39f854e 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -132,7 +132,7 @@ function N.enable(scope) vim.api.nvim_create_augroup(augroupName, { clear = true }) S.setSideID(S, vim.api.nvim_get_current_win(), "curr") - S.computeSplits(S, S.getSideID(S, "curr")) + S.refreshVSplits(S, scope) N.init(scope, true) @@ -217,12 +217,7 @@ function N.enable(scope) return D.log(p.event, "no new or too many unregistered windows") end - local focusedWin = wins[1] - - local isVSplit = S.computeSplits(S, focusedWin) - S.setSplit(S, { id = focusedWin, vertical = isVSplit }) - - if isVSplit then + if S.refreshVSplits(S, scope) then N.init(p.event) end end) diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index 84b769d..278e2f1 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -20,11 +20,36 @@ function State:save() _G.NoNeckPain.state = self end ----Sets the state splits to its original value. +---Sets the vsplits counter to 1. --- ---@private -function State:initSplits() - self.tabs[self.activeTab].wins.splits = nil +function State:initVSplits() + self.tabs[self.activeTab].wins.vsplits = 0 +end + +---Gets the tab vsplits counter. +--- +---@return number: the number of active vsplits. +---@private +function State:getVSplits() + return self.tabs[self.activeTab].wins.vsplits +end + +---Whether the side is enabled in the config or not. +--- +---@param side "left"|"right": the side of the window. +---@return boolean +---@private +function State:isSideEnabled(side) + return _G.NoNeckPain.config.buffers[side].enabled +end + +---Gets all integrations. +--- +---@return table: the integration infos. +---@private +function State:getIntegrations() + return self.tabs[self.activeTab].wins.integrations end ---Iterates over the tabs in the state to remove invalid tabs. @@ -514,117 +539,80 @@ function State:setTab(id) self.tabs[id] = { id = id, scratchPadEnabled = false, - layers = { - vsplit = 1, - split = 1, - }, wins = { + vsplits = 0, main = { curr = nil, left = nil, right = nil, }, - splits = nil, integrations = vim.deepcopy(Co.INTEGRATIONS), }, } self.activeTab = id end ----Sets the `layers` of the currently active tab. +---Increases the vsplits counter. --- ----@param vsplit number?: the number of opened vsplits. ----@param split number?: the number of opened splits. +---@param nb number: the number of columns in the given row. ---@private -function State:setLayers(vsplit, split) - if vsplit ~= nil then - self.tabs[self.activeTab].layers.vsplit = vsplit - end - - if vsplit ~= nil then - self.tabs[self.activeTab].layers.split = split - end +function State:increaseVSplits(nb) + self.tabs[self.activeTab].wins.vsplits = self.tabs[self.activeTab].wins.vsplits + nb end ----Removes the split with the given `id` from the state. +---Recursively walks in the `winlayout` until it has computed every column present in the UI. --- ----@param id number: the id of the split to remove. ----@private -function State:removeSplit(id) - self.tabs[self.activeTab].wins.splits[id] = nil -end - ----Decreases the layers of splits state values. +---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. --- ----@param isVSplit boolean: whether the window is a vsplit or not. ---@private -function State:decreaseLayers(isVSplit) - local scope = isVSplit and "vsplit" or "split" +function State:walkLayout(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 + -- even if we are super deep in the field, len minimal value is always 1. + if len <= depth then + len = depth + 1 + end - self.tabs[self.activeTab].layers[scope] = self.tabs[self.activeTab].layers[scope] - 1 + -- we remove the depth from the len in order to avoid counting parents multiple times. + self.increaseVSplits(self, len - depth) - if self.tabs[self.activeTab].layers[scope] < 1 then - self.tabs[self.activeTab].layers[scope] = 1 + -- reset vsplit as this layer as been computed already, increase depth as we will dug again. + depth = depth + 1 + vsplit = false + end + self.walkLayout(self, depth, vsplit, group) + -- depth 0 on a leaf is only possible after enabling nnp as there's nothing in the layout other than the main window + elseif depth == 0 and group == "leaf" then + self.increaseVSplits(self, 1) + end end end ----Determines current state of the split/vsplit windows by comparing widths and heights. +---Refresh vsplits counter based on the `winlayout`. --- ----@param focusedWin number: the id of the current window. ----@return boolean: whether the current window is a vsplit or not. +---@param scope string: the caller of the method. +---@return boolean: whether the number of vsplits changed or not. ---@private -function State:computeSplits(focusedWin) - local side = self.getSideID(self, "left") or self.getSideID(self, "right") - local sWidth, sHeight = 0, 0 - - -- when side buffer exists we rely on them, otherwise we fallback to the UI - if side ~= nil then - local nbSide = 1 - - if self.checkSides(self, "and", true) then - nbSide = 2 - end +function State:refreshVSplits(scope) + local currentVSplits = self.getVSplits(self) - sWidth, sHeight = A.getWidthAndHeight(side) - sWidth = vim.api.nvim_list_uis()[1].width - sWidth * nbSide - else - sWidth = vim.api.nvim_list_uis()[1].width - sHeight = vim.api.nvim_list_uis()[1].height - end - - local fWidth, fHeight = A.getWidthAndHeight(focusedWin) - local isVSplit = true + self.initVSplits(self) - local splitInF = math.floor(sHeight / fHeight) - if splitInF < 1 then - splitInF = 1 - end - - if splitInF > self.tabs[self.activeTab].layers.split then - isVSplit = false - end - - local vsplitInF = math.floor(sWidth / fWidth) - if vsplitInF < 1 then - vsplitInF = 1 - end - - if vsplitInF > self.tabs[self.activeTab].layers.vsplit then - isVSplit = true - end + self.walkLayout(self, 0, false, vim.fn.winlayout(self.activeTab)) - -- update anyway because we want state consistency - self.setLayers(self, vsplitInF, splitInF) + D.log(scope, "computed %d vsplits", self.getVSplits(self)) - D.log( - "Sp.compute", - "[split %d | vsplit %d] new split, vertical: %s", - self.tabs[self.activeTab].layers.split, - self.tabs[self.activeTab].layers.vsplit, - isVSplit - ) + self.save(self) - return isVSplit + return currentVSplits ~= self.getVSplits(self) end return State diff --git a/lua/no-neck-pain/wins.lua b/lua/no-neck-pain/wins.lua index 435f39e..216bcbf 100644 --- a/lua/no-neck-pain/wins.lua +++ b/lua/no-neck-pain/wins.lua @@ -10,7 +10,7 @@ local W = {} --- ---@param id number: the id of the window. ---@param width number: the width to apply to the window. ----@param side "left"|"right"|"split": the side of the window being resized, used for logging only. +---@param side "left"|"right"|"curr": the side of the window being resized, used for logging only. ---@private local function resize(id, width, side) D.log(side, "resizing %d with padding %d", id, width) @@ -21,7 +21,7 @@ local function resize(id, width, side) end ---Initializes the given `side` with the options from the user given configuration. ----@param side "left"|"right"|"split": the side of the window to initialize. +---@param side "left"|"right"|"curr": the side of the window to initialize. ---@param id number: the id of the window. ---@private function W.initSideOptions(side, id) @@ -177,30 +177,6 @@ function W.createSideBuffers(skipIntegrations) end 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 opened vsplits. - if S.checkSides(S, "or", true) and S.hasSplits(S) then - local side = S.getSideID(S, "left") or S.getSideID(S, "right") - local sWidth, _ = A.getWidthAndHeight(side) - local nbSide = 1 - - if S.getSideID(S, "left") and S.getSideID(S, "right") then - nbSide = 2 - end - - local tab = S.getTab(S) - - -- get the available usable width (screen size without side paddings) - sWidth = vim.api.nvim_list_uis()[1].width - sWidth * nbSide - sWidth = math.floor(sWidth / tab.layers.vsplit) - - for _, split in pairs(tab.wins.splits) do - if split.vertical then - resize(split.id, sWidth, "split") - end - end - end - -- closing integrations and reopening them means new window IDs if closedIntegrations then S.refreshIntegrations(S, "createSideBuffers") @@ -213,10 +189,11 @@ 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 - error("W.getPadding - attempted to get the padding of a non-existing UI.") + error("attempted to get the padding of a non-existing UI.") return 0 end @@ -226,55 +203,59 @@ function W.getPadding(side) -- 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, "[%s] - ui %s - no space left to create side buffers", side, width) return 0 end - local tab = S.getTab(S) + local columns = S.getVSplits(S) + + for _, s in ipairs(Co.SIDES) do + if S.isSideEnabled(S, s) then + columns = columns - 1 + end + 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.layers.vsplit + 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.layers.vsplit) + 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.layers.vsplit - ) + 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 + and ( + not S.isSideWinValid(S, side) + or side == _G.NoNeckPain.config.integrations[name].position ) + then + D.log(scope, "%s opened with width %d", name, tree.width) - paddingToSubstract = paddingToSubstract + tree.width + occupied = occupied + tree.width end end - return math.floor( - (width - paddingToSubstract - (_G.NoNeckPain.config.width * tab.layers.vsplit)) / 2 - ) + local final = math.floor((width - occupied) / 2) + + D.log(scope, "%d/%d with integrations - final %d", occupied, width, final) + + return final end ---Determine if the tab wins are still active and valid. From 8029fb5655c524a64fcc09d24362f1d85f364bd5 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sat, 29 Jun 2024 23:50:42 +0200 Subject: [PATCH 02/17] chore: add new state helpers --- lua/no-neck-pain/main.lua | 45 ++++++------------------- lua/no-neck-pain/state.lua | 67 +++++++------------------------------- lua/no-neck-pain/wins.lua | 35 -------------------- 3 files changed, 21 insertions(+), 126 deletions(-) diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index 39f854e..d29b4b8 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -238,7 +238,7 @@ function N.enable(scope) return end - if S.hasSplits(S) then + if S.getVSplits(S) > 0 then return D.log(s, "splits still active") end @@ -287,51 +287,26 @@ function N.enable(scope) vim.api.nvim_create_autocmd({ "WinClosed", "BufDelete" }, { callback = function(p) vim.schedule(function() - if - not S.isActiveTabRegistered(S) - or E.skip(nil) - or not S.hasSplits(S) - or W.stateWinsActive(true) - then + if not S.isActiveTabRegistered(S) or E.skip(nil) or S.getVSplits(S) == 0 then return end - S.tabs[S.activeTab].wins.splits = vim.tbl_filter(function(split) - if vim.api.nvim_win_is_valid(split.id) then - return true - end - - S.decreaseLayers(S, split.vertical) - - return false - end, S.tabs[S.activeTab].wins.splits) - - if #S.tabs[S.activeTab].wins.splits == 0 then - S.initSplits(S) - end - - -- 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 + S.refreshVSplits(S, p.event) - -- 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 - -- if neither curr and splits are remaining valids, we just disable - if not S.hasSplits(S) then + local wins = S.getUnregisteredWins(S) + if #wins == 0 then + D.log(p.event, "no active windows found") + return N.disable(p.event) end - haveCloseCurr = true + S.setSideID(S, wins[1], "curr") - local split = S.tabs[S.activeTab].wins.splits[1] + D.log(p.event, "re-routing to %d", S.getSideID(S, "curr")) - S.decreaseLayers(S, split.vertical) - S.setSideID(S, split.id, "curr") - S.removeSplit(S, split.id) + return N.init(p.event, true) end - - -- we only restore focus on curr if there's no split left - N.init(p.event, haveCloseCurr or not S.hasSplits(S)) end) end, group = augroupName, diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index 278e2f1..a884b35 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -178,43 +178,25 @@ end ---Gets all wins that are not already registered in the given `tab`. --- +---@param withCurr boolean?: whether we should filter curr or not ---@return table: the wins that are not in `tab`. ---@private -function State:getUnregisteredWins() - local wins = vim.api.nvim_tabpage_list_wins(self.activeTab) - local stateWins = self.getRegisteredWins(self) - - local validWins = {} - - for _, win in pairs(wins) do - if not vim.tbl_contains(stateWins, win) and not A.isRelativeWindow(win) then - table.insert(validWins, win) +function State:getUnregisteredWins(withCurr) + return vim.tbl_filter(function(win) + if A.isRelativeWindow(win) then + return false end - end - - return validWins -end - ----Gets all wins IDs that are registered in the state for the active tab. ---- ----@return table: the wins that are not in `tab`. ----@private -function State:getRegisteredWins() - local wins = {} - if self.tabs[self.activeTab].wins.main ~= nil then - for _, side in pairs(self.tabs[self.activeTab].wins.main) do - table.insert(wins, side) + if not withCurr and win == self.getSideID(self, "curr") then + return false end - end - if self.tabs[self.activeTab].wins.splits ~= nil then - for _, split in pairs(self.tabs[self.activeTab].wins.splits) do - table.insert(wins, split.id) + if win == self.getSideID(self, "left") or win == self.getSideID(self, "right") then + return false end - end - return wins + return true + end, vim.api.nvim_tabpage_list_wins(self.activeTab)) end ---Whether the given `fileType` matches a supported integration or not. @@ -367,20 +349,6 @@ function State:hasTabs() return self.tabs ~= nil end ----Whether there is splits registered in the active tab or not. ---- ----@return boolean ----@private -function State:hasSplits() - if not self.hasTabs(self) then - return false - end - - return self.tabs[self.activeTab] ~= nil - and self.tabs[self.activeTab].wins ~= nil - and self.tabs[self.activeTab].wins.splits ~= nil -end - ---Whether there is integrations registered in the active tab or not. --- ---@return boolean @@ -476,19 +444,6 @@ function State:setActiveTab(id) self.activeTab = id end ----Set a split in the state at the given id. ---- ----@param split table: the id of the split. ---- ----@private -function State:setSplit(split) - if self.tabs[self.activeTab].wins.splits == nil then - self.tabs[self.activeTab].wins.splits = {} - end - - self.tabs[self.activeTab].wins.splits[split.id] = split -end - ---Gets the tab with the given `id` from the state. --- ---@return table: the `tab` information. diff --git a/lua/no-neck-pain/wins.lua b/lua/no-neck-pain/wins.lua index 216bcbf..2b5c3bf 100644 --- a/lua/no-neck-pain/wins.lua +++ b/lua/no-neck-pain/wins.lua @@ -258,39 +258,4 @@ function W.getPadding(side) return final end ----Determine if the tab wins are still active and valid. ---- ----@param checkSplits boolean: whether splits state should be considered or not. ----@return boolean: whether all windows are active and valid or not. ----@private -function W.stateWinsActive(checkSplits) - if not S.isActiveTabValid(S) then - return false - end - - local tab = S.getTabSafe(S) - - if tab == nil then - return false - end - - 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 - - if checkSplits and tab.wins.splits ~= nil then - for _, split in pairs(tab.wins.splits) do - if not vim.api.nvim_win_is_valid(split.id) then - return false - end - end - end - - return true -end - return W From cc133c9a88e0b93c674d7bb2e7a181d3129e5248 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sun, 30 Jun 2024 00:01:42 +0200 Subject: [PATCH 03/17] fix: closing sides --- lua/no-neck-pain/main.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index d29b4b8..ac4c35b 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -238,9 +238,7 @@ function N.enable(scope) return end - if S.getVSplits(S) > 0 then - return D.log(s, "splits still active") - end + S.refreshVSplits(S, s) if ( From 375a5bcdec9b1dddc72583403e5db8a8fb0f046d Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sun, 30 Jun 2024 00:04:40 +0200 Subject: [PATCH 04/17] fix: integrations --- tests/test_integrations.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_integrations.lua b/tests/test_integrations.lua index 2c9abc6..bd744ae 100644 --- a/tests/test_integrations.lua +++ b/tests/test_integrations.lua @@ -383,17 +383,18 @@ T["TSPlayground"]["reduces `left` side if only active when integration is on `ri Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 149) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 142) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1001)"), 15) Helpers.expect.state(child, "tabs[1].wins.integrations.TSPlayground", { close = "TSPlaygroundToggle", fileTypePattern = "tsplayground", id = 1003, open = "TSPlaygroundToggle", - width = 298, + width = 284, }) Helpers.expect.state(child, "tabs[1].wins.main", { curr = 1000, - left = nil, + left = 1001, right = nil, }) @@ -409,9 +410,10 @@ T["TSPlayground"]["reduces `left` side if only active when integration is on `ri }) Helpers.expect.state(child, "tabs[1].wins.main", { curr = 1000, - left = 1004, + left = 1001, right = nil, }) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1001)"), 15) end T["aerial"] = MiniTest.new_set() From 7e4e767134888bc4effa0b5c22418d6aa0c150b8 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sun, 30 Jun 2024 00:57:22 +0200 Subject: [PATCH 05/17] fix: more tests --- lua/no-neck-pain/main.lua | 4 +- lua/no-neck-pain/state.lua | 14 ++---- tests/test_API.lua | 6 +-- tests/test_integrations.lua | 18 +++---- tests/test_tabs.lua | 95 ++++++------------------------------- 5 files changed, 32 insertions(+), 105 deletions(-) diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index ac4c35b..65ba4fe 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -163,9 +163,7 @@ function N.enable(scope) vim.api.nvim_create_autocmd({ "TabLeave" }, { callback = function(p) vim.schedule(function() - if - S.isActiveTabRegistered(S) and not vim.api.nvim_tabpage_is_valid(S.activeTab) - then + if not vim.api.nvim_tabpage_is_valid(S.activeTab) then S.refreshTabs(S, S.activeTab) D.log(p.event, "tab %d is now inactive", S.activeTab) else diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index a884b35..d540ebf 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -182,6 +182,8 @@ end ---@return table: the wins that are not in `tab`. ---@private function State:getUnregisteredWins(withCurr) + + return vim.tbl_filter(function(win) if A.isRelativeWindow(win) then return false @@ -272,20 +274,12 @@ function State:scanIntegrations(scope) return unregisteredIntegrations end ----Whether the `activeTab` is valid or not. ---- ----@return boolean ----@private -function State:isActiveTabValid() - return self.isActiveTabRegistered(self) and vim.api.nvim_tabpage_is_valid(self.activeTab) -end - ----Whether the `activeTab` is registered in the state or not. +---Whether the `activeTab` is registered in the state and valid. --- ---@return boolean ---@private function State:isActiveTabRegistered() - return self.hasTabs(self) and self.tabs[self.activeTab] ~= nil + return self.hasTabs(self) and self.tabs[self.activeTab] ~= nil and vim.api.nvim_tabpage_is_valid(self.activeTab) end ---Whether the side window is registered and enabled in the config or not. diff --git a/tests/test_API.lua b/tests/test_API.lua index e55c9ac..78952f7 100644 --- a/tests/test_API.lua +++ b/tests/test_API.lua @@ -244,7 +244,7 @@ T["enable"]["(single tab) sets state"] = function() right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) Helpers.expect.state_type(child, "tabs[1].wins.integrations", "table") @@ -272,7 +272,7 @@ T["enable"]["(multiple tab) sets state"] = function() left = 1001, right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) Helpers.expect.state_type(child, "tabs[1].wins.integrations", "table") @@ -296,7 +296,7 @@ T["enable"]["(multiple tab) sets state"] = function() left = 1004, right = 1005, }) - Helpers.expect.state(child, "tabs[2].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[2].wins.vsplits", 3) Helpers.expect.state_type(child, "tabs[2].wins.integrations", "table") diff --git a/tests/test_integrations.lua b/tests/test_integrations.lua index bd744ae..f9f88ad 100644 --- a/tests/test_integrations.lua +++ b/tests/test_integrations.lua @@ -149,7 +149,7 @@ T["nvimdapui"]["keeps sides open"] = function() right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 5) Helpers.expect.state(child, "tabs[1].wins.integrations.NvimDAPUI", { close = "lua require('dapui').close()", @@ -178,7 +178,7 @@ T["neotest"]["keeps sides open"] = function() child.lua([[require('neotest').summary.open()]]) vim.loop.sleep(50) - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 3) Helpers.expect.state(child, "tabs[1].wins.integrations.neotest", { close = "lua require('neotest').summary.close()", @@ -207,7 +207,7 @@ T["outline"]["keeps sides open"] = function() child.cmd("Outline") vim.loop.sleep(50) - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) Helpers.expect.state(child, "tabs[1].wins.integrations.outline", { close = "Outline", @@ -251,7 +251,7 @@ T["NvimTree"]["keeps sides open"] = function() right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) Helpers.expect.state(child, "tabs[1].wins.integrations.NvimTree", { close = "NvimTreeClose", @@ -290,7 +290,7 @@ T["neo-tree"]["keeps sides open"] = function() right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) Helpers.expect.state(child, "tabs[1].wins.integrations.NeoTree", { close = "Neotree close", @@ -321,7 +321,7 @@ T["TSPlayground"]["keeps sides open"] = function() child.cmd("TSPlaygroundToggle") vim.loop.sleep(50) - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 3) Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1004)"), 159) Helpers.expect.state(child, "tabs[1].wins.integrations.TSPlayground", { @@ -381,7 +381,7 @@ T["TSPlayground"]["reduces `left` side if only active when integration is on `ri child.cmd("TSPlaygroundToggle") vim.loop.sleep(50) - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 142) Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1001)"), 15) @@ -401,7 +401,7 @@ T["TSPlayground"]["reduces `left` side if only active when integration is on `ri child.cmd("TSPlaygroundToggle") vim.loop.sleep(50) - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 2) Helpers.expect.state(child, "tabs[1].wins.integrations.TSPlayground", { close = "TSPlaygroundToggle", @@ -441,7 +441,7 @@ T["aerial"]["keeps sides open"] = function() child.cmd("AerialToggle") - Helpers.expect.state(child, "tabs[1].wins.splits", vim.NIL) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1004)"), 25) Helpers.expect.state(child, "tabs[1].wins.integrations.aerial.id", 1004) diff --git a/tests/test_tabs.lua b/tests/test_tabs.lua index 09f35c0..d00ceae 100644 --- a/tests/test_tabs.lua +++ b/tests/test_tabs.lua @@ -185,10 +185,6 @@ T["tabnew/tabclose"]["doesn't keep closed tabs in state"] = function() Helpers.expect.state(child, "tabs", { { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -197,6 +193,7 @@ T["tabnew/tabclose"]["doesn't keep closed tabs in state"] = function() left = 1001, right = 1002, }, + vsplits = 1, }, }, }) @@ -206,10 +203,6 @@ T["tabnew/tabclose"]["doesn't keep closed tabs in state"] = function() Helpers.expect.state(child, "tabs", { { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -218,14 +211,11 @@ T["tabnew/tabclose"]["doesn't keep closed tabs in state"] = function() left = 1001, right = 1002, }, + vsplits = 1, }, }, { id = 2, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -234,6 +224,7 @@ T["tabnew/tabclose"]["doesn't keep closed tabs in state"] = function() left = 1004, right = 1005, }, + vsplits = 1, }, }, }) @@ -242,10 +233,6 @@ T["tabnew/tabclose"]["doesn't keep closed tabs in state"] = function() Helpers.expect.state(child, "tabs", { { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -254,6 +241,7 @@ T["tabnew/tabclose"]["doesn't keep closed tabs in state"] = function() left = 1001, right = 1002, }, + vsplits = 1, }, }, }) @@ -269,10 +257,6 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() Helpers.expect.state(child, "tabs", { { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -281,6 +265,7 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() left = 1001, right = 1002, }, + vsplits = 1, }, }, }) @@ -293,10 +278,6 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() Helpers.expect.state(child, "tabs", { { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -305,14 +286,11 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() left = 1001, right = 1002, }, + vsplits = 1, }, }, { id = 2, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -321,6 +299,7 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() left = 1004, right = 1005, }, + vsplits = 3, }, }, }) @@ -329,10 +308,6 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() Helpers.expect.state(child, "tabs", { { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -341,6 +316,7 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() left = 1001, right = 1002, }, + vsplits = 1, }, }, }) @@ -349,10 +325,6 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() Helpers.expect.state(child, "tabs", { { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -361,14 +333,11 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() left = 1001, right = 1002, }, + vsplits = 1, }, }, { id = 2, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -377,6 +346,7 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() left = 1006, right = 1007, }, + vsplits = 3, }, }, }) @@ -386,10 +356,6 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() Helpers.expect.state(child, "tabs", { { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -398,14 +364,11 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() left = 1001, right = 1002, }, + vsplits = 1, }, }, { id = 2, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -414,6 +377,7 @@ T["tabnew/tabclose"]["keeps state synchronized between tabs"] = function() left = 1006, right = 1007, }, + vsplits = 3, }, }, }) @@ -443,10 +407,6 @@ T["tabnew/tabclose"]["does not pick tab 1 for the first active tab"] = function( Helpers.expect.state(child, "activeTab", 2) Helpers.expect.state(child, "tabs[2]", { id = 2, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -455,6 +415,7 @@ T["tabnew/tabclose"]["does not pick tab 1 for the first active tab"] = function( left = 1002, right = 1003, }, + vsplits = 1, }, }) @@ -468,10 +429,6 @@ T["tabnew/tabclose"]["does not pick tab 1 for the first active tab"] = function( Helpers.toggle(child) Helpers.expect.state(child, "tabs[1]", { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = { @@ -521,14 +478,11 @@ T["tabnew/tabclose"]["does not pick tab 1 for the first active tab"] = function( left = 1004, right = 1005, }, + vsplits = 3, }, }) Helpers.expect.state(child, "tabs[2]", { id = 2, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -537,6 +491,7 @@ T["tabnew/tabclose"]["does not pick tab 1 for the first active tab"] = function( left = 1002, right = 1003, }, + vsplits = 1, }, }) end @@ -562,10 +517,6 @@ T["tabnew/tabclose"]["keep state synchronized on second tab"] = function() Helpers.expect.state(child, "activeTab", 2) Helpers.expect.state(child, "tabs[2]", { id = 2, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -592,10 +543,6 @@ T["tabnew/tabclose"]["keep state synchronized on second tab"] = function() Helpers.expect.state(child, "tabs[1]", vim.NIL) Helpers.expect.state(child, "tabs[2]", { id = 2, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -626,10 +573,6 @@ T["tabnew/tabclose"]["does not close nvim when quitting tab if some are left"] = Helpers.expect.state(child, "enabled", true) Helpers.expect.state(child, "tabs[1]", { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -643,10 +586,6 @@ T["tabnew/tabclose"]["does not close nvim when quitting tab if some are left"] = Helpers.expect.state(child, "activeTab", 2) Helpers.expect.state(child, "tabs[2]", { id = 2, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, @@ -663,10 +602,6 @@ T["tabnew/tabclose"]["does not close nvim when quitting tab if some are left"] = Helpers.expect.state(child, "activeTab", 1) Helpers.expect.state(child, "tabs[1]", { id = 1, - layers = { - split = 1, - vsplit = 1, - }, scratchPadEnabled = false, wins = { integrations = Co.INTEGRATIONS, From e930d3dd27263e5d492ef3229e6ea210922c094e Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sun, 30 Jun 2024 17:23:29 +0200 Subject: [PATCH 06/17] fix: flaky test --- tests/test_API.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/test_API.lua b/tests/test_API.lua index 78952f7..30a78dd 100644 --- a/tests/test_API.lua +++ b/tests/test_API.lua @@ -216,8 +216,6 @@ T["setup"]["starts the plugin on VimEnter"] = function() Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) Helpers.expect.state(child, "enabled", true) - - child.stop() end T["enable"] = MiniTest.new_set() @@ -364,7 +362,6 @@ T["disable"]["(multiple tab) resets state"] = function() end T["disable"]["(no file) does not close the window if unsaved buffer"] = function() - child.set_size(500, 500) child.lua([[ require('no-neck-pain').setup({width=50}) ]]) Helpers.toggle(child) @@ -388,7 +385,6 @@ T["disable"]["(no file) does not close the window if unsaved buffer"] = function end T["disable"]["(on file) does not close the window if unsaved buffer"] = function() - child.set_size(500, 500) child.restart({ "-u", "scripts/minimal_init.lua", "lua/no-neck-pain/main.lua" }) child.lua([[ require('no-neck-pain').setup({width=50}) ]]) Helpers.toggle(child) @@ -419,7 +415,6 @@ T["disable"]["relative window doesn't prevent quitting nvim"] = function() return end - child.set_size(500, 500) child.restart({ "-u", "scripts/init_with_incline.lua" }) child.lua([[ require('no-neck-pain').setup({width=50}) ]]) Helpers.toggle(child) From b1af8e485a9316ac58668ee855e7b1f16bf683cd Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sun, 30 Jun 2024 17:38:31 +0200 Subject: [PATCH 07/17] fix: mappings --- lua/no-neck-pain/main.lua | 3 ++- lua/no-neck-pain/state.lua | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index 65ba4fe..7ead9ed 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -96,6 +96,8 @@ function N.init(scope, goToCurr, skipIntegrations) D.log(scope, "init called on tab %d for current window %d", S.activeTab, S.getSideID(S, "curr")) + S.refreshVSplits(S, scope) + -- if we do not have side buffers, we must ensure we only trigger a focus if we re-create them local hadSideBuffers = true if S.checkSides(S, "and", false) then @@ -132,7 +134,6 @@ function N.enable(scope) vim.api.nvim_create_augroup(augroupName, { clear = true }) S.setSideID(S, vim.api.nvim_get_current_win(), "curr") - S.refreshVSplits(S, scope) N.init(scope, true) diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index d540ebf..0c9a75b 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -182,8 +182,6 @@ end ---@return table: the wins that are not in `tab`. ---@private function State:getUnregisteredWins(withCurr) - - return vim.tbl_filter(function(win) if A.isRelativeWindow(win) then return false @@ -279,7 +277,9 @@ end ---@return boolean ---@private function State:isActiveTabRegistered() - return self.hasTabs(self) and self.tabs[self.activeTab] ~= nil and vim.api.nvim_tabpage_is_valid(self.activeTab) + return self.hasTabs(self) + and self.tabs[self.activeTab] ~= nil + and vim.api.nvim_tabpage_is_valid(self.activeTab) end ---Whether the side window is registered and enabled in the config or not. From 357c78124ede12f3413e7ecc165eba0722ab8867 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sun, 30 Jun 2024 17:42:25 +0200 Subject: [PATCH 08/17] fix: wrong integration test --- tests/test_integrations.lua | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/test_integrations.lua b/tests/test_integrations.lua index f9f88ad..3b1f70f 100644 --- a/tests/test_integrations.lua +++ b/tests/test_integrations.lua @@ -207,7 +207,7 @@ T["outline"]["keeps sides open"] = function() child.cmd("Outline") vim.loop.sleep(50) - Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 4) Helpers.expect.state(child, "tabs[1].wins.integrations.outline", { close = "Outline", @@ -251,7 +251,7 @@ T["NvimTree"]["keeps sides open"] = function() right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 4) Helpers.expect.state(child, "tabs[1].wins.integrations.NvimTree", { close = "NvimTreeClose", @@ -290,7 +290,7 @@ T["neo-tree"]["keeps sides open"] = function() right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 4) Helpers.expect.state(child, "tabs[1].wins.integrations.NeoTree", { close = "Neotree close", @@ -358,7 +358,7 @@ T["TSPlayground"]["reduces `left` side if only active when integration is on `ri child.lua([[ require('no-neck-pain').setup({ - width = 50, + width = 20, buffers = { right = { enabled = false, @@ -376,21 +376,21 @@ T["TSPlayground"]["reduces `left` side if only active when integration is on `ri left = 1001, right = nil, }) - Helpers.expect.buf_width(child, "tabs[1].wins.main.left", 15) + Helpers.expect.buf_width(child, "tabs[1].wins.main.left", 30) child.cmd("TSPlaygroundToggle") vim.loop.sleep(50) - Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 3) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 142) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1001)"), 15) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 144) + Helpers.expect.buf_width(child, "tabs[1].wins.main.left", 20) Helpers.expect.state(child, "tabs[1].wins.integrations.TSPlayground", { close = "TSPlaygroundToggle", fileTypePattern = "tsplayground", id = 1003, open = "TSPlaygroundToggle", - width = 284, + width = 288, }) Helpers.expect.state(child, "tabs[1].wins.main", { curr = 1000, @@ -413,7 +413,7 @@ T["TSPlayground"]["reduces `left` side if only active when integration is on `ri left = 1001, right = nil, }) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1001)"), 15) + Helpers.expect.buf_width(child, "tabs[1].wins.main.left", 30) end T["aerial"] = MiniTest.new_set() @@ -441,7 +441,7 @@ T["aerial"]["keeps sides open"] = function() child.cmd("AerialToggle") - Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) + Helpers.expect.state(child, "tabs[1].wins.vsplits", 4) Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1004)"), 25) Helpers.expect.state(child, "tabs[1].wins.integrations.aerial.id", 1004) From 776caecf25bb152a7c4cab2609b30aeda67a7ca7 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Mon, 1 Jul 2024 00:06:24 +0200 Subject: [PATCH 09/17] chore: tests, getting there --- lua/no-neck-pain/main.lua | 119 ++++++-------------------- tests/test_commands.lua | 2 +- tests/test_options.lua | 4 +- tests/test_splits.lua | 171 +++++++++++++++----------------------- 4 files changed, 99 insertions(+), 197 deletions(-) diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index 7ead9ed..f4aef50 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -111,6 +111,8 @@ function N.init(scope, goToCurr, skipIntegrations) or (not hadSideBuffers and S.checkSides(S, "or", true)) or (S.isSideTheActiveWin(S, "left") or S.isSideTheActiveWin(S, "right")) then + D.log(scope, "re-routing focus to curr") + vim.fn.win_gotoid(S.getSideID(S, "curr")) end @@ -188,109 +190,27 @@ function N.enable(scope) desc = "Keeps track of the currently active tab", }) - vim.api.nvim_create_autocmd({ "WinEnter" }, { - callback = function(p) - vim.schedule(function() - p.event = string.format("%s:split", p.event) - if not S.isActiveTabRegistered(S) or E.skip(S.getTab(S)) then - return D.log(p.event, "skip") - end - - if S.checkSides(S, "and", false) then - return D.log(p.event, "no side buffer") - end - - if S.isSideTheActiveWin(S, "curr") then - return D.log(p.event, "current win") - end - - -- an integration isn't considered as a split - local isSupportedIntegration = S.isSupportedIntegration(S, p.event, nil) - if isSupportedIntegration then - return D.log(p.event, "on an integration") - end - - local wins = S.getUnregisteredWins(S) - - if #wins ~= 1 then - return D.log(p.event, "no new or too many unregistered windows") - end - - if S.refreshVSplits(S, scope) then - N.init(p.event) - end - end) - end, - group = augroupName, - desc = "WinEnter covers the split/vsplit management", - }) - vim.api.nvim_create_autocmd({ "QuitPre", "BufDelete" }, { callback = function(p) vim.schedule(function() - local s = string.format("%s:quit", p.event) - if - not S.isActiveTabRegistered(S) - or E.skip(nil) - or not S.isActiveTabRegistered(S) - then - return - end - - S.refreshVSplits(S, s) - - if - ( - (S.isSideRegistered(S, "left") and not S.isSideWinValid(S, "left")) - or (S.isSideRegistered(S, "right") and not S.isSideWinValid(S, "right")) - ) - or ( - p.event == "BufDelete" - and not _G.NoNeckPain.config.fallbackOnBufferDelete - and not S.isSideWinValid(S, "curr") - ) - then - D.log(s, "one of the NNP side has been closed") - - return N.disable(s) - end - - if S.isSideWinValid(S, "curr") then - D.log(s, "curr is still valid, skipping") - + if not S.isActiveTabRegistered(S) or E.skip(nil) then return end - -- if we still have a side valid but curr has been deleted (mostly because of a :bd), - -- we will fallback to the first valid side - if p.event == "QuitPre" then - D.log(s, "curr has been closed") - - return N.disable(s) - end - - D.log(s, "`curr` has been deleted, resetting state") + S.refreshVSplits(S, p.event) - vim.cmd("new") + if not vim.api.nvim_win_is_valid(S.getSideID(S, "curr")) then + if p.event == "BufDelete" and _G.NoNeckPain.config.fallbackOnBufferDelete then + D.log(p.event, "`curr` has been deleted, resetting state") - N.disable(string.format("%s:reset", s)) - N.enable(string.format("%s:reset", s)) - end) - end, - group = augroupName, - desc = "Handles the closure of main NNP windows", - }) + vim.cmd("new") - vim.api.nvim_create_autocmd({ "WinClosed", "BufDelete" }, { - callback = function(p) - vim.schedule(function() - if not S.isActiveTabRegistered(S) or E.skip(nil) or S.getVSplits(S) == 0 then - return - end + N.disable(string.format("%s:reset", p.event)) + N.enable(string.format("%s:reset", p.event)) - S.refreshVSplits(S, p.event) + return + end - if not vim.api.nvim_win_is_valid(S.getSideID(S, "curr")) then local wins = S.getUnregisteredWins(S) if #wins == 0 then D.log(p.event, "no active windows found") @@ -304,10 +224,21 @@ function N.enable(scope) return N.init(p.event, true) end + + if + (S.isSideRegistered(S, "left") and not S.isSideWinValid(S, "left")) + or (S.isSideRegistered(S, "right") and not S.isSideWinValid(S, "right")) + then + D.log(p.event, "one of the NNP side has been closed") + + return N.disable(p.event) + end + + return N.init(p.event) end) end, group = augroupName, - desc = "Aims at restoring NNP enable state after closing a split/vsplit buffer or a main buffer", + desc = "keeps track of the state after closing windows and deleting buffers", }) vim.api.nvim_create_autocmd({ "WinEnter", "WinClosed" }, { @@ -322,6 +253,8 @@ function N.enable(scope) return D.log(s, "skip") end + S.refreshVSplits(S, scope) + if S.wantsSides(S) and S.checkSides(S, "and", false) then return D.log(s, "no side buffer") end diff --git a/tests/test_commands.lua b/tests/test_commands.lua index 84c12f6..847c0ea 100644 --- a/tests/test_commands.lua +++ b/tests/test_commands.lua @@ -38,7 +38,7 @@ T["commands"]["NoNeckPainResize sets the config width and resizes windows"] = fu Helpers.expect.global(child, "_G.NoNeckPain.config.width", 20) -- need to know why the child isn't precise enough - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 20) + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 18) end T["commands"]["NoNeckPainResize throws with the plugin disabled"] = function() diff --git a/tests/test_options.lua b/tests/test_options.lua index 3fd1226..a11d4de 100644 --- a/tests/test_options.lua +++ b/tests/test_options.lua @@ -39,7 +39,7 @@ T["killAllBuffersOnDisable"] = MiniTest.new_set() T["killAllBuffersOnDisable"]["closes every windows when disabling the plugin"] = function() child.set_size(500, 500) - child.lua([[ require('no-neck-pain').setup({width=50,killAllBuffersOnDisable=true}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20,killAllBuffersOnDisable=true}) ]]) Helpers.toggle(child) Helpers.expect.state(child, "tabs[1].wins.main", { curr = 1000, left = 1001, right = 1002 }) @@ -62,6 +62,8 @@ T["fallbackOnBufferDelete"] = MiniTest.new_set() T["fallbackOnBufferDelete"]["invoking :bd keeps nnp enabled"] = function() child.set_size(500, 500) child.lua([[ require('no-neck-pain').setup({width=50,fallbackOnBufferDelete=true}) ]]) + + Helpers.expect.config(child, "fallbackOnBufferDelete", true) Helpers.toggle(child) Helpers.expect.state(child, "tabs[1].wins.main", { curr = 1000, left = 1001, right = 1002 }) diff --git a/tests/test_splits.lua b/tests/test_splits.lua index 1fa92b2..f785a1d 100644 --- a/tests/test_splits.lua +++ b/tests/test_splits.lua @@ -17,18 +17,17 @@ local T = MiniTest.new_set({ T["split"] = MiniTest.new_set() T["split"]["only one side buffer, closing help doesn't close NNP"] = function() - child.set_size(500, 500) - child.lua([[ require('no-neck-pain').setup({width=50, buffers={right={enabled=false}}}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20, buffers={right={enabled=false}}}) ]]) Helpers.toggle(child) + Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000 }) + child.cmd("h") - child.loop.sleep(50) - Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1002, 1000 }) + Helpers.expect.equality(Helpers.winsInTab(child), { 1002, 1001, 1000 }) Helpers.expect.state(child, "tabs[1].wins.main", { curr = 1000, left = 1001 }) - Helpers.expect.state(child, "tabs[1].wins.splits[1002]", { id = 1002, vertical = false }) - child.lua("vim.fn.win_gotoid(_G.NoNeckPain.state.tabs[1].wins.splits[1002].id)") + child.lua("vim.fn.win_gotoid(1002)") child.cmd("q") Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000 }) @@ -37,12 +36,10 @@ T["split"]["only one side buffer, closing help doesn't close NNP"] = function() end T["split"]["closing `curr` makes `split` the new `curr`"] = function() - child.set_size(200, 200) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20}) ]]) Helpers.toggle(child) child.cmd("split") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) Helpers.expect.state(child, "tabs[1].wins.main", { @@ -50,7 +47,6 @@ T["split"]["closing `curr` makes `split` the new `curr`"] = function() left = 1001, right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.splits[1003]", { id = 1003, vertical = false }) child.lua("vim.fn.win_gotoid(_G.NoNeckPain.state.tabs[1].wins.main.curr)") child.cmd("q") @@ -60,12 +56,10 @@ T["split"]["closing `curr` makes `split` the new `curr`"] = function() end T["split"]["keeps side buffers"] = function() - child.set_size(200, 200) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20}) ]]) Helpers.toggle(child) child.cmd("split") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) Helpers.expect.state(child, "tabs[1].wins.main", { @@ -73,19 +67,17 @@ T["split"]["keeps side buffers"] = function() left = 1001, right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.splits[1003]", { id = 1003, vertical = false }) - child.lua("vim.fn.win_gotoid(_G.NoNeckPain.state.tabs[1].wins.splits[1003].id)") + child.lua("vim.fn.win_gotoid(1003)") child.cmd("q") Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) - Helpers.expect.buf_width(child, "tabs[1].wins.main.left", 15) - Helpers.expect.buf_width(child, "tabs[1].wins.main.right", 15) + Helpers.expect.buf_width(child, "tabs[1].wins.main.left", 30) + Helpers.expect.buf_width(child, "tabs[1].wins.main.right", 30) end T["split"]["keeps correct focus"] = function() - child.set_size(300, 300) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20}) ]]) Helpers.toggle(child) Helpers.expect.equality(Helpers.currentWin(child), 1000) @@ -125,61 +117,54 @@ T["vsplit"]["does not create side buffers when there's not enough space"] = func end T["vsplit"]["corretly size splits when opening helper with side buffers open"] = function() - child.set_size(150, 150) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + child.set_size(30, 300) + child.lua([[ require('no-neck-pain').setup({width=20}) ]]) Helpers.toggle(child) child.cmd("vsplit") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) - Helpers.expect.buf_width(child, "tabs[1].wins.splits[1003].id", 50) - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 67) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 129) + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 128) child.cmd("h") - child.loop.sleep(50) - Helpers.expect.equality(Helpers.winsInTab(child), { 1004, 1001, 1003, 1000, 1002 }) + Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1004, 1003, 1000, 1002 }) - Helpers.expect.buf_width(child, "tabs[1].wins.splits[1004].id", 150) - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 67) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1004)"), 129) + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 128) end T["vsplit"]["correctly position side buffers when there's enough space"] = function() - child.set_size(500, 500) child.cmd("vsplit") Helpers.expect.equality(Helpers.winsInTab(child, 1), { 1001, 1000 }) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20}) ]]) Helpers.toggle(child) Helpers.expect.equality(Helpers.winsInTab(child), { 1002, 1001, 1000, 1003 }) end T["vsplit"]["preserve vsplit width when having side buffers"] = function() - child.set_size(500, 500) - child.lua([[ require('no-neck-pain').setup({width=50,buffers={right={enabled=false}}}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20,buffers={right={enabled=false}}}) ]]) Helpers.toggle(child) Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000 }) child.cmd("vsplit") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1002, 1000 }) - Helpers.expect.buf_width(child, "tabs[1].wins.splits[1002].id", 65) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1002)"), 32) end T["vsplit"]["closing `curr` makes `split` the new `curr`"] = function() - child.set_size(400, 400) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20}) ]]) Helpers.toggle(child) child.cmd("vsplit") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) Helpers.expect.state(child, "tabs[1].wins.main", { @@ -187,7 +172,6 @@ T["vsplit"]["closing `curr` makes `split` the new `curr`"] = function() left = 1001, right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.splits[1003]", { id = 1003, vertical = true }) child.lua("vim.fn.win_gotoid(_G.NoNeckPain.state.tabs[1].wins.main.curr)") child.cmd("q") @@ -206,15 +190,13 @@ T["vsplit"]["hides side buffers"] = function() Helpers.toggle(child) child.cmd("vsplit") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1003, 1000 }) Helpers.expect.state(child, "tabs[1].wins.main", { curr = 1000, }) - Helpers.expect.state(child, "tabs[1].wins.splits[1003]", { id = 1003, vertical = true }) - child.lua("vim.fn.win_gotoid(_G.NoNeckPain.state.tabs[1].wins.splits[1003].id)") + child.lua("vim.fn.win_gotoid(1003)") child.cmd("q") Helpers.expect.equality(Helpers.winsInTab(child), { 1004, 1000, 1005 }) @@ -227,21 +209,17 @@ T["vsplit"]["hides side buffers"] = function() end T["vsplit"]["many vsplit leave side buffers open as long as there's space for it"] = function() - child.set_size(100, 100) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20}) ]]) Helpers.toggle(child) Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) child.cmd("vsplit") - child.loop.sleep(50) child.cmd("vsplit") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1004, 1003, 1000 }) child.cmd("q") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1005, 1003, 1000, 1006 }) Helpers.expect.state(child, "tabs[_G.NoNeckPain.state.activeTab].wins.main", { @@ -252,8 +230,7 @@ T["vsplit"]["many vsplit leave side buffers open as long as there's space for it end T["vsplit"]["keeps correct focus"] = function() - child.set_size(200, 200) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + child.lua([[ require('no-neck-pain').setup({width=10}) ]]) Helpers.toggle(child) Helpers.expect.equality(Helpers.currentWin(child), 1000) @@ -270,8 +247,7 @@ end T["vsplit/split"] = MiniTest.new_set() T["vsplit/split"]["state is correctly sync'd even after many changes"] = function() - child.set_size(100, 100) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20}) ]]) Helpers.expect.equality(Helpers.winsInTab(child, 1), { 1000 }) @@ -284,12 +260,10 @@ T["vsplit/split"]["state is correctly sync'd even after many changes"] = functio child.cmd("q") child.cmd("vsplit") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1004, 1000, 1002 }) child.cmd("vsplit") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1005, 1004, 1000 }) @@ -305,21 +279,17 @@ T["vsplit/split"]["state is correctly sync'd even after many changes"] = functio end T["vsplit/split"]["closing side buffers because of splits restores focus"] = function() - child.set_size(100, 100) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + child.lua([[ require('no-neck-pain').setup({width=20}) ]]) Helpers.toggle(child) Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) child.cmd("vsplit") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) child.cmd("vsplit") - child.loop.sleep(50) child.cmd("vsplit") - child.loop.sleep(50) Helpers.expect.equality(Helpers.winsInTab(child), { 1005, 1004, 1003, 1000 }) @@ -330,50 +300,47 @@ T["vsplit/split"]["closing side buffers because of splits restores focus"] = fun Helpers.expect.equality(Helpers.currentWin(child), 1000) end -T["vsplit/split"]["closing help page doens't break layout"] = function() - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) - Helpers.toggle(child) - - Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) - - child.cmd("split") - child.cmd("h") - Helpers.expect.equality(Helpers.winsInTab(child), { 1004, 1001, 1003, 1000, 1002 }) - - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 48) - - child.cmd("q") - Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) - - Helpers.expect.equality(Helpers.currentWin(child), 1003) - - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 48) -end - -T["vsplit/split"]["splits and vsplits keeps a correct size"] = function() - child.set_size(50, 500) - child.lua([[ require('no-neck-pain').setup({width=50}) ]]) - Helpers.toggle(child) - - Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) - Helpers.expect.equality(Helpers.currentWin(child), 1000) - - child.cmd("split") - vim.loop.sleep(50) - - Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) - Helpers.expect.equality(Helpers.currentWin(child), 1003) - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 468) - - child.cmd("vsplit") - vim.loop.sleep(50) - - Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1004, 1003, 1000, 1002 }) - Helpers.expect.equality(Helpers.currentWin(child), 1004) - - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 468) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 417) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1000)"), 468) -end +-- T["vsplit/split"]["closing help page doens't break layout"] = function() +-- child.lua([[ require('no-neck-pain').setup({width=50}) ]]) +-- Helpers.toggle(child) +-- +-- Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) +-- +-- child.cmd("split") +-- child.cmd("h") +-- Helpers.expect.equality(Helpers.winsInTab(child), { 1004, 1001, 1003, 1000, 1002 }) +-- +-- Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 48) +-- +-- child.cmd("q") +-- Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) +-- +-- Helpers.expect.equality(Helpers.currentWin(child), 1003) +-- +-- Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 48) +-- end +-- +-- T["vsplit/split"]["splits and vsplits keeps a correct size"] = function() +-- child.lua([[ require('no-neck-pain').setup({width=20}) ]]) +-- Helpers.toggle(child) +-- +-- Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) +-- Helpers.expect.equality(Helpers.currentWin(child), 1000) +-- +-- child.cmd("split") +-- +-- Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) +-- Helpers.expect.equality(Helpers.currentWin(child), 1003) +-- Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 468) +-- +-- child.cmd("vsplit") +-- +-- Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1004, 1003, 1000, 1002 }) +-- Helpers.expect.equality(Helpers.currentWin(child), 1004) +-- +-- Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 468) +-- Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 417) +-- Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1000)"), 468) +-- end return T From 41947788019a72728f1e06c881717446a99415c4 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Mon, 1 Jul 2024 00:14:08 +0200 Subject: [PATCH 10/17] fix: unused skips --- lua/no-neck-pain/main.lua | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index f4aef50..b966462 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -244,39 +244,21 @@ function N.enable(scope) vim.api.nvim_create_autocmd({ "WinEnter", "WinClosed" }, { callback = function(p) vim.schedule(function() - local s = string.format("%s:integration", p.event) - if - not S.isActiveTabRegistered(S) - or not S.isActiveTabRegistered(S) - or E.skip(S.getTab(S)) - then - return D.log(s, "skip") + if not S.isActiveTabRegistered(S) or E.skip(S.getTab(S)) then + return D.log(p.event, "skip") end S.refreshVSplits(S, scope) if S.wantsSides(S) and S.checkSides(S, "and", false) then - return D.log(s, "no side buffer") + return D.log(p.event, "no side buffer") end if p.event == "WinClosed" and not S.hasIntegrations(S) then - return D.log(s, "no registered integration") - end - - local unregistered = S.getUnregisteredWins(S) - if p.event == "WinEnter" and #unregistered == 0 then - return D.log(s, "no new windows") - end - - if - p.event == "WinEnter" - and #unregistered == 1 - and not S.isSupportedIntegration(S, s, unregistered[1]) - then - return D.log(s, "encountered a new window, not an integration") + return D.log(p.event, "no registered integration") end - N.init(s, false, true) + N.init(p.event, false, true) end) end, group = augroupName, From 6d74bb45eb89aca2a6387b79cc623256fe2dd18d Mon Sep 17 00:00:00 2001 From: shortcuts Date: Wed, 3 Jul 2024 01:49:05 +0200 Subject: [PATCH 11/17] fix: inconsistent walking --- lua/no-neck-pain/main.lua | 2 +- lua/no-neck-pain/state.lua | 68 +++++++++++++++++++------------------- lua/no-neck-pain/wins.lua | 31 +++++++++++++---- tests/test_API.lua | 8 +++-- 4 files changed, 64 insertions(+), 45 deletions(-) diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index b966462..cc1fcfc 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -248,7 +248,7 @@ function N.enable(scope) return D.log(p.event, "skip") end - S.refreshVSplits(S, scope) + S.refreshVSplits(S, p.event) if S.wantsSides(S) and S.checkSides(S, "and", false) then return D.log(p.event, "no side buffer") diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index 0c9a75b..44ee4c3 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -20,19 +20,20 @@ function State:save() _G.NoNeckPain.state = self end ----Sets the vsplits counter to 1. +---Initializes the vsplits to an empty table. --- ---@private function State:initVSplits() - self.tabs[self.activeTab].wins.vsplits = 0 + self.tabs[self.activeTab].wins.vsplits = {} end ---Gets the tab vsplits counter. --- ----@return number: the number of active vsplits. +---@return table: the vsplits window IDs. +---@return number: the number of vsplits. ---@private function State:getVSplits() - return self.tabs[self.activeTab].wins.vsplits + return self.tabs[self.activeTab].wins.vsplits, A.length(self.tabs[self.activeTab].wins.vsplits) end ---Whether the side is enabled in the config or not. @@ -489,7 +490,7 @@ function State:setTab(id) id = id, scratchPadEnabled = false, wins = { - vsplits = 0, + vsplits = {}, main = { curr = nil, left = nil, @@ -501,12 +502,17 @@ function State:setTab(id) self.activeTab = id end ----Increases the vsplits counter. +---Insert the given ids window ids in the state. --- ----@param nb number: the number of columns in the given row. +---@param vsplits table: the vsplits leafs to add to the state. ---@private -function State:increaseVSplits(nb) - self.tabs[self.activeTab].wins.vsplits = self.tabs[self.activeTab].wins.vsplits + nb +function State:insertVSplits(vsplits) + for _, vsplit in ipairs(vsplits) do + -- only add the leaf because we can receive something like { "col" {...}}, which will then be walked in and inserted later + if vsplit[1] == "leaf" then + self.tabs[self.activeTab].wins.vsplits[vsplit[2]] = true + end + end end ---Recursively walks in the `winlayout` until it has computed every column present in the UI. @@ -517,51 +523,45 @@ end ---that depth from the number of elements in the current `row` in order to avoid counting all parents many times. --- ---@private -function State:walkLayout(depth, vsplit, curr) +function State:walkLayout(parent, 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 - -- even if we are super deep in the field, len minimal value is always 1. - if len <= depth then - len = depth + 1 - end - + if type(group) == "table" then + -- a row indicates a `vsplit` window container + if parent == "row" then -- 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 + self.insertVSplits(self, group) + parent = nil end - self.walkLayout(self, depth, vsplit, group) + self.walkLayout(self, parent, group) -- depth 0 on a leaf is only possible after enabling nnp as there's nothing in the layout other than the main window - elseif depth == 0 and group == "leaf" then - self.increaseVSplits(self, 1) + elseif group == "leaf" and parent == nil then + self.insertVSplits(self, { curr }) + elseif type(group) == "string" then + parent = group end end end ----Refresh vsplits counter based on the `winlayout`. +---Refresh vsplits state based on the `winlayout`. --- ---@param scope string: the caller of the method. ---@return boolean: whether the number of vsplits changed or not. ---@private function State:refreshVSplits(scope) - local currentVSplits = self.getVSplits(self) + local _, nbVSplits = self.getVSplits(self) self.initVSplits(self) - self.walkLayout(self, 0, false, vim.fn.winlayout(self.activeTab)) + self.walkLayout(self, nil, vim.fn.winlayout(self.activeTab)) - D.log(scope, "computed %d vsplits", self.getVSplits(self)) + D.log(scope, "computed %d vsplits", nbVSplits) self.save(self) - return currentVSplits ~= self.getVSplits(self) + local _, nbNBVsplits = self.getVSplits(self) + + -- TODO: real table diff + return nbVSplits ~= nbNBVsplits end return State diff --git a/lua/no-neck-pain/wins.lua b/lua/no-neck-pain/wins.lua index 2b5c3bf..99e913a 100644 --- a/lua/no-neck-pain/wins.lua +++ b/lua/no-neck-pain/wins.lua @@ -10,7 +10,7 @@ local W = {} --- ---@param id number: the id of the window. ---@param width number: the width to apply to the window. ----@param side "left"|"right"|"curr": the side of the window being resized, used for logging only. +---@param side "left"|"right"|"curr"|"vsplit": the side of the window being resized, used for logging only. ---@private local function resize(id, width, side) D.log(side, "resizing %d with padding %d", id, width) @@ -177,6 +177,27 @@ function W.createSideBuffers(skipIntegrations) end end + local vsplits, nbVSplits = S.getVSplits(S) + local leftID = S.getSideID(S, "left") + local rightID = S.getSideID(S, "right") + + -- if we still have side buffers open at this point, and we have vsplit opened, + -- there might be width issues so we the resize opened vsplits. + if (leftID or rightID) and nbVSplits > 0 then + local sWidth = wins.left.padding or wins.right.padding + local nbSide = leftID and rightID and 2 or 1 + + -- get the available usable width (screen size without side paddings) + sWidth = vim.api.nvim_list_uis()[1].width - sWidth * nbSide + sWidth = math.floor(sWidth / (nbVSplits - nbSide)) + + for vsplit, _ in pairs(vsplits) do + if vsplit ~= leftID and vsplit ~= rightID and vsplit ~= S.getSideID(S, "curr") then + resize(vsplit, sWidth, "vsplit") + end + end + end + -- closing integrations and reopening them means new window IDs if closedIntegrations then S.refreshIntegrations(S, "createSideBuffers") @@ -208,18 +229,14 @@ function W.getPadding(side) return 0 end - local columns = S.getVSplits(S) + local _, columns = S.getVSplits(S) for _, s in ipairs(Co.SIDES) do - if S.isSideEnabled(S, s) then + if S.isSideEnabled(S, s) and columns > 1 then columns = columns - 1 end 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 * columns diff --git a/tests/test_API.lua b/tests/test_API.lua index 30a78dd..e5a5b91 100644 --- a/tests/test_API.lua +++ b/tests/test_API.lua @@ -242,7 +242,7 @@ T["enable"]["(single tab) sets state"] = function() right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) + Helpers.expect.state(child, "tabs[1].wins.vsplits[1000]", true) Helpers.expect.state_type(child, "tabs[1].wins.integrations", "table") @@ -270,7 +270,7 @@ T["enable"]["(multiple tab) sets state"] = function() left = 1001, right = 1002, }) - Helpers.expect.state(child, "tabs[1].wins.vsplits", 1) + Helpers.expect.state(child, "tabs[1].wins.vsplits[1000]", true) Helpers.expect.state_type(child, "tabs[1].wins.integrations", "table") @@ -294,7 +294,9 @@ T["enable"]["(multiple tab) sets state"] = function() left = 1004, right = 1005, }) - Helpers.expect.state(child, "tabs[2].wins.vsplits", 3) + Helpers.expect.state(child, "tabs[2].wins.vsplits[1003]", true) + Helpers.expect.state(child, "tabs[2].wins.vsplits[1004]", true) + Helpers.expect.state(child, "tabs[2].wins.vsplits[1005]", true) Helpers.expect.state_type(child, "tabs[2].wins.integrations", "table") From 6a1e0d94feae50f7788ea0b4df79a83c80568fd4 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Wed, 3 Jul 2024 01:52:33 +0200 Subject: [PATCH 12/17] chore: descriptions --- lua/no-neck-pain/state.lua | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index 44ee4c3..b5a8fb7 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -515,25 +515,22 @@ function State:insertVSplits(vsplits) end end ----Recursively walks in the `winlayout` until it has computed every column present in the UI. +---Recursively walks in the `winlayout` until it has computed every column present. --- ----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. +---Finding a parentless leaf means we are at the basic nvim just opened level +---Finding any other string than a leaf result on a parent (row or col) +---Finding a group of type table means we have a set of childrens windows +--- When we are on a children of a row, we insert all of them in our vsplit states and reset the parent --- ---@private function State:walkLayout(parent, curr) for _, group in ipairs(curr) do if type(group) == "table" then - -- a row indicates a `vsplit` window container if parent == "row" then - -- we remove the depth from the len in order to avoid counting parents multiple times. self.insertVSplits(self, group) parent = nil end self.walkLayout(self, parent, group) - -- depth 0 on a leaf is only possible after enabling nnp as there's nothing in the layout other than the main window elseif group == "leaf" and parent == nil then self.insertVSplits(self, { curr }) elseif type(group) == "string" then @@ -542,7 +539,7 @@ function State:walkLayout(parent, curr) end end ----Refresh vsplits state based on the `winlayout`. +---Refreshes the vsplit states by analyzing the winlayout. --- ---@param scope string: the caller of the method. ---@return boolean: whether the number of vsplits changed or not. From 3bd5ab2a53d88be3eeb48517c8d55b116bf92bad Mon Sep 17 00:00:00 2001 From: shortcuts Date: Wed, 3 Jul 2024 23:51:55 +0200 Subject: [PATCH 13/17] chore: this is huge --- lua/no-neck-pain/main.lua | 6 +-- lua/no-neck-pain/state.lua | 94 ++++++++++++++++---------------------- lua/no-neck-pain/wins.lua | 46 ++++++++++--------- tests/test_splits.lua | 11 ++--- 4 files changed, 72 insertions(+), 85 deletions(-) diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index cc1fcfc..ae67cac 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -96,7 +96,7 @@ function N.init(scope, goToCurr, skipIntegrations) D.log(scope, "init called on tab %d for current window %d", S.activeTab, S.getSideID(S, "curr")) - S.refreshVSplits(S, scope) + S.scanLayout(S, scope) -- if we do not have side buffers, we must ensure we only trigger a focus if we re-create them local hadSideBuffers = true @@ -197,7 +197,7 @@ function N.enable(scope) return end - S.refreshVSplits(S, p.event) + S.scanLayout(S, p.event) if not vim.api.nvim_win_is_valid(S.getSideID(S, "curr")) then if p.event == "BufDelete" and _G.NoNeckPain.config.fallbackOnBufferDelete then @@ -248,7 +248,7 @@ function N.enable(scope) return D.log(p.event, "skip") end - S.refreshVSplits(S, p.event) + S.scanLayout(S, p.event) if S.wantsSides(S) and S.checkSides(S, "and", false) then return D.log(p.event, "no side buffer") diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index b5a8fb7..cb8785a 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -13,20 +13,27 @@ function State:init() self.tabs = nil end ----Saves the state in the global _G.NoNeckPain.state object. +---Sets the integrations state of the current tab to its original value. --- ---@private -function State:save() - _G.NoNeckPain.state = self +function State:initIntegrations() + self.tabs[self.activeTab].wins.integrations = vim.deepcopy(Co.INTEGRATIONS) end ----Initializes the vsplits to an empty table. +---Sets the vsplits state of the current tab to its original value. --- ---@private function State:initVSplits() self.tabs[self.activeTab].wins.vsplits = {} end +---Saves the state in the global _G.NoNeckPain.state object. +--- +---@private +function State:save() + _G.NoNeckPain.state = self +end + ---Gets the tab vsplits counter. --- ---@return table: the vsplits window IDs. @@ -80,14 +87,6 @@ function State:refreshTabs(id) return #self.tabs end ----Refresh the integrations of the active state tab. ---- ----@param scope string: the caller of the method. ----@private -function State:refreshIntegrations(scope) - self.tabs[self.activeTab].wins.integrations = self.scanIntegrations(self, scope) -end - ---Closes side integrations if opened. --- ---@return boolean: whether we closed something or not. @@ -251,28 +250,6 @@ function State:isSupportedIntegration(scope, win) return false, nil end ----Scans the current tab wins to update registered side integrations. ---- ----@param scope string: the caller of the method. ----@return table: the update state integrations table. ----@private -function State:scanIntegrations(scope) - local wins = self.getUnregisteredWins(self) - local unregisteredIntegrations = vim.deepcopy(Co.INTEGRATIONS) - - for _, win in pairs(wins) do - local supported, name, integration = self.isSupportedIntegration(self, scope, win) - if supported and name and integration then - integration.width = vim.api.nvim_win_get_width(win) * 2 - integration.id = win - - unregisteredIntegrations[name] = integration - end - end - - return unregisteredIntegrations -end - ---Whether the `activeTab` is registered in the state and valid. --- ---@return boolean @@ -502,15 +479,24 @@ function State:setTab(id) self.activeTab = id end ----Insert the given ids window ids in the state. +---Set the given layout windows in their corresponding entity (vsplits or integrations). +---We only consider `leaf` because we can receive something like `{ "col" {...}}`, which will then be walked in and considered later --- ----@param vsplits table: the vsplits leafs to add to the state. ----@private -function State:insertVSplits(vsplits) - for _, vsplit in ipairs(vsplits) do - -- only add the leaf because we can receive something like { "col" {...}}, which will then be walked in and inserted later - if vsplit[1] == "leaf" then - self.tabs[self.activeTab].wins.vsplits[vsplit[2]] = true +---@param scope string: the caller of the method. +---@param wins table: the layout windows. +---@private +function State:setLayoutWindows(scope, wins) + for _, win in ipairs(wins) do + if win[1] == "leaf" then + local supported, name, integration = self.isSupportedIntegration(self, scope, win[2]) + if supported and name and integration then + integration.width = vim.api.nvim_win_get_width(win[2]) * 2 + integration.id = win[2] + + self.tabs[self.activeTab].wins.integrations[name] = integration + else + self.tabs[self.activeTab].wins.vsplits[win[2]] = true + end end end end @@ -520,43 +506,43 @@ end ---Finding a parentless leaf means we are at the basic nvim just opened level ---Finding any other string than a leaf result on a parent (row or col) ---Finding a group of type table means we have a set of childrens windows ---- When we are on a children of a row, we insert all of them in our vsplit states and reset the parent +--- When we are on a children of a row, we will set the layout wins in state in order to determine if they are integrations or not --- +---@param scope string: the caller of the method. ---@private -function State:walkLayout(parent, curr) +function State:walkLayout(scope, parent, curr) for _, group in ipairs(curr) do if type(group) == "table" then if parent == "row" then - self.insertVSplits(self, group) + self.setLayoutWindows(self, scope, group) parent = nil end - self.walkLayout(self, parent, group) + self.walkLayout(self, scope, parent, group) elseif group == "leaf" and parent == nil then - self.insertVSplits(self, { curr }) + self.setLayoutWindows(self, scope, { curr }) elseif type(group) == "string" then parent = group end end end ----Refreshes the vsplit states by analyzing the winlayout. +---Scans the winlayout in order to identify window position and type. --- ---@param scope string: the caller of the method. ---@return boolean: whether the number of vsplits changed or not. ---@private -function State:refreshVSplits(scope) +function State:scanLayout(scope) local _, nbVSplits = self.getVSplits(self) self.initVSplits(self) - - self.walkLayout(self, nil, vim.fn.winlayout(self.activeTab)) - - D.log(scope, "computed %d vsplits", nbVSplits) - + self.initIntegrations(self) + self.walkLayout(self, scope, nil, vim.fn.winlayout(self.activeTab)) self.save(self) local _, nbNBVsplits = self.getVSplits(self) + D.log(scope, "computed %d vsplits", nbVSplits) + -- TODO: real table diff return nbVSplits ~= nbNBVsplits end diff --git a/lua/no-neck-pain/wins.lua b/lua/no-neck-pain/wins.lua index 99e913a..a8a65ce 100644 --- a/lua/no-neck-pain/wins.lua +++ b/lua/no-neck-pain/wins.lua @@ -108,7 +108,7 @@ end ---@private function W.createSideBuffers(skipIntegrations) -- before creating side buffers, we determine if we should consider externals - S.refreshIntegrations(S, "createSideBuffers") + S.scanLayout(S, "createSideBuffers") local wins = { left = { cmd = "topleft vnew", padding = 0 }, @@ -164,19 +164,6 @@ function W.createSideBuffers(skipIntegrations) S.reopenIntegration(S) end - for _, side in pairs(Co.SIDES) do - if S.isSideRegistered(S, side) then - local padding = wins[side].padding or W.getPadding(side) - - if padding > _G.NoNeckPain.config.minSideBufferWidth then - resize(S.getSideID(S, side), padding, side) - else - W.close("W.createSideBuffers", S.getSideID(S, side), side) - S.setSideID(S, nil, side) - end - end - end - local vsplits, nbVSplits = S.getVSplits(S) local leftID = S.getSideID(S, "left") local rightID = S.getSideID(S, "right") @@ -184,23 +171,38 @@ function W.createSideBuffers(skipIntegrations) -- if we still have side buffers open at this point, and we have vsplit opened, -- there might be width issues so we the resize opened vsplits. if (leftID or rightID) and nbVSplits > 0 then - local sWidth = wins.left.padding or wins.right.padding - local nbSide = leftID and rightID and 2 or 1 - - -- get the available usable width (screen size without side paddings) - sWidth = vim.api.nvim_list_uis()[1].width - sWidth * nbSide - sWidth = math.floor(sWidth / (nbVSplits - nbSide)) + -- local sWidth = wins.left.padding or wins.right.padding + -- local nbSide = leftID and rightID and 2 or 1 + -- + -- -- get the available usable width (screen size without side paddings) + -- sWidth = vim.api.nvim_list_uis()[1].width - sWidth * nbSide + -- sWidth = math.floor(sWidth / (nbVSplits - nbSide)) for vsplit, _ in pairs(vsplits) do if vsplit ~= leftID and vsplit ~= rightID and vsplit ~= S.getSideID(S, "curr") then - resize(vsplit, sWidth, "vsplit") + resize(vsplit, _G.NoNeckPain.config.width, "vsplit") + end + end + + resize(S.getSideID(S, "curr"), _G.NoNeckPain.config.width, "curr") + end + + for _, side in pairs(Co.SIDES) do + if S.isSideRegistered(S, side) then + local padding = wins[side].padding or W.getPadding(side) + + if padding > _G.NoNeckPain.config.minSideBufferWidth then + resize(S.getSideID(S, side), padding, side) + else + W.close("W.createSideBuffers", S.getSideID(S, side), side) + S.setSideID(S, nil, side) end end end -- closing integrations and reopening them means new window IDs if closedIntegrations then - S.refreshIntegrations(S, "createSideBuffers") + S.scanLayout(S, "createSideBuffers") end end diff --git a/tests/test_splits.lua b/tests/test_splits.lua index f785a1d..84e2c09 100644 --- a/tests/test_splits.lua +++ b/tests/test_splits.lua @@ -117,7 +117,6 @@ T["vsplit"]["does not create side buffers when there's not enough space"] = func end T["vsplit"]["corretly size splits when opening helper with side buffers open"] = function() - child.set_size(30, 300) child.lua([[ require('no-neck-pain').setup({width=20}) ]]) Helpers.toggle(child) @@ -125,15 +124,15 @@ T["vsplit"]["corretly size splits when opening helper with side buffers open"] = Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 129) - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 128) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 20) + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 17) child.cmd("h") - Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1004, 1003, 1000, 1002 }) + Helpers.expect.equality(Helpers.winsInTab(child), { 1004, 1001, 1003, 1000, 1002 }) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1004)"), 129) - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 128) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1004)"), 80) + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 17) end T["vsplit"]["correctly position side buffers when there's enough space"] = function() From ef776d96ffcbbe308b75785b5eb4897eb4f0043e Mon Sep 17 00:00:00 2001 From: shortcuts Date: Thu, 4 Jul 2024 00:07:57 +0200 Subject: [PATCH 14/17] chore: less refresh --- lua/no-neck-pain/main.lua | 7 ++----- lua/no-neck-pain/state.lua | 13 ------------- lua/no-neck-pain/wins.lua | 28 +++++++++++++--------------- scripts/minimal_init.lua | 1 + tests/test_splits.lua | 6 +++--- 5 files changed, 19 insertions(+), 36 deletions(-) diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index ae67cac..0cea02e 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -96,8 +96,6 @@ function N.init(scope, goToCurr, skipIntegrations) D.log(scope, "init called on tab %d for current window %d", S.activeTab, S.getSideID(S, "curr")) - S.scanLayout(S, scope) - -- if we do not have side buffers, we must ensure we only trigger a focus if we re-create them local hadSideBuffers = true if S.checkSides(S, "and", false) then @@ -130,17 +128,16 @@ function N.enable(scope) D.log(scope, "calling enable for tab %d", A.getCurrentTab()) + S.setEnabled(S) S.setTab(S, A.getCurrentTab()) local augroupName = A.getAugroupName(S.activeTab) vim.api.nvim_create_augroup(augroupName, { clear = true }) S.setSideID(S, vim.api.nvim_get_current_win(), "curr") - + S.scanLayout(S, scope) N.init(scope, true) - S.setEnabled(S) - vim.api.nvim_create_autocmd({ "VimResized" }, { callback = function(p) vim.schedule(function() diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index cb8785a..6b62701 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -220,19 +220,6 @@ function State:isSupportedIntegration(scope, win) return true, integrationName, integrationInfo end - if fileType == "" and tab ~= nil then - local wins = self.getUnregisteredWins(self) - - D.log(scope, "computing recursively") - if #wins ~= 1 or wins[1] == win then - D.log(scope, "too many windows to determine") - - return false, nil, nil - end - - return self.isSupportedIntegration(self, scope, wins[1]) - end - local registeredIntegrations = tab ~= nil and tab.wins.integrations or Co.INTEGRATIONS for name, integration in pairs(registeredIntegrations) do diff --git a/lua/no-neck-pain/wins.lua b/lua/no-neck-pain/wins.lua index a8a65ce..5521401 100644 --- a/lua/no-neck-pain/wins.lua +++ b/lua/no-neck-pain/wins.lua @@ -107,9 +107,6 @@ end ---@param skipIntegrations boolean?: skip integrations action when true. ---@private function W.createSideBuffers(skipIntegrations) - -- before creating side buffers, we determine if we should consider externals - S.scanLayout(S, "createSideBuffers") - local wins = { left = { cmd = "topleft vnew", padding = 0 }, right = { cmd = "botright vnew", padding = 0 }, @@ -164,6 +161,19 @@ function W.createSideBuffers(skipIntegrations) S.reopenIntegration(S) end + for _, side in pairs(Co.SIDES) do + if S.isSideRegistered(S, side) then + local padding = wins[side].padding or W.getPadding(side) + + if padding > _G.NoNeckPain.config.minSideBufferWidth then + resize(S.getSideID(S, side), padding, side) + else + W.close("W.createSideBuffers", S.getSideID(S, side), side) + S.setSideID(S, nil, side) + end + end + end + local vsplits, nbVSplits = S.getVSplits(S) local leftID = S.getSideID(S, "left") local rightID = S.getSideID(S, "right") @@ -187,18 +197,6 @@ function W.createSideBuffers(skipIntegrations) resize(S.getSideID(S, "curr"), _G.NoNeckPain.config.width, "curr") end - for _, side in pairs(Co.SIDES) do - if S.isSideRegistered(S, side) then - local padding = wins[side].padding or W.getPadding(side) - - if padding > _G.NoNeckPain.config.minSideBufferWidth then - resize(S.getSideID(S, side), padding, side) - else - W.close("W.createSideBuffers", S.getSideID(S, side), side) - S.setSideID(S, nil, side) - end - end - end -- closing integrations and reopening them means new window IDs if closedIntegrations then diff --git a/scripts/minimal_init.lua b/scripts/minimal_init.lua index 289fd57..e357869 100644 --- a/scripts/minimal_init.lua +++ b/scripts/minimal_init.lua @@ -2,5 +2,6 @@ vim.cmd([[let &rtp.=','.getcwd()]]) vim.cmd("set rtp+=deps/mini.nvim") +-- require('no-neck-pain').setup({width=50}) require("mini.test").setup() require("mini.doc").setup() diff --git a/tests/test_splits.lua b/tests/test_splits.lua index 84e2c09..601a193 100644 --- a/tests/test_splits.lua +++ b/tests/test_splits.lua @@ -124,8 +124,8 @@ T["vsplit"]["corretly size splits when opening helper with side buffers open"] = Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 20) - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 17) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 19) + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 18) child.cmd("h") @@ -156,7 +156,7 @@ T["vsplit"]["preserve vsplit width when having side buffers"] = function() Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1002, 1000 }) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1002)"), 32) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1002)"), 38) end T["vsplit"]["closing `curr` makes `split` the new `curr`"] = function() From 5a04153314d23a949cb0124c67ae39103901bae0 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Thu, 4 Jul 2024 00:12:13 +0200 Subject: [PATCH 15/17] fix: splits --- lua/no-neck-pain/wins.lua | 1 - scripts/minimal_init.lua | 1 - tests/test_splits.lua | 95 +++++++++++++++++++-------------------- 3 files changed, 47 insertions(+), 50 deletions(-) diff --git a/lua/no-neck-pain/wins.lua b/lua/no-neck-pain/wins.lua index 5521401..e9ca140 100644 --- a/lua/no-neck-pain/wins.lua +++ b/lua/no-neck-pain/wins.lua @@ -197,7 +197,6 @@ function W.createSideBuffers(skipIntegrations) resize(S.getSideID(S, "curr"), _G.NoNeckPain.config.width, "curr") end - -- closing integrations and reopening them means new window IDs if closedIntegrations then S.scanLayout(S, "createSideBuffers") diff --git a/scripts/minimal_init.lua b/scripts/minimal_init.lua index e357869..289fd57 100644 --- a/scripts/minimal_init.lua +++ b/scripts/minimal_init.lua @@ -2,6 +2,5 @@ vim.cmd([[let &rtp.=','.getcwd()]]) vim.cmd("set rtp+=deps/mini.nvim") --- require('no-neck-pain').setup({width=50}) require("mini.test").setup() require("mini.doc").setup() diff --git a/tests/test_splits.lua b/tests/test_splits.lua index 601a193..9ec55c6 100644 --- a/tests/test_splits.lua +++ b/tests/test_splits.lua @@ -73,7 +73,7 @@ T["split"]["keeps side buffers"] = function() Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) Helpers.expect.buf_width(child, "tabs[1].wins.main.left", 30) - Helpers.expect.buf_width(child, "tabs[1].wins.main.right", 30) + Helpers.expect.buf_width(child, "tabs[1].wins.main.right", 28) end T["split"]["keeps correct focus"] = function() @@ -124,15 +124,15 @@ T["vsplit"]["corretly size splits when opening helper with side buffers open"] = Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) - Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 19) - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 18) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 20) + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 20) child.cmd("h") Helpers.expect.equality(Helpers.winsInTab(child), { 1004, 1001, 1003, 1000, 1002 }) Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1004)"), 80) - Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 17) + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 20) end T["vsplit"]["correctly position side buffers when there's enough space"] = function() @@ -294,52 +294,51 @@ T["vsplit/split"]["closing side buffers because of splits restores focus"] = fun child.cmd("q") child.cmd("q") - Helpers.expect.equality(Helpers.winsInTab(child), { 1006, 1003, 1000, 1007 }) + Helpers.expect.equality(Helpers.winsInTab(child), { 1012, 1004, 1003, 1013 }) - Helpers.expect.equality(Helpers.currentWin(child), 1000) + Helpers.expect.equality(Helpers.currentWin(child), 1004) +end + +T["vsplit/split"]["closing help page doens't break layout"] = function() + child.lua([[ require('no-neck-pain').setup({width=50}) ]]) + Helpers.toggle(child) + + Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) + + child.cmd("split") + child.cmd("h") + Helpers.expect.equality(Helpers.winsInTab(child), { 1004, 1001, 1003, 1000, 1002 }) + + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 50) + + child.cmd("q") + Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) + + Helpers.expect.equality(Helpers.currentWin(child), 1003) + + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 50) end --- T["vsplit/split"]["closing help page doens't break layout"] = function() --- child.lua([[ require('no-neck-pain').setup({width=50}) ]]) --- Helpers.toggle(child) --- --- Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) --- --- child.cmd("split") --- child.cmd("h") --- Helpers.expect.equality(Helpers.winsInTab(child), { 1004, 1001, 1003, 1000, 1002 }) --- --- Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 48) --- --- child.cmd("q") --- Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) --- --- Helpers.expect.equality(Helpers.currentWin(child), 1003) --- --- Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 48) --- end --- --- T["vsplit/split"]["splits and vsplits keeps a correct size"] = function() --- child.lua([[ require('no-neck-pain').setup({width=20}) ]]) --- Helpers.toggle(child) --- --- Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) --- Helpers.expect.equality(Helpers.currentWin(child), 1000) --- --- child.cmd("split") --- --- Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) --- Helpers.expect.equality(Helpers.currentWin(child), 1003) --- Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 468) --- --- child.cmd("vsplit") --- --- Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1004, 1003, 1000, 1002 }) --- Helpers.expect.equality(Helpers.currentWin(child), 1004) --- --- Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 468) --- Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 417) --- Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1000)"), 468) --- end +T["vsplit/split"]["splits and vsplits keeps a correct size"] = function() + child.lua([[ require('no-neck-pain').setup({width=20}) ]]) + Helpers.toggle(child) + + Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1000, 1002 }) + Helpers.expect.equality(Helpers.currentWin(child), 1000) + + child.cmd("split") + + Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1003, 1000, 1002 }) + Helpers.expect.equality(Helpers.currentWin(child), 1003) + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 20) + + child.cmd("vsplit") + + Helpers.expect.equality(Helpers.winsInTab(child), { 1001, 1004, 1003, 1000, 1002 }) + Helpers.expect.equality(Helpers.currentWin(child), 1004) + + Helpers.expect.buf_width(child, "tabs[1].wins.main.curr", 20) + Helpers.expect.equality(child.lua_get("vim.api.nvim_win_get_width(1003)"), 2) +end return T From 14c86a7e57cd8a0c75a521ffad9ec7083a301bb3 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Thu, 4 Jul 2024 01:18:57 +0200 Subject: [PATCH 16/17] chore: layout and side checking stuffs --- Makefile | 2 -- lua/no-neck-pain/main.lua | 8 ++++-- lua/no-neck-pain/state.lua | 55 +++++++++++++++---------------------- lua/no-neck-pain/wins.lua | 44 +++++++++++------------------ tests/test_autocmds.lua | 5 ---- tests/test_integrations.lua | 7 ----- tests/test_options.lua | 4 --- tests/test_tabs.lua | 4 --- 8 files changed, 43 insertions(+), 86 deletions(-) diff --git a/Makefile b/Makefile index d7bb657..cf4965b 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,6 @@ $(addprefix test-, $(TESTFILES)): test-%: -c "lua MiniTest.run_file('tests/test_$*.lua', { execute = { reporter = MiniTest.gen_reporter.stdout({ group_depth = 2 }) } })" deps: ./scripts/clone_deps.sh 1 || true - # bc for mini.nvim before this date - ./scripts/reset_deps_at_date.sh ./deps/mini.nvim deps-lint: luarocks install argparse --force diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index 0cea02e..20d2daf 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -81,6 +81,8 @@ function N.toggleSide(scope, side) return N.disable(scope) end + S.scanLayout(S, scope) + N.init(scope) end @@ -223,15 +225,15 @@ function N.enable(scope) end if - (S.isSideRegistered(S, "left") and not S.isSideWinValid(S, "left")) - or (S.isSideRegistered(S, "right") and not S.isSideWinValid(S, "right")) + (S.isSideWinValid(S, "left") and not S.isSideWinValid(S, "left")) + or (S.isSideWinValid(S, "right") and not S.isSideWinValid(S, "right")) then D.log(p.event, "one of the NNP side has been closed") return N.disable(p.event) end - return N.init(p.event) + -- return N.init(p.event) end) end, group = augroupName, diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index 6b62701..257388f 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -45,7 +45,7 @@ end ---Whether the side is enabled in the config or not. --- ----@param side "left"|"right": the side of the window. +---@param side "left"|"right"|"curr": the side of the window. ---@return boolean ---@private function State:isSideEnabled(side) @@ -247,21 +247,19 @@ function State:isActiveTabRegistered() and vim.api.nvim_tabpage_is_valid(self.activeTab) end ----Whether the side window is registered and enabled in the config or not. +---Whether the side window is registered and a valid window. --- ---@param side "left"|"right"|"curr": the side of the window. ---@return boolean ---@private -function State:isSideRegistered(side) - if not self.isActiveTabRegistered(self) then +function State:isSideWinValid(side) + if side ~= "curr" and not self.isSideEnabled(self, side) then return false end - if self.getSideID(self, side) == nil then - return false - end + local id = self.getSideID(self, side) - return _G.NoNeckPain.config.buffers[side].enabled + return id ~= nil and vim.api.nvim_win_is_valid(id) end ---Whether the sides window are registered and enabled in the config or not. @@ -272,23 +270,12 @@ end ---@private function State:checkSides(condition, expected) if condition == "or" then - return self.isSideRegistered(self, "left") == expected - or self.isSideRegistered(self, "right") == expected + return self.isSideWinValid(self, "left") == expected + or self.isSideWinValid(self, "right") == expected end - return self.isSideRegistered(self, "left") == expected - and self.isSideRegistered(self, "right") == expected -end - ----Whether the side window is registered and a valid window. ---- ----@param side "left"|"right"|"curr": the side of the window. ----@return boolean ----@private -function State:isSideWinValid(side) - local id = self.getSideID(self, side) - - return id ~= nil and vim.api.nvim_win_is_valid(id) + return self.isSideWinValid(self, "left") == expected + and self.isSideWinValid(self, "right") == expected end ---Whether the side window is the currently active one or not. @@ -474,15 +461,16 @@ end ---@private function State:setLayoutWindows(scope, wins) for _, win in ipairs(wins) do - if win[1] == "leaf" then - local supported, name, integration = self.isSupportedIntegration(self, scope, win[2]) + local id = win[2] + if win[1] == "leaf" and not A.isRelativeWindow(id) then + local supported, name, integration = self.isSupportedIntegration(self, scope, id) if supported and name and integration then - integration.width = vim.api.nvim_win_get_width(win[2]) * 2 - integration.id = win[2] + integration.width = vim.api.nvim_win_get_width(id) * 2 + integration.id = id self.tabs[self.activeTab].wins.integrations[name] = integration else - self.tabs[self.activeTab].wins.vsplits[win[2]] = true + self.tabs[self.activeTab].wins.vsplits[id] = true end end end @@ -499,10 +487,11 @@ end ---@private function State:walkLayout(scope, parent, curr) for _, group in ipairs(curr) do - if type(group) == "table" then + if type(group) == "table" and group[1] ~= "leaf" then if parent == "row" then - self.setLayoutWindows(self, scope, group) - parent = nil + -- if we are on a `col`, we don't care about how many windows are in it, + -- because their width is grouped, so one is enough to keep track of + self.setLayoutWindows(self, scope, group[1] == "col" and { group[2][1] } or group) end self.walkLayout(self, scope, parent, group) elseif group == "leaf" and parent == nil then @@ -526,9 +515,9 @@ function State:scanLayout(scope) self.walkLayout(self, scope, nil, vim.fn.winlayout(self.activeTab)) self.save(self) - local _, nbNBVsplits = self.getVSplits(self) + local vsplits, nbNBVsplits = self.getVSplits(self) - D.log(scope, "computed %d vsplits", nbVSplits) + D.log(scope, "computed vsplits: %s", vim.inspect(vsplits)) -- TODO: real table diff return nbVSplits ~= nbNBVsplits diff --git a/lua/no-neck-pain/wins.lua b/lua/no-neck-pain/wins.lua index e9ca140..0f03afb 100644 --- a/lua/no-neck-pain/wins.lua +++ b/lua/no-neck-pain/wins.lua @@ -162,7 +162,7 @@ function W.createSideBuffers(skipIntegrations) end for _, side in pairs(Co.SIDES) do - if S.isSideRegistered(S, side) then + if S.isSideWinValid(S, side) then local padding = wins[side].padding or W.getPadding(side) if padding > _G.NoNeckPain.config.minSideBufferWidth then @@ -180,21 +180,19 @@ function W.createSideBuffers(skipIntegrations) -- if we still have side buffers open at this point, and we have vsplit opened, -- there might be width issues so we the resize opened vsplits. - if (leftID or rightID) and nbVSplits > 0 then - -- local sWidth = wins.left.padding or wins.right.padding - -- local nbSide = leftID and rightID and 2 or 1 - -- - -- -- get the available usable width (screen size without side paddings) - -- sWidth = vim.api.nvim_list_uis()[1].width - sWidth * nbSide - -- sWidth = math.floor(sWidth / (nbVSplits - nbSide)) + if (leftID or rightID) and nbVSplits > 1 then + local sWidth = wins.left.padding or wins.right.padding + local nbSide = leftID and rightID and 2 or 1 + + -- get the available usable width (screen size without side paddings) + sWidth = vim.api.nvim_list_uis()[1].width - sWidth * nbSide + sWidth = math.floor(sWidth / (nbVSplits - nbSide)) for vsplit, _ in pairs(vsplits) do if vsplit ~= leftID and vsplit ~= rightID and vsplit ~= S.getSideID(S, "curr") then - resize(vsplit, _G.NoNeckPain.config.width, "vsplit") + resize(vsplit, sWidth, string.format("vsplit:%s", vsplit)) end end - - resize(S.getSideID(S, "curr"), _G.NoNeckPain.config.width, "curr") end -- closing integrations and reopening them means new window IDs @@ -210,20 +208,10 @@ end ---@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 - error("attempted to get the padding of a non-existing UI.") - - return 0 - end - - local width = uis[1].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(scope, "[%s] - ui %s - no space left to create side buffers", side, width) + if _G.NoNeckPain.config.width >= vim.o.columns then + D.log(scope, "[%s] - ui %s - no space left to create side buffers", side, vim.o.columns) return 0 end @@ -231,7 +219,7 @@ function W.getPadding(side) local _, columns = S.getVSplits(S) for _, s in ipairs(Co.SIDES) do - if S.isSideEnabled(S, s) and columns > 1 then + if S.isSideWinValid(S, s) and columns > 1 then columns = columns - 1 end end @@ -243,13 +231,13 @@ function W.getPadding(side) -- if there's no space left according to the config width, -- then we don't have to create side buffers. - if occupied >= width then + if occupied >= vim.o.columns then D.log(scope, "%d occupied - no space left to create side", occupied) return 0 end - D.log(scope, "%d/%d with vsplits, computing integrations", occupied, width) + D.log(scope, "%d/%d with vsplits, computing integrations", occupied, vim.o.columns) -- now we need to determine how much we should substract from the remaining padding -- if there's side integrations open. @@ -267,9 +255,9 @@ function W.getPadding(side) end end - local final = math.floor((width - occupied) / 2) + local final = math.floor((vim.o.columns - occupied) / 2) - D.log(scope, "%d/%d with integrations - final %d", occupied, width, final) + D.log(scope, "%d/%d with integrations - final %d", occupied, vim.o.columns, final) return final end diff --git a/tests/test_autocmds.lua b/tests/test_autocmds.lua index 3b3ad1f..ed858d2 100644 --- a/tests/test_autocmds.lua +++ b/tests/test_autocmds.lua @@ -38,7 +38,6 @@ T["auto command"]["disabling clears VimEnter autocmd"] = function() end T["auto command"]["does not shift when opening/closing float window"] = function() - child.set_size(5, 200) child.lua([[ require('no-neck-pain').setup({width=50}) ]]) Helpers.toggle(child) @@ -86,7 +85,6 @@ end T["skipEnteringNoNeckPainBuffer"] = MiniTest.new_set() T["skipEnteringNoNeckPainBuffer"]["goes to new valid buffer when entering side"] = function() - child.set_size(5, 200) child.lua( [[ require('no-neck-pain').setup({width=50, autocmds = { skipEnteringNoNeckPainBuffer = true }}) ]] ) @@ -128,7 +126,6 @@ T["skipEnteringNoNeckPainBuffer"]["goes to new valid buffer when entering side"] end T["skipEnteringNoNeckPainBuffer"]["does not register if scratchPad feature is enabled (global)"] = function() - child.set_size(5, 200) child.lua( [[ require('no-neck-pain').setup({width=50, buffers = { scratchPad = { enabled = true } }, autocmds = { skipEnteringNoNeckPainBuffer = true }}) ]] ) @@ -147,7 +144,6 @@ T["skipEnteringNoNeckPainBuffer"]["does not register if scratchPad feature is en end T["skipEnteringNoNeckPainBuffer"]["does not register if scratchPad feature is enabled (left)"] = function() - child.set_size(5, 200) child.lua( [[ require('no-neck-pain').setup({width=50, buffers = { left = { scratchPad = { enabled = true } } }, autocmds = { skipEnteringNoNeckPainBuffer = true }}) ]] ) @@ -166,7 +162,6 @@ T["skipEnteringNoNeckPainBuffer"]["does not register if scratchPad feature is en end T["skipEnteringNoNeckPainBuffer"]["does not register if scratchPad feature is enabled (right)"] = function() - child.set_size(5, 200) child.lua( [[ require('no-neck-pain').setup({width=50, buffers = { right = { scratchPad = { enabled = true } } }, autocmds = { skipEnteringNoNeckPainBuffer = true }}) ]] ) diff --git a/tests/test_integrations.lua b/tests/test_integrations.lua index 3b1f70f..b4c8328 100644 --- a/tests/test_integrations.lua +++ b/tests/test_integrations.lua @@ -124,7 +124,6 @@ T["nvimdapui"]["keeps sides open"] = function() end child.restart({ "-u", "scripts/init_with_nvimdapui.lua" }) - child.set_size(20, 100) Helpers.toggle(child) @@ -164,7 +163,6 @@ T["neotest"] = MiniTest.new_set() T["neotest"]["keeps sides open"] = function() child.restart({ "-u", "scripts/init_with_neotest.lua", "lua/no-neck-pain/main.lua" }) - child.set_size(20, 100) Helpers.toggle(child) @@ -193,7 +191,6 @@ T["outline"] = MiniTest.new_set() T["outline"]["keeps sides open"] = function() child.restart({ "-u", "scripts/init_with_outline.lua", "lua/no-neck-pain/main.lua" }) - child.set_size(20, 100) Helpers.toggle(child) @@ -266,7 +263,6 @@ T["neo-tree"] = MiniTest.new_set() T["neo-tree"]["keeps sides open"] = function() child.restart({ "-u", "scripts/init_with_neotree.lua", "foo" }) - child.set_size(5, 300) Helpers.toggle(child) @@ -305,7 +301,6 @@ T["TSPlayground"] = MiniTest.new_set() T["TSPlayground"]["keeps sides open"] = function() child.restart({ "-u", "scripts/init_with_tsplayground.lua" }) - child.set_size(5, 300) Helpers.toggle(child) @@ -354,7 +349,6 @@ end T["TSPlayground"]["reduces `left` side if only active when integration is on `right`"] = function() child.restart({ "-u", "scripts/init_with_tsplayground.lua" }) - child.set_size(5, 300) child.lua([[ require('no-neck-pain').setup({ @@ -426,7 +420,6 @@ T["aerial"]["keeps sides open"] = function() end child.restart({ "-u", "scripts/init_with_aerial.lua" }) - child.set_size(5, 500) Helpers.toggle(child) diff --git a/tests/test_options.lua b/tests/test_options.lua index a11d4de..b5b0cac 100644 --- a/tests/test_options.lua +++ b/tests/test_options.lua @@ -17,7 +17,6 @@ local T = MiniTest.new_set({ T["minSideBufferWidth"] = MiniTest.new_set() T["minSideBufferWidth"]["closes side buffer respecting the given value"] = function() - child.set_size(500, 500) child.lua([[ require('no-neck-pain').setup({width=50}) ]]) Helpers.toggle(child) @@ -38,7 +37,6 @@ end T["killAllBuffersOnDisable"] = MiniTest.new_set() T["killAllBuffersOnDisable"]["closes every windows when disabling the plugin"] = function() - child.set_size(500, 500) child.lua([[ require('no-neck-pain').setup({width=20,killAllBuffersOnDisable=true}) ]]) Helpers.toggle(child) @@ -60,7 +58,6 @@ end T["fallbackOnBufferDelete"] = MiniTest.new_set() T["fallbackOnBufferDelete"]["invoking :bd keeps nnp enabled"] = function() - child.set_size(500, 500) child.lua([[ require('no-neck-pain').setup({width=50,fallbackOnBufferDelete=true}) ]]) Helpers.expect.config(child, "fallbackOnBufferDelete", true) @@ -76,7 +73,6 @@ T["fallbackOnBufferDelete"]["invoking :bd keeps nnp enabled"] = function() end T["fallbackOnBufferDelete"]["still allows nvim to quit"] = function() - child.set_size(500, 500) child.lua([[ require('no-neck-pain').setup({width=50,fallbackOnBufferDelete=true}) ]]) Helpers.toggle(child) diff --git a/tests/test_tabs.lua b/tests/test_tabs.lua index d00ceae..e7ce676 100644 --- a/tests/test_tabs.lua +++ b/tests/test_tabs.lua @@ -107,8 +107,6 @@ T["TabNewEntered"]["starts the plugin on new tab"] = function() Helpers.expect.state(child, "activeTab", 2) Helpers.expect.equality(Helpers.winsInTab(child), { 1004, 1003, 1005 }) - - child.stop() end T["TabNewEntered"]["does not re-enable if the user disables it"] = function() @@ -143,8 +141,6 @@ T["TabNewEntered"]["does not re-enable if the user disables it"] = function() Helpers.expect.state(child, "activeTab", 2) Helpers.expect.equality(Helpers.winsInTab(child), { 1003 }) - - child.stop() end T["tabnew/tabclose"] = MiniTest.new_set() From 8c4b23b726bf9d3d12c828135af1595d379fdcbb Mon Sep 17 00:00:00 2001 From: shortcuts Date: Sat, 6 Jul 2024 00:17:13 +0200 Subject: [PATCH 17/17] chore: i broke even more things --- lua/no-neck-pain/main.lua | 74 ++++++++++++-------------------------- lua/no-neck-pain/state.lua | 31 +++++++++------- 2 files changed, 40 insertions(+), 65 deletions(-) diff --git a/lua/no-neck-pain/main.lua b/lua/no-neck-pain/main.lua index 20d2daf..2b1f830 100644 --- a/lua/no-neck-pain/main.lua +++ b/lua/no-neck-pain/main.lua @@ -162,41 +162,33 @@ function N.enable(scope) desc = "Resizes side windows after terminal has been resized, closes them if not enough space left.", }) - vim.api.nvim_create_autocmd({ "TabLeave" }, { + vim.api.nvim_create_autocmd({ "TabLeave", "TabEnter" }, { callback = function(p) - vim.schedule(function() - if not vim.api.nvim_tabpage_is_valid(S.activeTab) then - S.refreshTabs(S, S.activeTab) - D.log(p.event, "tab %d is now inactive", S.activeTab) - else + A.debounce(p.event, function() + if p.event == "TabLeave" then + S.refreshTabs(S) D.log(p.event, "tab %d left", S.activeTab) + + return end - end) - end, - group = augroupName, - desc = "Removes potentially inactive tabs from the state", - }) - vim.api.nvim_create_autocmd({ "TabEnter" }, { - callback = function(p) - vim.schedule(function() S.setActiveTab(S, A.getCurrentTab()) D.log(p.event, "tab %d entered", S.activeTab) end) end, group = augroupName, - desc = "Keeps track of the currently active tab", + desc = "Keeps track of the currently active tab and the tab state", }) - vim.api.nvim_create_autocmd({ "QuitPre", "BufDelete" }, { + vim.api.nvim_create_autocmd({ "QuitPre", "BufDelete", "WinEnter", "WinClosed" }, { callback = function(p) vim.schedule(function() - if not S.isActiveTabRegistered(S) or E.skip(nil) then + if not S.isActiveTabRegistered(S) or E.skip(S.getTab(S)) then return end - S.scanLayout(S, p.event) + local refresh = S.scanLayout(S, p.event) if not vim.api.nvim_win_is_valid(S.getSideID(S, "curr")) then if p.event == "BufDelete" and _G.NoNeckPain.config.fallbackOnBufferDelete then @@ -210,14 +202,14 @@ function N.enable(scope) return end - local wins = S.getUnregisteredWins(S) - if #wins == 0 then + local vsplits, nbVSplits = S.getVSplits(S, true) + if nbVSplits == 0 then D.log(p.event, "no active windows found") return N.disable(p.event) end - S.setSideID(S, wins[1], "curr") + S.setSideID(S, vsplits[1], "curr") D.log(p.event, "re-routing to %d", S.getSideID(S, "curr")) @@ -225,43 +217,21 @@ function N.enable(scope) end if - (S.isSideWinValid(S, "left") and not S.isSideWinValid(S, "left")) - or (S.isSideWinValid(S, "right") and not S.isSideWinValid(S, "right")) + (S.isSideEnabled(S, "left") and not S.isSideWinValid(S, "left")) + or (S.isSideEnabled(S, "right") and not S.isSideWinValid(S, "right")) then D.log(p.event, "one of the NNP side has been closed") return N.disable(p.event) end - -- return N.init(p.event) - end) - end, - group = augroupName, - desc = "keeps track of the state after closing windows and deleting buffers", - }) - - vim.api.nvim_create_autocmd({ "WinEnter", "WinClosed" }, { - callback = function(p) - vim.schedule(function() - if not S.isActiveTabRegistered(S) or E.skip(S.getTab(S)) then - return D.log(p.event, "skip") - end - - S.scanLayout(S, p.event) - - if S.wantsSides(S) and S.checkSides(S, "and", false) then - return D.log(p.event, "no side buffer") - end - - if p.event == "WinClosed" and not S.hasIntegrations(S) then - return D.log(p.event, "no registered integration") + if refresh then + return N.init(p.event) end - - N.init(p.event, false, true) end) end, group = augroupName, - desc = "Resize to apply on WinEnter/Closed of an integration", + desc = "keeps track of the state after closing windows and deleting buffers", }) if _G.NoNeckPain.config.autocmds.skipEnteringNoNeckPainBuffer then @@ -318,10 +288,10 @@ end --- Disables the plugin for the given tab, clear highlight groups and autocmds, closes side buffers and resets the internal state. ---@private function N.disable(scope) - D.log(scope, "calling disable for tab %d", S.activeTab) - local activeTab = S.activeTab + D.log(scope, "calling disable for tab %d", activeTab) + local wins = vim.tbl_filter(function(win) return win ~= S.getSideID(S, "left") and win ~= S.getSideID(S, "right") @@ -349,13 +319,13 @@ function N.disable(scope) end end - pcall(vim.api.nvim_del_augroup_by_name, A.getAugroupName(S.activeTab)) + pcall(vim.api.nvim_del_augroup_by_name, A.getAugroupName(activeTab)) pcall(vim.api.nvim_del_augroup_by_name, "NoNeckPainVimEnterAutocmd") return vim.cmd("quitall!") end - pcall(vim.api.nvim_del_augroup_by_name, A.getAugroupName(S.activeTab)) + pcall(vim.api.nvim_del_augroup_by_name, A.getAugroupName(activeTab)) local sides = { left = S.getSideID(S, "left"), right = S.getSideID(S, "right") } local currID = S.getSideID(S, "curr") diff --git a/lua/no-neck-pain/state.lua b/lua/no-neck-pain/state.lua index 257388f..2dcd655 100644 --- a/lua/no-neck-pain/state.lua +++ b/lua/no-neck-pain/state.lua @@ -36,11 +36,23 @@ end ---Gets the tab vsplits counter. --- +---@param filterMain boolean?: whether we should remove main windows or not. ---@return table: the vsplits window IDs. ---@return number: the number of vsplits. ---@private -function State:getVSplits() - return self.tabs[self.activeTab].wins.vsplits, A.length(self.tabs[self.activeTab].wins.vsplits) +function State:getVSplits(filterMain) + if not filterMain then + return self.tabs[self.activeTab].wins.vsplits, + A.length(self.tabs[self.activeTab].wins.vsplits) + end + + local filtered = vim.tbl_filter(function(vsplit) + return vsplit == self.getSideID(self, "left") + or vsplit == self.getSideID(self, "right") + or vsplit == self.getSideID(self, "curr") + end, self.tabs[self.activeTab].wins.vsplits) + + return filtered, A.length(filtered) end ---Whether the side is enabled in the config or not. @@ -62,19 +74,12 @@ end ---Iterates over the tabs in the state to remove invalid tabs. --- ----@param id number?: the `id` of the tab to remove from the state, defaults to the current tabpage. ---@return number: the total `tabs` in the state. ---@private -function State:refreshTabs(id) - id = id or A.getCurrentTab() - - local refreshedTabs = {} - - for _, tab in pairs(self.tabs) do - if tab.id ~= id and vim.api.nvim_tabpage_is_valid(tab.id) then - refreshedTabs[tab.id] = tab - end - end +function State:refreshTabs() + local refreshedTabs = vim.tbl_filter(function(tab) + return vim.api.nvim_tabpage_is_valid(tab.id) + end, self.tabs) if #refreshedTabs == 0 then self.tabs = nil