Skip to content

feat: Option for grouping empty directories #247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions doc/nvim-tree-lua.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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*

Expand Down
2 changes: 1 addition & 1 deletion lua/nvim-tree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions lua/nvim-tree/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {},
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
17 changes: 13 additions & 4 deletions lua/nvim-tree/lib.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -91,13 +91,22 @@ 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
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
Expand All @@ -113,7 +122,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)
Expand Down Expand Up @@ -159,7 +168,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
Expand Down
63 changes: 60 additions & 3 deletions lua/nvim-tree/populate.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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, -- If node is grouped, this points to the next child dir/link node
has_children = has_children,
entries = {}
}
Expand Down Expand Up @@ -78,6 +79,25 @@ 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
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
Expand All @@ -100,7 +120,7 @@ end

local should_ignore = gen_ignore_check()

function M.refresh_entries(entries, cwd)
function M.refresh_entries(entries, cwd, parent_node)
local handle = luv.fs_scandir(cwd)
if type(handle) == 'string' then
api.nvim_err_writeln(handle)
Expand All @@ -120,10 +140,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
Expand All @@ -139,6 +161,21 @@ function M.refresh_entries(entries, cwd)
end
end

-- Handle grouped dirs
local next_node = parent_node.group_next
if next_node then
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
parent_node.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
Expand Down Expand Up @@ -173,12 +210,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, parent_node)
local handle = luv.fs_scandir(cwd)
if type(handle) == 'string' then
api.nvim_err_writeln(handle)
Expand Down Expand Up @@ -206,6 +249,20 @@ function M.populate(entries, cwd)

-- Create Nodes --

-- Group empty dirs
if parent_node and vim.g.nvim_tree_group_empty == 1 then
if should_group(cwd, dirs, files, links) then
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
end

for _, dirname in ipairs(dirs) do
local dir = dir_new(cwd, dirname)
if luv.fs_access(dir.absolute_path, 'R') then
Expand Down
14 changes: 10 additions & 4 deletions lua/nvim-tree/renderer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down