diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 32696f10999..fb586a50889 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -181,6 +181,7 @@ Show the mappings: `g?` `I` Toggle Filter: Git Ignore |nvim-tree-api.tree.toggle_gitignore_filter()| `J` Last Sibling |nvim-tree-api.node.navigate.sibling.last()| `K` First Sibling |nvim-tree-api.node.navigate.sibling.first()| +`L` Toggle Group Empty |nvim-tree-api.node.open.toggle_group_empty()| `M` Toggle Filter: No Bookmark |nvim-tree-api.tree.toggle_no_bookmark_filter()| `m` Toggle Bookmark |nvim-tree-api.marks.toggle()| `o` Open |nvim-tree-api.node.open.edit()| @@ -1848,6 +1849,12 @@ node.open.vertical() *nvim-tree-api.node.open.vertical()* node.open.horizontal() *nvim-tree-api.node.open.horizontal()* |nvim-tree-api.node.edit()|, file will be opened in a new horizontal split. + *nvim-tree-api.node.open.toggle_group_empty()* +node.open.toggle_group_empty() + Toggle |nvim-tree.renderer.group_empty| for a specific folder. + Does nothing on files. + Needs |nvim-tree.renderer.group_empty| set. + node.open.drop() *nvim-tree-api.node.open.drop()* Switch to window with selected file if it exists. Open file otherwise. @@ -2196,6 +2203,7 @@ You are encouraged to copy these to your own |nvim-tree.on_attach| function. vim.keymap.set('n', 'I', api.tree.toggle_gitignore_filter, opts('Toggle Filter: Git Ignore')) vim.keymap.set('n', 'J', api.node.navigate.sibling.last, opts('Last Sibling')) vim.keymap.set('n', 'K', api.node.navigate.sibling.first, opts('First Sibling')) + vim.keymap.set('n', 'L', api.node.open.toggle_group_empty, opts('Toggle Group Empty')) vim.keymap.set('n', 'M', api.tree.toggle_no_bookmark_filter, opts('Toggle Filter: No Bookmark')) vim.keymap.set('n', 'm', api.marks.toggle, opts('Toggle Bookmark')) vim.keymap.set('n', 'o', api.node.open.edit, opts('Open')) diff --git a/lua/nvim-tree/api.lua b/lua/nvim-tree/api.lua index 3c85071e899..aa2265a678a 100644 --- a/lua/nvim-tree/api.lua +++ b/lua/nvim-tree/api.lua @@ -175,13 +175,13 @@ end ---@param mode string ---@return fun(node: table) -local function open_or_expand_or_dir_up(mode) +local function open_or_expand_or_dir_up(mode, toggle_group) return function(node) if node.name == ".." then actions.root.change_dir.fn ".." elseif node.nodes then - lib.expand_or_collapse(node) - else + lib.expand_or_collapse(node, toggle_group) + elseif not toggle_group then edit(mode, node) end end @@ -195,6 +195,7 @@ Api.node.open.no_window_picker = wrap_node(open_or_expand_or_dir_up "edit_no_pic Api.node.open.vertical = wrap_node(open_or_expand_or_dir_up "vsplit") Api.node.open.horizontal = wrap_node(open_or_expand_or_dir_up "split") Api.node.open.tab = wrap_node(open_or_expand_or_dir_up "tabnew") +Api.node.open.toggle_group_empty = wrap_node(open_or_expand_or_dir_up("toggle_group_empty", true)) Api.node.open.preview = wrap_node(open_or_expand_or_dir_up "preview") Api.node.open.preview_no_picker = wrap_node(open_or_expand_or_dir_up "preview_no_picker") diff --git a/lua/nvim-tree/keymap.lua b/lua/nvim-tree/keymap.lua index 700a479461e..0b0797a8dd6 100644 --- a/lua/nvim-tree/keymap.lua +++ b/lua/nvim-tree/keymap.lua @@ -72,6 +72,7 @@ function M.default_on_attach(bufnr) vim.keymap.set('n', 'I', api.tree.toggle_gitignore_filter, opts('Toggle Filter: Git Ignore')) vim.keymap.set('n', 'J', api.node.navigate.sibling.last, opts('Last Sibling')) vim.keymap.set('n', 'K', api.node.navigate.sibling.first, opts('First Sibling')) + vim.keymap.set('n', 'L', api.node.open.toggle_group_empty, opts('Toggle Group Empty')) vim.keymap.set('n', 'M', api.tree.toggle_no_bookmark_filter, opts('Toggle Filter: No Bookmark')) vim.keymap.set('n', 'm', api.marks.toggle, opts('Toggle Bookmark')) vim.keymap.set('n', 'o', api.node.open.edit, opts('Open')) diff --git a/lua/nvim-tree/lib.lua b/lua/nvim-tree/lib.lua index 09642281419..900e9dcd298 100644 --- a/lua/nvim-tree/lib.lua +++ b/lua/nvim-tree/lib.lua @@ -3,6 +3,7 @@ local view = require "nvim-tree.view" local core = require "nvim-tree.core" local utils = require "nvim-tree.utils" local events = require "nvim-tree.events" +local explorer_node = require "nvim-tree.explorer.node" ---@class LibOpenOpts ---@field path string|nil path @@ -86,6 +87,34 @@ function M.get_last_group_node(node) return node end +---Group empty folders +-- Recursively group nodes +---@param node Node +---@return Node[] +function M.group_empty_folders(node) + local is_root = not node.parent + local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1] + if M.group_empty and not is_root and child_folder_only then + node.group_next = child_folder_only + local ns = M.group_empty_folders(child_folder_only) + node.nodes = ns or {} + return ns + end + return node.nodes +end + +---Ungroup empty folders +-- If a node is grouped, ungroup it: put node.group_next to the node.nodes and set node.group_next to nil +---@param node Node +function M.ungroup_empty_folders(node) + local cur = node + while cur and cur.group_next do + cur.nodes = { cur.group_next } + cur.group_next = nil + cur = cur.nodes[1] + end +end + ---@param node Node ---@return Node[] function M.get_all_nodes_in_group(node) @@ -98,8 +127,21 @@ function M.get_all_nodes_in_group(node) return nodes end +-- Toggle group empty folders +---@param head_node Node +local function toggle_group_folders(head_node) + local is_grouped = head_node.group_next ~= nil + + if is_grouped then + M.ungroup_empty_folders(head_node) + else + M.group_empty_folders(head_node) + end +end + ---@param node Node -function M.expand_or_collapse(node) +function M.expand_or_collapse(node, toggle_group) + toggle_group = toggle_group or false if node.has_children then node.has_children = false end @@ -108,9 +150,20 @@ function M.expand_or_collapse(node) core.get_explorer():expand(node) end - local open = not M.get_last_group_node(node).open - for _, n in ipairs(M.get_all_nodes_in_group(node)) do - n.open = open + local head_node = utils.get_parent_of_group(node) + if toggle_group then + toggle_group_folders(head_node) + end + + local open = M.get_last_group_node(node).open + local next_open + if toggle_group then + next_open = open + else + next_open = not open + end + for _, n in ipairs(M.get_all_nodes_in_group(head_node)) do + n.open = next_open end renderer.draw() @@ -213,6 +266,7 @@ function M.setup(opts) M.hijack_directories = opts.hijack_directories M.respect_buf_cwd = opts.respect_buf_cwd M.select_prompts = opts.select_prompts + M.group_empty = opts.renderer.group_empty end return M