From c9d4b362cba6f4b4cfdb82a40f8fc652415786d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sindre=20T=2E=20Str=C3=B8m?= Date: Wed, 24 Mar 2021 22:55:52 +0100 Subject: [PATCH 1/4] feat: Implemented optional grouping of empty dirs. Grouping is controlled by the setting `vim.g.nvim_tree_group_empty`. --- lua/nvim-tree.lua | 2 +- lua/nvim-tree/fs.lua | 4 +++ lua/nvim-tree/lib.lua | 16 ++++++++--- lua/nvim-tree/populate.lua | 58 ++++++++++++++++++++++++++++++++++++-- lua/nvim-tree/renderer.lua | 14 ++++++--- 5 files changed, 82 insertions(+), 12 deletions(-) diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index ccf3dd619e1..b85183c4c58 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -95,7 +95,7 @@ function M.on_keypress(mode) if node.name == ".." then return lib.change_dir("..") elseif mode == "cd" and node.entries ~= nil then - return lib.change_dir(node.absolute_path) + return lib.change_dir(lib.get_last_group_node(node).absolute_path) elseif mode == "cd" then return end diff --git a/lua/nvim-tree/fs.lua b/lua/nvim-tree/fs.lua index 22cad45dd5b..db1283b0174 100644 --- a/lua/nvim-tree/fs.lua +++ b/lua/nvim-tree/fs.lua @@ -3,6 +3,7 @@ local luv = vim.loop local open_mode = luv.constants.O_CREAT + luv.constants.O_WRONLY + luv.constants.O_TRUNC local utils = require'nvim-tree.utils' +local lib = require'nvim-tree.lib' local M = {} local clipboard = { move = {}, @@ -41,6 +42,7 @@ local function get_num_entries(iter) end function M.create(node) + node = lib.get_last_group_node(node) if node.name == '..' then return end local add_into @@ -168,6 +170,7 @@ local function do_single_paste(source, dest, action_type, action_fn) end local function do_paste(node, action_type, action_fn) + node = lib.get_last_group_node(node) if node.name == '..' then return end local clip = clipboard[action_type] if #clip == 0 then return end @@ -242,6 +245,7 @@ end function M.rename(with_sub) return function(node) + node = lib.get_last_group_node(node) if node.name == '..' then return end local namelen = node.name:len() diff --git a/lua/nvim-tree/lib.lua b/lua/nvim-tree/lib.lua index 37b7179b34e..03f435e1fad 100644 --- a/lua/nvim-tree/lib.lua +++ b/lua/nvim-tree/lib.lua @@ -46,7 +46,7 @@ M.Tree = { function M.init(with_open, with_render) M.Tree.cwd = luv.cwd() - populate(M.Tree.entries, M.Tree.cwd, M.Tree) + populate(M.Tree.entries, M.Tree.cwd) local stat = luv.fs_stat(M.Tree.cwd) M.Tree.last_modified = stat.mtime.sec @@ -91,13 +91,21 @@ function M.get_node_at_cursor() return get_node_at_line(line)(M.Tree.entries) end +function M.get_last_group_node(node) + local next = node + while next.group_next do + next = next.group_next + end + return next +end + function M.unroll_dir(node) node.open = not node.open if node.has_children then node.has_children = false end if #node.entries > 0 then renderer.draw(M.Tree, true) else - populate(node.entries, node.link_to or node.absolute_path) + populate(node.entries, node.link_to or node.absolute_path, node) renderer.draw(M.Tree, true) end end @@ -113,7 +121,7 @@ end -- TODO update only entries where directory has changed local function refresh_nodes(node) - refresh_entries(node.entries, node.absolute_path or node.cwd) + refresh_entries(node.entries, node.absolute_path or node.cwd, node) for _, entry in ipairs(node.entries) do if entry.entries and entry.open then refresh_nodes(entry) @@ -159,7 +167,7 @@ function M.set_index_and_redraw(fname) if fname:match(entry.match_path..'/') ~= nil then if #entry.entries == 0 then reload = true - populate(entry.entries, entry.absolute_path) + populate(entry.entries, entry.absolute_path, entry) end if entry.open == false then reload = true diff --git a/lua/nvim-tree/populate.lua b/lua/nvim-tree/populate.lua index 59f2246293f..884c1cc86e1 100644 --- a/lua/nvim-tree/populate.lua +++ b/lua/nvim-tree/populate.lua @@ -34,6 +34,7 @@ local function dir_new(cwd, name) match_name = path_to_matching_str(name), match_path = path_to_matching_str(absolute_path), open = false, + group_next = nil, has_children = has_children, entries = {} } @@ -78,6 +79,20 @@ local function link_new(cwd, name) } end +local function should_group(cwd, dirs, files, links) + if #dirs == 1 and #files == 0 and #links == 0 then + return true + end + + if #dirs == 0 and #files == 0 and #links == 1 then + local absolute_path = utils.path_join({ cwd, links[1] }) + local link_to = luv.fs_realpath(absolute_path) + return (link_to ~= nil) and luv.fs_stat(link_to).type == 'directory' + end + + return false +end + local function gen_ignore_check() local ignore_list = {} if vim.g.nvim_tree_ignore and #vim.g.nvim_tree_ignore > 0 then @@ -100,7 +115,7 @@ end local should_ignore = gen_ignore_check() -function M.refresh_entries(entries, cwd) +function M.refresh_entries(entries, cwd, node_entry) local handle = luv.fs_scandir(cwd) if type(handle) == 'string' then api.nvim_err_writeln(handle) @@ -120,10 +135,12 @@ function M.refresh_entries(entries, cwd) local links = {} local files = {} local new_entries = {} + local num_new_entries = 0 while true do local name, t = luv.fs_scandir_next(handle) if not name then break end + num_new_entries = num_new_entries + 1 if not should_ignore(name) then if t == 'directory' then @@ -139,6 +156,21 @@ function M.refresh_entries(entries, cwd) end end + -- Handle grouped dirs + local next_node = node_entry.group_next + if next_node then + next_node.open = node_entry.open + if num_new_entries ~= 1 or not new_entries[next_node.name] then + -- dir is no longer only containing a group dir, or group dir has been removed + -- either way: sever the group link on current dir + node_entry.group_next = nil + named_entries[next_node.name] = next_node + else + M.refresh_entries(entries, next_node.absolute_path, next_node) + return + end + end + local idx = 1 for _, name in ipairs(cached_entries) do if not new_entries[name] then @@ -173,12 +205,18 @@ function M.refresh_entries(entries, cwd) change_prev = false end end - if change_prev then prev = name end + if change_prev and not (next_node and next_node.name == name) then + prev = name + end end end + + if next_node then + table.insert(entries, 1, next_node) + end end -function M.populate(entries, cwd) +function M.populate(entries, cwd, dir_entry) local handle = luv.fs_scandir(cwd) if type(handle) == 'string' then api.nvim_err_writeln(handle) @@ -206,6 +244,20 @@ function M.populate(entries, cwd) -- Create Nodes -- + -- Group empty dirs + if dir_entry and vim.g.nvim_tree_group_empty == 1 then + if should_group(cwd, dirs, files, links) then + local child + if dirs[1] then child = dir_new(cwd, dirs[1]) end + if links[1] then child = link_new(cwd, links[1]) end + if luv.fs_access(child.absolute_path, 'R') then + dir_entry.group_next = child + M.populate(entries, child.absolute_path, child) + return + end + end + end + for _, dirname in ipairs(dirs) do local dir = dir_new(cwd, dirname) if luv.fs_access(dir.absolute_path, 'R') then diff --git a/lua/nvim-tree/renderer.lua b/lua/nvim-tree/renderer.lua index 552bad5ea02..7865c52f15a 100644 --- a/lua/nvim-tree/renderer.lua +++ b/lua/nvim-tree/renderer.lua @@ -252,17 +252,23 @@ local function update_draw_data(tree, depth, markers) local git_icon = get_git_icons(node, index, offset, #icon+1) or "" -- INFO: this is mandatory in order to keep gui attributes (bold/italics) local folder_hl = "NvimTreeFolderName" + local name = node.name + local next = node.group_next + while next do + name = name .. "/" .. next.name + next = next.group_next + end if not has_children then folder_hl = "NvimTreeEmptyFolderName" end - set_folder_hl(index, offset, #icon, #node.name+#git_icon, folder_hl) + set_folder_hl(index, offset, #icon, #name+#git_icon, folder_hl) if git_hl then - set_folder_hl(index, offset, #icon, #node.name+#git_icon, git_hl) + set_folder_hl(index, offset, #icon, #name+#git_icon, git_hl) end index = index + 1 if node.open then - table.insert(lines, padding..icon..git_icon..node.name..(vim.g.nvim_tree_add_trailing == 1 and '/' or '')) + table.insert(lines, padding..icon..git_icon..name..(vim.g.nvim_tree_add_trailing == 1 and '/' or '')) update_draw_data(node, depth + 2, markers) else - table.insert(lines, padding..icon..git_icon..node.name..(vim.g.nvim_tree_add_trailing == 1 and '/' or '')) + table.insert(lines, padding..icon..git_icon..name..(vim.g.nvim_tree_add_trailing == 1 and '/' or '')) end elseif node.link_to then local icon = get_symlink_icon() From 6a1da2b4d802ff5b55810e56a95e40e4cf4c092c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sindre=20T=2E=20Str=C3=B8m?= Date: Thu, 25 Mar 2021 00:53:48 +0100 Subject: [PATCH 2/4] chore: Added documentation for g:nvim_tree_group_empty --- doc/nvim-tree-lua.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index a2068a20bbd..03151e534bc 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -203,6 +203,11 @@ functionnalities. Can be 0 or 1. When 1, appends a trailing slash to folder names. 0 by default. +|g:nvim_tree_group_empty| *g:nvim_tree_group_empty* + +Can be 0 or 1. When 1, folders that contain only one folder are grouped +together. 0 by default. + ============================================================================== INFORMATIONS *nvim-tree-info* From 49648509bc381482c0e5eb874974729422028584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sindre=20T=2E=20Str=C3=B8m?= Date: Thu, 25 Mar 2021 01:18:07 +0100 Subject: [PATCH 3/4] chore: Added some code documentation. --- lua/nvim-tree/lib.lua | 1 + lua/nvim-tree/populate.lua | 31 ++++++++++++++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lua/nvim-tree/lib.lua b/lua/nvim-tree/lib.lua index 03f435e1fad..9fafa7d4183 100644 --- a/lua/nvim-tree/lib.lua +++ b/lua/nvim-tree/lib.lua @@ -91,6 +91,7 @@ function M.get_node_at_cursor() return get_node_at_line(line)(M.Tree.entries) end +-- If node is grouped, return the last node in the group. Otherwise, return the given node. function M.get_last_group_node(node) local next = node while next.group_next do diff --git a/lua/nvim-tree/populate.lua b/lua/nvim-tree/populate.lua index 884c1cc86e1..a2324ec0ae4 100644 --- a/lua/nvim-tree/populate.lua +++ b/lua/nvim-tree/populate.lua @@ -34,7 +34,7 @@ local function dir_new(cwd, name) match_name = path_to_matching_str(name), match_path = path_to_matching_str(absolute_path), open = false, - group_next = nil, + group_next = nil, -- If node is grouped, this points to the next child dir/link node has_children = has_children, entries = {} } @@ -79,6 +79,11 @@ local function link_new(cwd, name) } end +-- Returns true if there is either exactly 1 dir, or exactly 1 symlink dir. Otherwise, false. +-- @param cwd Absolute path to the parent directory +-- @param dirs List of dir names +-- @param files List of file names +-- @param links List of symlink names local function should_group(cwd, dirs, files, links) if #dirs == 1 and #files == 0 and #links == 0 then return true @@ -115,7 +120,7 @@ end local should_ignore = gen_ignore_check() -function M.refresh_entries(entries, cwd, node_entry) +function M.refresh_entries(entries, cwd, parent_node) local handle = luv.fs_scandir(cwd) if type(handle) == 'string' then api.nvim_err_writeln(handle) @@ -157,13 +162,13 @@ function M.refresh_entries(entries, cwd, node_entry) end -- Handle grouped dirs - local next_node = node_entry.group_next + local next_node = parent_node.group_next if next_node then - next_node.open = node_entry.open + next_node.open = parent_node.open if num_new_entries ~= 1 or not new_entries[next_node.name] then -- dir is no longer only containing a group dir, or group dir has been removed -- either way: sever the group link on current dir - node_entry.group_next = nil + parent_node.group_next = nil named_entries[next_node.name] = next_node else M.refresh_entries(entries, next_node.absolute_path, next_node) @@ -216,7 +221,7 @@ function M.refresh_entries(entries, cwd, node_entry) end end -function M.populate(entries, cwd, dir_entry) +function M.populate(entries, cwd, parent_node) local handle = luv.fs_scandir(cwd) if type(handle) == 'string' then api.nvim_err_writeln(handle) @@ -245,14 +250,14 @@ function M.populate(entries, cwd, dir_entry) -- Create Nodes -- -- Group empty dirs - if dir_entry and vim.g.nvim_tree_group_empty == 1 then + if parent_node and vim.g.nvim_tree_group_empty == 1 then if should_group(cwd, dirs, files, links) then - local child - if dirs[1] then child = dir_new(cwd, dirs[1]) end - if links[1] then child = link_new(cwd, links[1]) end - if luv.fs_access(child.absolute_path, 'R') then - dir_entry.group_next = child - M.populate(entries, child.absolute_path, child) + local child_node + if dirs[1] then child_node = dir_new(cwd, dirs[1]) end + if links[1] then child_node = link_new(cwd, links[1]) end + if luv.fs_access(child_node.absolute_path, 'R') then + parent_node.group_next = child_node + M.populate(entries, child_node.absolute_path, child_node) return end end From a5bb8d87e44b6ca7e4b6db2980232dac12f2e258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sindre=20T=2E=20Str=C3=B8m?= Date: Thu, 25 Mar 2021 11:11:01 +0100 Subject: [PATCH 4/4] chore: Updated README to reflect new option --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 653a6bf21e9..f9586b8ce67 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ let g:nvim_tree_width_allow_resize = 1 "0 by default, will not resize the tree let g:nvim_tree_disable_netrw = 0 "1 by default, disables netrw let g:nvim_tree_hijack_netrw = 0 "1 by default, prevents netrw from automatically opening when opening directories (but lets you keep its other utilities) let g:nvim_tree_add_trailing = 1 "0 by default, append a trailing slash to folder names +let g:nvim_tree_group_empty = 1 " 0 by default, compact folders that only contain a single folder into one node in the file tree let g:nvim_tree_show_icons = { \ 'git': 1, \ 'folders': 0,