From dd901f76e8cea2b7c92da57fba9cd237621413bb Mon Sep 17 00:00:00 2001 From: uanela Date: Sat, 27 Dec 2025 14:35:53 +0200 Subject: [PATCH 01/15] wip(explorer): rename :expand to :expand_dir_node and all places using it --- lua/nvim-tree/actions/finders/find-file.lua | 2 +- lua/nvim-tree/actions/tree/modifiers/expand.lua | 4 ++-- lua/nvim-tree/explorer/init.lua | 2 +- lua/nvim-tree/node/directory.lua | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/nvim-tree/actions/finders/find-file.lua b/lua/nvim-tree/actions/finders/find-file.lua index 4f5d8089a85..fd66c646a29 100644 --- a/lua/nvim-tree/actions/finders/find-file.lua +++ b/lua/nvim-tree/actions/finders/find-file.lua @@ -66,7 +66,7 @@ function M.fn(path) dir.open = true end if #dir.nodes == 0 then - core.get_explorer():expand(dir) + core.get_explorer():expand_dir_node(dir) if dir.group_next and incremented_line then line = line - 1 end diff --git a/lua/nvim-tree/actions/tree/modifiers/expand.lua b/lua/nvim-tree/actions/tree/modifiers/expand.lua index 44e3fa67baa..e2ba684a2eb 100644 --- a/lua/nvim-tree/actions/tree/modifiers/expand.lua +++ b/lua/nvim-tree/actions/tree/modifiers/expand.lua @@ -23,7 +23,7 @@ local function expand(node) node = node:last_group_node() node.open = true if #node.nodes == 0 then - core.get_explorer():expand(node) + core.get_explorer():expand_dir_node(node) end end @@ -66,7 +66,7 @@ local function should_expand(expansion_count, node, should_descend) if not dir.open and should_descend(expansion_count, node) then if #node.nodes == 0 then - core.get_explorer():expand(dir) -- populate node.group_next + core.get_explorer():expand_dir_node(dir) -- populate node.group_next end if dir.group_next then diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua index 013d2c2efa4..93c71c5131c 100644 --- a/lua/nvim-tree/explorer/init.lua +++ b/lua/nvim-tree/explorer/init.lua @@ -191,7 +191,7 @@ function Explorer:create_autocmds() end ---@param node DirectoryNode -function Explorer:expand(node) +function Explorer:expand_dir_node(node) self:_load(node) end diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index 0965e4ab9fc..da1599cbdc5 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -167,7 +167,7 @@ function DirectoryNode:expand_or_collapse(toggle_group) end if #self.nodes == 0 then - self.explorer:expand(self) + self.explorer:expand_dir_node(self) end local head_node = self:get_parent_of_group() or self From 79338692513b668fb77862bc1b00374847635f3a Mon Sep 17 00:00:00 2001 From: uanela Date: Sun, 28 Dec 2025 11:21:09 +0200 Subject: [PATCH 02/15] wip: move expand function to directory --- lua/nvim-tree/actions/tree/modifiers/expand.lua | 8 -------- lua/nvim-tree/node/directory.lua | 10 ++++++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lua/nvim-tree/actions/tree/modifiers/expand.lua b/lua/nvim-tree/actions/tree/modifiers/expand.lua index e2ba684a2eb..e8dd9382eb2 100644 --- a/lua/nvim-tree/actions/tree/modifiers/expand.lua +++ b/lua/nvim-tree/actions/tree/modifiers/expand.lua @@ -18,14 +18,6 @@ local function to_lookup_table(list) return table end ----@param node DirectoryNode -local function expand(node) - node = node:last_group_node() - node.open = true - if #node.nodes == 0 then - core.get_explorer():expand_dir_node(node) - end -end ---@param should_descend fun(expansion_count: integer, node: Node): boolean ---@return fun(expansion_count: integer, node: Node): boolean diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index da1599cbdc5..bde6b8946e7 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -1,6 +1,7 @@ local git_utils = require("nvim-tree.git.utils") local icons = require("nvim-tree.renderer.components.devicons") local notify = require("nvim-tree.notify") +local core = require("nvim-tree.core") local Node = require("nvim-tree.node") @@ -290,4 +291,13 @@ function DirectoryNode:clone(api_nodes) return clone end +---@param node DirectoryNode +function DirectoryNode:expand(node) + node = node:last_group_node() + node.open = true + if #node.nodes == 0 then + core.get_explorer():expand_dir_node(node) + end +end + return DirectoryNode From 7481c19691f59db334c4d6b215d15474c4b4c977 Mon Sep 17 00:00:00 2001 From: uanela Date: Sun, 28 Dec 2025 19:29:48 +0200 Subject: [PATCH 03/15] refactor: move expand functionality to node methods Move expansion logic from actions/tree/modifiers/expand.lua into DirectoryNode and Explorer methods. Update API to use new methods via wrap_explorer. Remove expand module and its setup calls. --- lua/nvim-tree/actions/tree/init.lua | 1 - .../actions/tree/modifiers/expand.lua | 157 ------------------ lua/nvim-tree/actions/tree/modifiers/init.lua | 5 - lua/nvim-tree/api.lua | 4 +- lua/nvim-tree/explorer/init.lua | 22 +++ lua/nvim-tree/node/directory.lua | 126 +++++++++++++- lua/nvim-tree/node/init.lua | 11 ++ 7 files changed, 156 insertions(+), 170 deletions(-) delete mode 100644 lua/nvim-tree/actions/tree/modifiers/expand.lua diff --git a/lua/nvim-tree/actions/tree/init.lua b/lua/nvim-tree/actions/tree/init.lua index 2ff9e9b71cf..bcf14b9b79f 100644 --- a/lua/nvim-tree/actions/tree/init.lua +++ b/lua/nvim-tree/actions/tree/init.lua @@ -8,7 +8,6 @@ M.resize = require("nvim-tree.actions.tree.resize") function M.setup(opts) M.find_file.setup(opts) - M.modifiers.setup(opts) M.open.setup(opts) M.toggle.setup(opts) M.resize.setup(opts) diff --git a/lua/nvim-tree/actions/tree/modifiers/expand.lua b/lua/nvim-tree/actions/tree/modifiers/expand.lua deleted file mode 100644 index e8dd9382eb2..00000000000 --- a/lua/nvim-tree/actions/tree/modifiers/expand.lua +++ /dev/null @@ -1,157 +0,0 @@ -local core = require("nvim-tree.core") -local Iterator = require("nvim-tree.iterators.node-iterator") -local notify = require("nvim-tree.notify") - -local FileNode = require("nvim-tree.node.file") -local DirectoryNode = require("nvim-tree.node.directory") - -local M = {} - ----@param list string[] ----@return table -local function to_lookup_table(list) - local table = {} - for _, element in ipairs(list) do - table[element] = true - end - - return table -end - - ----@param should_descend fun(expansion_count: integer, node: Node): boolean ----@return fun(expansion_count: integer, node: Node): boolean -local function limit_folder_discovery(should_descend) - return function(expansion_count, node) - local should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY - if should_halt then - notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders") - return false - end - - return should_descend(expansion_count, node) - end -end - ----@param _ integer expansion_count ----@param node Node ----@return boolean -local function descend_until_empty(_, node) - local dir = node:as(DirectoryNode) - if not dir then - return false - end - - local should_exclude = M.EXCLUDE[dir.name] - return not should_exclude -end - ----@param expansion_count integer ----@param node Node ----@param should_descend fun(expansion_count: integer, node: Node): boolean ----@return boolean -local function should_expand(expansion_count, node, should_descend) - local dir = node:as(DirectoryNode) - if not dir then - return false - end - - if not dir.open and should_descend(expansion_count, node) then - if #node.nodes == 0 then - core.get_explorer():expand_dir_node(dir) -- populate node.group_next - end - - if dir.group_next then - local expand_next = should_expand(expansion_count, dir.group_next, should_descend) - if expand_next then - dir.open = true - end - return expand_next - else - return true - end - end - return false -end - - ----@param should_descend fun(expansion_count: integer, node: Node): boolean ----@return fun(node): any -local function gen_iterator(should_descend) - local expansion_count = 0 - - return function(parent) - if parent.parent and parent.nodes and not parent.open then - expansion_count = expansion_count + 1 - expand(parent) - end - - Iterator.builder(parent.nodes) - :hidden() - :applier(function(node) - if should_expand(expansion_count, node, should_descend) then - expansion_count = expansion_count + 1 - node = node:as(DirectoryNode) - if node then - expand(node) - end - end - end) - :recursor(function(node) - if not should_descend(expansion_count, node) then - return nil - end - - if node.group_next then - return { node.group_next } - end - - if node.open and node.nodes then - return node.nodes - end - - return nil - end) - :iterate() - end -end - ----@param node Node? ----@param expand_opts ApiTreeExpandOpts? -local function expand_node(node, expand_opts) - if not node then - return - end - local descend_until = limit_folder_discovery((expand_opts and expand_opts.expand_until) or descend_until_empty) - gen_iterator(descend_until)(node) - - local explorer = core.get_explorer() - if explorer then - explorer.renderer:draw() - end -end - ----Expand the directory node or the root ----@param node Node ----@param expand_opts ApiTreeExpandOpts? -function M.all(node, expand_opts) - expand_node(node and node:as(DirectoryNode) or core.get_explorer(), expand_opts) -end - ----Expand the directory node or parent node ----@param node Node ----@param expand_opts ApiTreeExpandOpts? -function M.node(node, expand_opts) - if not node then - return - end - - expand_node(node:is(FileNode) and node.parent or node:as(DirectoryNode), expand_opts) -end - -function M.setup(opts) - M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery - M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude) -end - -return M diff --git a/lua/nvim-tree/actions/tree/modifiers/init.lua b/lua/nvim-tree/actions/tree/modifiers/init.lua index cf1ae40bc5a..cee579313a2 100644 --- a/lua/nvim-tree/actions/tree/modifiers/init.lua +++ b/lua/nvim-tree/actions/tree/modifiers/init.lua @@ -1,10 +1,5 @@ local M = {} M.collapse = require("nvim-tree.actions.tree.modifiers.collapse") -M.expand = require("nvim-tree.actions.tree.modifiers.expand") - -function M.setup(opts) - M.expand.setup(opts) -end return M diff --git a/lua/nvim-tree/api.lua b/lua/nvim-tree/api.lua index e5a109142f1..83903044b4b 100644 --- a/lua/nvim-tree/api.lua +++ b/lua/nvim-tree/api.lua @@ -198,7 +198,7 @@ Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse.all) ---@class ApiTreeExpandOpts ---@field expand_until (fun(expansion_count: integer, node: Node): boolean)|nil -Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand.all) +Api.tree.expand_all = wrap_node(wrap_explorer("expand_all")) Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle") Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored") Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean") @@ -327,7 +327,7 @@ Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn({ Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn({ where = "next", what = "opened" })) Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "opened" })) -Api.node.expand = wrap_node(actions.tree.modifiers.expand.node) +Api.node.expand = wrap_node(wrap_explorer("expand_node")) Api.node.collapse = wrap_node(actions.tree.modifiers.collapse.node) ---@class ApiNodeDeleteWipeBufferOpts diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua index 93c71c5131c..02aefc33c0f 100644 --- a/lua/nvim-tree/explorer/init.lua +++ b/lua/nvim-tree/explorer/init.lua @@ -683,6 +683,28 @@ function Explorer:get_nodes() return self:clone() end +---Expand the directory node or the root +---@param node Node +---@param expand_opts ApiTreeExpandOpts? +function Explorer:expand_all(node, expand_opts) + if node then + node:expand(expand_opts) + else + self.expand(self, expand_opts) + end +end + +---Expand the directory node or parent node +---@param node Node +---@param expand_opts ApiTreeExpandOpts? +function Explorer:expand_node(node, expand_opts) + if not node then + return + end + + node:expand(expand_opts) +end + function Explorer:setup(opts) config = opts end diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index bde6b8946e7..adb8f51e34b 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -1,7 +1,7 @@ local git_utils = require("nvim-tree.git.utils") local icons = require("nvim-tree.renderer.components.devicons") local notify = require("nvim-tree.notify") -local core = require("nvim-tree.core") +local Iterator = require("nvim-tree.iterators.node-iterator") local Node = require("nvim-tree.node") @@ -291,12 +291,128 @@ function DirectoryNode:clone(api_nodes) return clone end ----@param node DirectoryNode -function DirectoryNode:expand(node) - node = node:last_group_node() +---@private +---@param should_descend fun(expansion_count: integer, node: Node): boolean +---@return fun(expansion_count: integer, node: Node): boolean +function DirectoryNode:limit_folder_discovery(should_descend) + local MAX_FOLDER_DISCOVERY = self.explorer.opts.actions.expand_all.max_folder_discovery + return function(expansion_count) + local should_halt = expansion_count >= MAX_FOLDER_DISCOVERY + if should_halt then + notify.warn("expansion iteration was halted after " .. MAX_FOLDER_DISCOVERY .. " discovered folders") + return false + end + + return should_descend(expansion_count, self) + end +end + +---@private +---@param list string[] +---@return table +local function to_lookup_table(list) + local table = {} + for _, element in ipairs(list) do + table[element] = true + end + + return table +end + +---@private +---@param _ integer expansion_count +---@return boolean +function DirectoryNode:descend_until_empty(_) + local EXCLUDE = to_lookup_table(self.explorer.opts.actions.expand_all.exclude) + local should_exclude = EXCLUDE[self.name] + return not should_exclude +end + +---@private +---@param expansion_count integer +---@param node Node +---@param should_descend fun(expansion_count: integer, node: Node): boolean +---@return boolean +function DirectoryNode:should_expand(expansion_count, node, should_descend) + local dir = node:as(DirectoryNode) + if not dir then + return false + end + + if not dir.open and should_descend(expansion_count, node) then + if #node.nodes == 0 then + self.explorer:expand_dir_node(dir) -- populate node.group_next + end + + if dir.group_next then + local expand_next = self:should_expand(expansion_count, dir.group_next, should_descend) + if expand_next then + dir.open = true + end + return expand_next + else + return true + end + end + return false +end + +---@private +---@param should_descend fun(expansion_count: integer, node: Node): boolean +---@return fun(node): any +function DirectoryNode:gen_iterator(should_descend) + local expansion_count = 0 + + return function(parent) + if parent.parent and parent.nodes and not parent.open then + expansion_count = expansion_count + 1 + parent:expand_dir_node() + end + + Iterator.builder(parent.nodes) + :hidden() + :applier(function(node) + if DirectoryNode:should_expand(expansion_count, node, should_descend) then + expansion_count = expansion_count + 1 + node = node:as(DirectoryNode) + if node then + self:expand_dir_node() + end + end + end) + :recursor(function(node) + if not should_descend(expansion_count, node) then + return nil + end + + if node.group_next then + return { node.group_next } + end + + if node.open and node.nodes then + return node.nodes + end + + return nil + end) + :iterate() + end +end + +---@param expand_opts ApiTreeExpandOpts? +function DirectoryNode:expand(expand_opts) + local descend_until_empty_fn = self.descend_until_empty + local descend_until = self:limit_folder_discovery((expand_opts and expand_opts.expand_until) or descend_until_empty_fn) + self:gen_iterator(descend_until)(self) + + self.explorer.renderer:draw() +end + +function DirectoryNode:expand_dir_node() + local node = self:last_group_node() node.open = true if #node.nodes == 0 then - core.get_explorer():expand_dir_node(node) + self.explorer:expand_dir_node(self) end end diff --git a/lua/nvim-tree/node/init.lua b/lua/nvim-tree/node/init.lua index da0dcc5b0eb..006e6ce2608 100644 --- a/lua/nvim-tree/node/init.lua +++ b/lua/nvim-tree/node/init.lua @@ -1,4 +1,6 @@ local Class = require("nvim-tree.classic") +local DirectoryNode = require("nvim-tree.node.directory") +local FileNode = require("nvim-tree.node.file") ---Abstract Node class. ---@class (exact) Node: Class @@ -144,4 +146,13 @@ function Node:clone(api_nodes) return clone end +---@param expand_opts ApiTreeExpandOpts? +function Node:expand(expand_opts) + if self:is(FileNode) then + self.parent.expand(self.parent, expand_opts) + else + self:as(DirectoryNode).expand_opts(expand_opts) + end +end + return Node From a192e8882af1cd8476baec4f3d2f15b941b15c94 Mon Sep 17 00:00:00 2001 From: uanela Date: Sun, 28 Dec 2025 19:43:57 +0200 Subject: [PATCH 04/15] refactor(directory): correctly calling class methods and passing correct values --- lua/nvim-tree/node/directory.lua | 2 +- lua/nvim-tree/node/init.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index adb8f51e34b..9f137f0cda3 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -412,7 +412,7 @@ function DirectoryNode:expand_dir_node() local node = self:last_group_node() node.open = true if #node.nodes == 0 then - self.explorer:expand_dir_node(self) + self.explorer:expand_dir_node(node) end end diff --git a/lua/nvim-tree/node/init.lua b/lua/nvim-tree/node/init.lua index 006e6ce2608..531fcb616e7 100644 --- a/lua/nvim-tree/node/init.lua +++ b/lua/nvim-tree/node/init.lua @@ -149,7 +149,7 @@ end ---@param expand_opts ApiTreeExpandOpts? function Node:expand(expand_opts) if self:is(FileNode) then - self.parent.expand(self.parent, expand_opts) + self.parent:expand(expand_opts) else self:as(DirectoryNode).expand_opts(expand_opts) end From 2b8623eb2db782fa746cfbd6cd0a11fbd7f1f228 Mon Sep 17 00:00:00 2001 From: uanela Date: Thu, 22 Jan 2026 16:57:49 +0200 Subject: [PATCH 05/15] fix: moving directory and file nodes require to avoid circular depency --- lua/nvim-tree/node/init.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lua/nvim-tree/node/init.lua b/lua/nvim-tree/node/init.lua index 531fcb616e7..6b4ddacf816 100644 --- a/lua/nvim-tree/node/init.lua +++ b/lua/nvim-tree/node/init.lua @@ -1,6 +1,4 @@ local Class = require("nvim-tree.classic") -local DirectoryNode = require("nvim-tree.node.directory") -local FileNode = require("nvim-tree.node.file") ---Abstract Node class. ---@class (exact) Node: Class @@ -148,6 +146,9 @@ end ---@param expand_opts ApiTreeExpandOpts? function Node:expand(expand_opts) + local DirectoryNode = require("nvim-tree.node.directory") + local FileNode = require("nvim-tree.node.file") + if self:is(FileNode) then self.parent:expand(expand_opts) else From 2f185434f089e339a055d60674e7813507d3d900 Mon Sep 17 00:00:00 2001 From: uanela Date: Thu, 22 Jan 2026 17:02:16 +0200 Subject: [PATCH 06/15] fix(node): correctly calling expand on parent without circular dependency --- lua/nvim-tree/node/init.lua | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lua/nvim-tree/node/init.lua b/lua/nvim-tree/node/init.lua index 6b4ddacf816..1cee60e7afc 100644 --- a/lua/nvim-tree/node/init.lua +++ b/lua/nvim-tree/node/init.lua @@ -146,13 +146,8 @@ end ---@param expand_opts ApiTreeExpandOpts? function Node:expand(expand_opts) - local DirectoryNode = require("nvim-tree.node.directory") - local FileNode = require("nvim-tree.node.file") - - if self:is(FileNode) then + if self.parent then self.parent:expand(expand_opts) - else - self:as(DirectoryNode).expand_opts(expand_opts) end end From 9e19b5e13fb2ef8dc9bb67d340cc54f805f07a34 Mon Sep 17 00:00:00 2001 From: uanela Date: Thu, 22 Jan 2026 20:08:56 +0200 Subject: [PATCH 07/15] fix(node.directory): fix not expanding all folders --- lua/nvim-tree/node/directory.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index 9f137f0cda3..bad28aaa7d2 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -322,9 +322,9 @@ end ---@private ---@param _ integer expansion_count ---@return boolean -function DirectoryNode:descend_until_empty(_) - local EXCLUDE = to_lookup_table(self.explorer.opts.actions.expand_all.exclude) - local should_exclude = EXCLUDE[self.name] +function DirectoryNode:descend_until_empty(node) + local EXCLUDE = to_lookup_table(node.explorer.opts.actions.expand_all.exclude) + local should_exclude = EXCLUDE[node.name] return not should_exclude end @@ -341,7 +341,7 @@ function DirectoryNode:should_expand(expansion_count, node, should_descend) if not dir.open and should_descend(expansion_count, node) then if #node.nodes == 0 then - self.explorer:expand_dir_node(dir) -- populate node.group_next + node.explorer:expand_dir_node(dir) -- populate node.group_next end if dir.group_next then @@ -376,7 +376,7 @@ function DirectoryNode:gen_iterator(should_descend) expansion_count = expansion_count + 1 node = node:as(DirectoryNode) if node then - self:expand_dir_node() + node:expand_dir_node() end end end) From 78224816bc0b63c583f2c25689c48c093631ce51 Mon Sep 17 00:00:00 2001 From: uanela Date: Thu, 22 Jan 2026 21:22:56 +0200 Subject: [PATCH 08/15] refactor(node.director): move gen_iterator to expand and safely call descend_until_empty --- lua/nvim-tree/node/directory.lua | 81 +++++++++++++++----------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index bad28aaa7d2..553f098a3ab 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -320,11 +320,10 @@ local function to_lookup_table(list) end ---@private ----@param _ integer expansion_count ---@return boolean -function DirectoryNode:descend_until_empty(node) - local EXCLUDE = to_lookup_table(node.explorer.opts.actions.expand_all.exclude) - local should_exclude = EXCLUDE[node.name] +function DirectoryNode:descend_until_empty() + local EXCLUDE = to_lookup_table(self.explorer.opts.actions.expand_all.exclude) + local should_exclude = EXCLUDE[self.name] return not should_exclude end @@ -357,53 +356,49 @@ function DirectoryNode:should_expand(expansion_count, node, should_descend) return false end ----@private ----@param should_descend fun(expansion_count: integer, node: Node): boolean ----@return fun(node): any -function DirectoryNode:gen_iterator(should_descend) +---@param expand_opts ApiTreeExpandOpts? +function DirectoryNode:expand(expand_opts) local expansion_count = 0 - return function(parent) - if parent.parent and parent.nodes and not parent.open then - expansion_count = expansion_count + 1 - parent:expand_dir_node() - end + local function safe_descend_until_empty(_, node) + return self.descend_until_empty(node) -- calling with dot so that node is attached as self + end - Iterator.builder(parent.nodes) - :hidden() - :applier(function(node) - if DirectoryNode:should_expand(expansion_count, node, should_descend) then - expansion_count = expansion_count + 1 - node = node:as(DirectoryNode) - if node then - node:expand_dir_node() - end - end - end) - :recursor(function(node) - if not should_descend(expansion_count, node) then - return nil - end + local should_descend = self:limit_folder_discovery((expand_opts and expand_opts.expand_until) or safe_descend_until_empty) - if node.group_next then - return { node.group_next } - end - if node.open and node.nodes then - return node.nodes - end + if self.parent and self.nodes and not self.open then + expansion_count = expansion_count + 1 + self:expand_dir_node() + end + Iterator.builder(self.nodes) + :hidden() + :applier(function(node) + if DirectoryNode:should_expand(expansion_count, node, should_descend) then + expansion_count = expansion_count + 1 + node = node:as(DirectoryNode) + if node then + node:expand_dir_node() + end + end + end) + :recursor(function(node) + if not should_descend(expansion_count, node) then return nil - end) - :iterate() - end -end + end ----@param expand_opts ApiTreeExpandOpts? -function DirectoryNode:expand(expand_opts) - local descend_until_empty_fn = self.descend_until_empty - local descend_until = self:limit_folder_discovery((expand_opts and expand_opts.expand_until) or descend_until_empty_fn) - self:gen_iterator(descend_until)(self) + if node.group_next then + return { node.group_next } + end + + if node.open and node.nodes then + return node.nodes + end + + return nil + end) + :iterate() self.explorer.renderer:draw() end From 2f851d5a68340b49c1ca94c9216137bbfd21c9b4 Mon Sep 17 00:00:00 2001 From: uanela Date: Sat, 24 Jan 2026 11:43:29 +0200 Subject: [PATCH 09/15] feat: move collapse to tree actions and remove modifiers folder --- lua/nvim-tree/actions/tree/{modifiers => }/collapse.lua | 0 lua/nvim-tree/actions/tree/init.lua | 2 +- lua/nvim-tree/actions/tree/modifiers/init.lua | 5 ----- lua/nvim-tree/api.lua | 4 ++-- 4 files changed, 3 insertions(+), 8 deletions(-) rename lua/nvim-tree/actions/tree/{modifiers => }/collapse.lua (100%) delete mode 100644 lua/nvim-tree/actions/tree/modifiers/init.lua diff --git a/lua/nvim-tree/actions/tree/modifiers/collapse.lua b/lua/nvim-tree/actions/tree/collapse.lua similarity index 100% rename from lua/nvim-tree/actions/tree/modifiers/collapse.lua rename to lua/nvim-tree/actions/tree/collapse.lua diff --git a/lua/nvim-tree/actions/tree/init.lua b/lua/nvim-tree/actions/tree/init.lua index bcf14b9b79f..6ae2760fb7c 100644 --- a/lua/nvim-tree/actions/tree/init.lua +++ b/lua/nvim-tree/actions/tree/init.lua @@ -1,7 +1,7 @@ local M = {} M.find_file = require("nvim-tree.actions.tree.find-file") -M.modifiers = require("nvim-tree.actions.tree.modifiers") +M.collapse = require("nvim-tree.actions.tree.collapse") M.open = require("nvim-tree.actions.tree.open") M.toggle = require("nvim-tree.actions.tree.toggle") M.resize = require("nvim-tree.actions.tree.resize") diff --git a/lua/nvim-tree/actions/tree/modifiers/init.lua b/lua/nvim-tree/actions/tree/modifiers/init.lua deleted file mode 100644 index cee579313a2..00000000000 --- a/lua/nvim-tree/actions/tree/modifiers/init.lua +++ /dev/null @@ -1,5 +0,0 @@ -local M = {} - -M.collapse = require("nvim-tree.actions.tree.modifiers.collapse") - -return M diff --git a/lua/nvim-tree/api.lua b/lua/nvim-tree/api.lua index 9680da60835..7bf316231fb 100644 --- a/lua/nvim-tree/api.lua +++ b/lua/nvim-tree/api.lua @@ -176,7 +176,7 @@ Api.tree.search_node = wrap(actions.finders.search_node.fn) ---@class ApiCollapseOpts ---@field keep_buffers boolean|nil default false -Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse.all) +Api.tree.collapse_all = wrap(actions.tree.collapse.all) ---@class ApiTreeExpandOpts ---@field expand_until (fun(expansion_count: integer, node: Node): boolean)|nil @@ -311,7 +311,7 @@ Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn({ where = "next" Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "opened" })) Api.node.expand = wrap_node(wrap_explorer("expand_node")) -Api.node.collapse = wrap_node(actions.tree.modifiers.collapse.node) +Api.node.collapse = wrap_node(actions.tree.collapse.node) ---@class ApiNodeDeleteWipeBufferOpts ---@field force boolean|nil default false From 8f3fd2040db480acf263cdf801dc409345e2b341 Mon Sep 17 00:00:00 2001 From: uanela Date: Sat, 24 Jan 2026 11:44:13 +0200 Subject: [PATCH 10/15] feat: move should_expand to Node for better OO --- lua/nvim-tree/node/directory.lua | 36 ++------------------------------ lua/nvim-tree/node/init.lua | 29 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index 553f098a3ab..b31540bda30 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -327,35 +327,6 @@ function DirectoryNode:descend_until_empty() return not should_exclude end ----@private ----@param expansion_count integer ----@param node Node ----@param should_descend fun(expansion_count: integer, node: Node): boolean ----@return boolean -function DirectoryNode:should_expand(expansion_count, node, should_descend) - local dir = node:as(DirectoryNode) - if not dir then - return false - end - - if not dir.open and should_descend(expansion_count, node) then - if #node.nodes == 0 then - node.explorer:expand_dir_node(dir) -- populate node.group_next - end - - if dir.group_next then - local expand_next = self:should_expand(expansion_count, dir.group_next, should_descend) - if expand_next then - dir.open = true - end - return expand_next - else - return true - end - end - return false -end - ---@param expand_opts ApiTreeExpandOpts? function DirectoryNode:expand(expand_opts) local expansion_count = 0 @@ -375,12 +346,9 @@ function DirectoryNode:expand(expand_opts) Iterator.builder(self.nodes) :hidden() :applier(function(node) - if DirectoryNode:should_expand(expansion_count, node, should_descend) then + if node:should_expand(expansion_count, should_descend) then expansion_count = expansion_count + 1 - node = node:as(DirectoryNode) - if node then - node:expand_dir_node() - end + node:expand_dir_node() end end) :recursor(function(node) diff --git a/lua/nvim-tree/node/init.lua b/lua/nvim-tree/node/init.lua index 1cee60e7afc..cf2c6e50dfa 100644 --- a/lua/nvim-tree/node/init.lua +++ b/lua/nvim-tree/node/init.lua @@ -144,6 +144,35 @@ function Node:clone(api_nodes) return clone end +---@param expansion_count integer +---@param should_descend fun(expansion_count: integer, node: Node): boolean +---@return boolean +function Node:should_expand(expansion_count, should_descend) + local DirectoryNode = require("nvim-tree.node.directory") + + local dir = self:as(DirectoryNode) + if not dir then + return false + end + + if not dir.open and should_descend(expansion_count, self) then + if #self.nodes == 0 then + self.explorer:expand_dir_node(dir) -- populate node.group_next + end + + if dir.group_next then + local expand_next = dir.group_next:should_expand(expansion_count, should_descend) + if expand_next then + dir.open = true + end + return expand_next + else + return true + end + end + return false +end + ---@param expand_opts ApiTreeExpandOpts? function Node:expand(expand_opts) if self.parent then From 34c6f072d72febc7481666dfa8fc3eee40b8ea0e Mon Sep 17 00:00:00 2001 From: uanela Date: Sun, 25 Jan 2026 10:49:42 +0200 Subject: [PATCH 11/15] chore: add should_expand in DirectoryNode to avoid circular dependecy --- lua/nvim-tree/node/directory.lua | 22 ++++++++++++++++++++++ lua/nvim-tree/node/init.lua | 22 ---------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index b31540bda30..98521b7bc63 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -327,6 +327,28 @@ function DirectoryNode:descend_until_empty() return not should_exclude end +---@param expansion_count integer +---@param should_descend fun(expansion_count: integer, node: Node): boolean +---@return boolean +function DirectoryNode:should_expand(expansion_count, should_descend) + if not self.open and should_descend(expansion_count, self) then + if #self.nodes == 0 then + self.explorer:expand_dir_node(self) -- populate node.group_next + end + + if self.group_next then + local expand_next = self.group_next:should_expand(expansion_count, should_descend) + if expand_next then + self.open = true + end + return expand_next + else + return true + end + end + return false +end + ---@param expand_opts ApiTreeExpandOpts? function DirectoryNode:expand(expand_opts) local expansion_count = 0 diff --git a/lua/nvim-tree/node/init.lua b/lua/nvim-tree/node/init.lua index cf2c6e50dfa..3bfc2860c32 100644 --- a/lua/nvim-tree/node/init.lua +++ b/lua/nvim-tree/node/init.lua @@ -148,28 +148,6 @@ end ---@param should_descend fun(expansion_count: integer, node: Node): boolean ---@return boolean function Node:should_expand(expansion_count, should_descend) - local DirectoryNode = require("nvim-tree.node.directory") - - local dir = self:as(DirectoryNode) - if not dir then - return false - end - - if not dir.open and should_descend(expansion_count, self) then - if #self.nodes == 0 then - self.explorer:expand_dir_node(dir) -- populate node.group_next - end - - if dir.group_next then - local expand_next = dir.group_next:should_expand(expansion_count, should_descend) - if expand_next then - dir.open = true - end - return expand_next - else - return true - end - end return false end From afb40e37c0fdd25c39d5315548e9a325ba86f521 Mon Sep 17 00:00:00 2001 From: uanela Date: Sun, 25 Jan 2026 11:05:08 +0200 Subject: [PATCH 12/15] fix(explorer): correctly calling expand as a class method --- lua/nvim-tree/explorer/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua index 1b0d2cf3da7..37cdeca36e7 100644 --- a/lua/nvim-tree/explorer/init.lua +++ b/lua/nvim-tree/explorer/init.lua @@ -694,7 +694,7 @@ function Explorer:expand_all(node, expand_opts) if node then node:expand(expand_opts) else - self.expand(self, expand_opts) + self:expand(expand_opts) end end From 456f8178a3b1e1f8b509748b6be0f1b3bcdf167f Mon Sep 17 00:00:00 2001 From: uanela Date: Sun, 25 Jan 2026 12:17:30 +0200 Subject: [PATCH 13/15] fix: adding underscore on unsed variables --- lua/nvim-tree/node/init.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/nvim-tree/node/init.lua b/lua/nvim-tree/node/init.lua index 3bfc2860c32..ca08b8ad724 100644 --- a/lua/nvim-tree/node/init.lua +++ b/lua/nvim-tree/node/init.lua @@ -144,10 +144,10 @@ function Node:clone(api_nodes) return clone end ----@param expansion_count integer ----@param should_descend fun(expansion_count: integer, node: Node): boolean +---@param _expansion_count integer +---@param _should_descend fun(expansion_count: integer, node: Node): boolean ---@return boolean -function Node:should_expand(expansion_count, should_descend) +function Node:should_expand(_expansion_count, _should_descend) return false end From 721e3cdd51c43b97d0a76f5f054524caf5beb94e Mon Sep 17 00:00:00 2001 From: uanela Date: Sun, 25 Jan 2026 12:44:09 +0200 Subject: [PATCH 14/15] chore: move descend_until_empty to local function --- lua/nvim-tree/node/directory.lua | 46 +++++++++++++++----------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index 98521b7bc63..9059aeace20 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -307,26 +307,6 @@ function DirectoryNode:limit_folder_discovery(should_descend) end end ----@private ----@param list string[] ----@return table -local function to_lookup_table(list) - local table = {} - for _, element in ipairs(list) do - table[element] = true - end - - return table -end - ----@private ----@return boolean -function DirectoryNode:descend_until_empty() - local EXCLUDE = to_lookup_table(self.explorer.opts.actions.expand_all.exclude) - local should_exclude = EXCLUDE[self.name] - return not should_exclude -end - ---@param expansion_count integer ---@param should_descend fun(expansion_count: integer, node: Node): boolean ---@return boolean @@ -349,15 +329,31 @@ function DirectoryNode:should_expand(expansion_count, should_descend) return false end +---@param list string[] +---@return table +local function to_lookup_table(list) + local table = {} + for _, element in ipairs(list) do + table[element] = true + end + + return table +end + +---@param _ integer +---@param node Node +---@return boolean +local function descend_until_empty(_, node) + local EXCLUDE = to_lookup_table(node.explorer.opts.actions.expand_all.exclude) + local should_exclude = EXCLUDE[node.name] + return not should_exclude +end + ---@param expand_opts ApiTreeExpandOpts? function DirectoryNode:expand(expand_opts) local expansion_count = 0 - local function safe_descend_until_empty(_, node) - return self.descend_until_empty(node) -- calling with dot so that node is attached as self - end - - local should_descend = self:limit_folder_discovery((expand_opts and expand_opts.expand_until) or safe_descend_until_empty) + local should_descend = self:limit_folder_discovery((expand_opts and expand_opts.expand_until) or descend_until_empty) if self.parent and self.nodes and not self.open then From 01d45eefe337eff3361bc6dcd86364afbd5423c1 Mon Sep 17 00:00:00 2001 From: uanela Date: Mon, 26 Jan 2026 08:17:19 +0200 Subject: [PATCH 15/15] fix(directory): correctly pass node instead of self ot should_descend --- lua/nvim-tree/node/directory.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index 9059aeace20..82beedd14cc 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -296,14 +296,14 @@ end ---@return fun(expansion_count: integer, node: Node): boolean function DirectoryNode:limit_folder_discovery(should_descend) local MAX_FOLDER_DISCOVERY = self.explorer.opts.actions.expand_all.max_folder_discovery - return function(expansion_count) + return function(expansion_count, node) local should_halt = expansion_count >= MAX_FOLDER_DISCOVERY if should_halt then notify.warn("expansion iteration was halted after " .. MAX_FOLDER_DISCOVERY .. " discovered folders") return false end - return should_descend(expansion_count, self) + return should_descend(expansion_count, node) end end