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/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 44e3fa67baa..00000000000 --- a/lua/nvim-tree/actions/tree/modifiers/expand.lua +++ /dev/null @@ -1,165 +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 node DirectoryNode -local function expand(node) - node = node:last_group_node() - node.open = true - if #node.nodes == 0 then - core.get_explorer():expand(node) - end -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) -- 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 013d2c2efa4..02aefc33c0f 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 @@ -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 0965e4ab9fc..9f137f0cda3 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 Iterator = require("nvim-tree.iterators.node-iterator") local Node = require("nvim-tree.node") @@ -167,7 +168,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 @@ -290,4 +291,129 @@ function DirectoryNode:clone(api_nodes) return clone end +---@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 + self.explorer:expand_dir_node(node) + end +end + return DirectoryNode diff --git a/lua/nvim-tree/node/init.lua b/lua/nvim-tree/node/init.lua index da0dcc5b0eb..531fcb616e7 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(expand_opts) + else + self:as(DirectoryNode).expand_opts(expand_opts) + end +end + return Node