Skip to content

Commit 709d6b9

Browse files
authored
feat: Option for grouping empty directories (#247)
1 parent e0b9882 commit 709d6b9

File tree

7 files changed

+94
-12
lines changed

7 files changed

+94
-12
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ let g:nvim_tree_width_allow_resize = 1 "0 by default, will not resize the tree
3434
let g:nvim_tree_disable_netrw = 0 "1 by default, disables netrw
3535
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)
3636
let g:nvim_tree_add_trailing = 1 "0 by default, append a trailing slash to folder names
37+
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
3738
let g:nvim_tree_show_icons = {
3839
\ 'git': 1,
3940
\ 'folders': 0,

doc/nvim-tree-lua.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ functionnalities.
203203
Can be 0 or 1. When 1, appends a trailing slash to folder names.
204204
0 by default.
205205

206+
|g:nvim_tree_group_empty| *g:nvim_tree_group_empty*
207+
208+
Can be 0 or 1. When 1, folders that contain only one folder are grouped
209+
together. 0 by default.
210+
206211
==============================================================================
207212
INFORMATIONS *nvim-tree-info*
208213

lua/nvim-tree.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ function M.on_keypress(mode)
9595
if node.name == ".." then
9696
return lib.change_dir("..")
9797
elseif mode == "cd" and node.entries ~= nil then
98-
return lib.change_dir(node.absolute_path)
98+
return lib.change_dir(lib.get_last_group_node(node).absolute_path)
9999
elseif mode == "cd" then
100100
return
101101
end

lua/nvim-tree/fs.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ local luv = vim.loop
33
local open_mode = luv.constants.O_CREAT + luv.constants.O_WRONLY + luv.constants.O_TRUNC
44

55
local utils = require'nvim-tree.utils'
6+
local lib = require'nvim-tree.lib'
67
local M = {}
78
local clipboard = {
89
move = {},
@@ -41,6 +42,7 @@ local function get_num_entries(iter)
4142
end
4243

4344
function M.create(node)
45+
node = lib.get_last_group_node(node)
4446
if node.name == '..' then return end
4547

4648
local add_into
@@ -168,6 +170,7 @@ local function do_single_paste(source, dest, action_type, action_fn)
168170
end
169171

170172
local function do_paste(node, action_type, action_fn)
173+
node = lib.get_last_group_node(node)
171174
if node.name == '..' then return end
172175
local clip = clipboard[action_type]
173176
if #clip == 0 then return end
@@ -242,6 +245,7 @@ end
242245

243246
function M.rename(with_sub)
244247
return function(node)
248+
node = lib.get_last_group_node(node)
245249
if node.name == '..' then return end
246250

247251
local namelen = node.name:len()

lua/nvim-tree/lib.lua

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ M.Tree = {
4646

4747
function M.init(with_open, with_render)
4848
M.Tree.cwd = luv.cwd()
49-
populate(M.Tree.entries, M.Tree.cwd, M.Tree)
49+
populate(M.Tree.entries, M.Tree.cwd)
5050

5151
local stat = luv.fs_stat(M.Tree.cwd)
5252
M.Tree.last_modified = stat.mtime.sec
@@ -91,13 +91,22 @@ function M.get_node_at_cursor()
9191
return get_node_at_line(line)(M.Tree.entries)
9292
end
9393

94+
-- If node is grouped, return the last node in the group. Otherwise, return the given node.
95+
function M.get_last_group_node(node)
96+
local next = node
97+
while next.group_next do
98+
next = next.group_next
99+
end
100+
return next
101+
end
102+
94103
function M.unroll_dir(node)
95104
node.open = not node.open
96105
if node.has_children then node.has_children = false end
97106
if #node.entries > 0 then
98107
renderer.draw(M.Tree, true)
99108
else
100-
populate(node.entries, node.link_to or node.absolute_path)
109+
populate(node.entries, node.link_to or node.absolute_path, node)
101110
renderer.draw(M.Tree, true)
102111
end
103112
end
@@ -113,7 +122,7 @@ end
113122

114123
-- TODO update only entries where directory has changed
115124
local function refresh_nodes(node)
116-
refresh_entries(node.entries, node.absolute_path or node.cwd)
125+
refresh_entries(node.entries, node.absolute_path or node.cwd, node)
117126
for _, entry in ipairs(node.entries) do
118127
if entry.entries and entry.open then
119128
refresh_nodes(entry)
@@ -159,7 +168,7 @@ function M.set_index_and_redraw(fname)
159168
if fname:match(entry.match_path..'/') ~= nil then
160169
if #entry.entries == 0 then
161170
reload = true
162-
populate(entry.entries, entry.absolute_path)
171+
populate(entry.entries, entry.absolute_path, entry)
163172
end
164173
if entry.open == false then
165174
reload = true

lua/nvim-tree/populate.lua

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ local function dir_new(cwd, name)
3434
match_name = path_to_matching_str(name),
3535
match_path = path_to_matching_str(absolute_path),
3636
open = false,
37+
group_next = nil, -- If node is grouped, this points to the next child dir/link node
3738
has_children = has_children,
3839
entries = {}
3940
}
@@ -78,6 +79,25 @@ local function link_new(cwd, name)
7879
}
7980
end
8081

82+
-- Returns true if there is either exactly 1 dir, or exactly 1 symlink dir. Otherwise, false.
83+
-- @param cwd Absolute path to the parent directory
84+
-- @param dirs List of dir names
85+
-- @param files List of file names
86+
-- @param links List of symlink names
87+
local function should_group(cwd, dirs, files, links)
88+
if #dirs == 1 and #files == 0 and #links == 0 then
89+
return true
90+
end
91+
92+
if #dirs == 0 and #files == 0 and #links == 1 then
93+
local absolute_path = utils.path_join({ cwd, links[1] })
94+
local link_to = luv.fs_realpath(absolute_path)
95+
return (link_to ~= nil) and luv.fs_stat(link_to).type == 'directory'
96+
end
97+
98+
return false
99+
end
100+
81101
local function gen_ignore_check()
82102
local ignore_list = {}
83103
if vim.g.nvim_tree_ignore and #vim.g.nvim_tree_ignore > 0 then
@@ -100,7 +120,7 @@ end
100120

101121
local should_ignore = gen_ignore_check()
102122

103-
function M.refresh_entries(entries, cwd)
123+
function M.refresh_entries(entries, cwd, parent_node)
104124
local handle = luv.fs_scandir(cwd)
105125
if type(handle) == 'string' then
106126
api.nvim_err_writeln(handle)
@@ -120,10 +140,12 @@ function M.refresh_entries(entries, cwd)
120140
local links = {}
121141
local files = {}
122142
local new_entries = {}
143+
local num_new_entries = 0
123144

124145
while true do
125146
local name, t = luv.fs_scandir_next(handle)
126147
if not name then break end
148+
num_new_entries = num_new_entries + 1
127149

128150
if not should_ignore(name) then
129151
if t == 'directory' then
@@ -139,6 +161,21 @@ function M.refresh_entries(entries, cwd)
139161
end
140162
end
141163

164+
-- Handle grouped dirs
165+
local next_node = parent_node.group_next
166+
if next_node then
167+
next_node.open = parent_node.open
168+
if num_new_entries ~= 1 or not new_entries[next_node.name] then
169+
-- dir is no longer only containing a group dir, or group dir has been removed
170+
-- either way: sever the group link on current dir
171+
parent_node.group_next = nil
172+
named_entries[next_node.name] = next_node
173+
else
174+
M.refresh_entries(entries, next_node.absolute_path, next_node)
175+
return
176+
end
177+
end
178+
142179
local idx = 1
143180
for _, name in ipairs(cached_entries) do
144181
if not new_entries[name] then
@@ -173,12 +210,18 @@ function M.refresh_entries(entries, cwd)
173210
change_prev = false
174211
end
175212
end
176-
if change_prev then prev = name end
213+
if change_prev and not (next_node and next_node.name == name) then
214+
prev = name
215+
end
177216
end
178217
end
218+
219+
if next_node then
220+
table.insert(entries, 1, next_node)
221+
end
179222
end
180223

181-
function M.populate(entries, cwd)
224+
function M.populate(entries, cwd, parent_node)
182225
local handle = luv.fs_scandir(cwd)
183226
if type(handle) == 'string' then
184227
api.nvim_err_writeln(handle)
@@ -206,6 +249,20 @@ function M.populate(entries, cwd)
206249

207250
-- Create Nodes --
208251

252+
-- Group empty dirs
253+
if parent_node and vim.g.nvim_tree_group_empty == 1 then
254+
if should_group(cwd, dirs, files, links) then
255+
local child_node
256+
if dirs[1] then child_node = dir_new(cwd, dirs[1]) end
257+
if links[1] then child_node = link_new(cwd, links[1]) end
258+
if luv.fs_access(child_node.absolute_path, 'R') then
259+
parent_node.group_next = child_node
260+
M.populate(entries, child_node.absolute_path, child_node)
261+
return
262+
end
263+
end
264+
end
265+
209266
for _, dirname in ipairs(dirs) do
210267
local dir = dir_new(cwd, dirname)
211268
if luv.fs_access(dir.absolute_path, 'R') then

lua/nvim-tree/renderer.lua

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,17 +252,23 @@ local function update_draw_data(tree, depth, markers)
252252
local git_icon = get_git_icons(node, index, offset, #icon+1) or ""
253253
-- INFO: this is mandatory in order to keep gui attributes (bold/italics)
254254
local folder_hl = "NvimTreeFolderName"
255+
local name = node.name
256+
local next = node.group_next
257+
while next do
258+
name = name .. "/" .. next.name
259+
next = next.group_next
260+
end
255261
if not has_children then folder_hl = "NvimTreeEmptyFolderName" end
256-
set_folder_hl(index, offset, #icon, #node.name+#git_icon, folder_hl)
262+
set_folder_hl(index, offset, #icon, #name+#git_icon, folder_hl)
257263
if git_hl then
258-
set_folder_hl(index, offset, #icon, #node.name+#git_icon, git_hl)
264+
set_folder_hl(index, offset, #icon, #name+#git_icon, git_hl)
259265
end
260266
index = index + 1
261267
if node.open then
262-
table.insert(lines, padding..icon..git_icon..node.name..(vim.g.nvim_tree_add_trailing == 1 and '/' or ''))
268+
table.insert(lines, padding..icon..git_icon..name..(vim.g.nvim_tree_add_trailing == 1 and '/' or ''))
263269
update_draw_data(node, depth + 2, markers)
264270
else
265-
table.insert(lines, padding..icon..git_icon..node.name..(vim.g.nvim_tree_add_trailing == 1 and '/' or ''))
271+
table.insert(lines, padding..icon..git_icon..name..(vim.g.nvim_tree_add_trailing == 1 and '/' or ''))
266272
end
267273
elseif node.link_to then
268274
local icon = get_symlink_icon()

0 commit comments

Comments
 (0)