Skip to content
3 changes: 3 additions & 0 deletions lua/neo-tree/defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ local config = {
folder_closed = "",
folder_open = "",
folder_empty = "ﰊ",
folder_empty_open = "ﰊ",
-- The next two settings are only a fallback, if you use nvim-web-devicons and configure default icons there
-- then these will never be used.
default = "*",
Expand Down Expand Up @@ -365,6 +366,8 @@ local config = {
async_directory_scan = "auto", -- "auto" means refreshes are async, but it's synchronous when called from the Neotree commands.
-- "always" means directory scans are always async.
-- "never" means directory scans are never async.
scan_mode = "shallow", -- "shallow": Don't scan into directories to detect possible empty directory a priori
-- "deep": Scan into directories to detect empty or grouped empty directories a priori.
bind_to_cwd = true, -- true creates a 2-way binding between vim's cwd and neo-tree's root
cwd_target = {
sidebar = "tab", -- sidebar is when position = left or right
Expand Down
6 changes: 5 additions & 1 deletion lua/neo-tree/sources/common/components.lua
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,17 @@ M.filtered_by = function(config, node, state)
return result
end



M.icon = function(config, node, state)
local icon = config.default or " "
local highlight = config.highlight or highlights.FILE_ICON
if node.type == "directory" then
highlight = highlights.DIRECTORY_ICON
if node.loaded and not node:has_children() then
icon = config.folder_empty or config.folder_open or "-"
icon = not node.empty_expanded
and config.folder_empty
or config.folder_empty_open
elseif node:is_expanded() then
icon = config.folder_open or "-"
else
Expand Down
3 changes: 3 additions & 0 deletions lua/neo-tree/sources/filesystem/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,9 @@ M.toggle_directory = function(state, node, path_to_reveal, skip_redraw, recursiv
if path_to_reveal then
renderer.focus_node(state, path_to_reveal)
end
elseif require("neo-tree").config.filesystem.scan_mode == "deep" then
node.empty_expanded = not node.empty_expanded
renderer.redraw(state)
end
end

Expand Down
268 changes: 187 additions & 81 deletions lua/neo-tree/sources/filesystem/lib/fs_scan.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ local log = require("neo-tree.log")
local fs_watch = require("neo-tree.sources.filesystem.lib.fs_watch")
local git = require("neo-tree.git")
local events = require("neo-tree.events")
local async = require("plenary.async")

local Path = require("plenary.path")
local os_sep = Path.path.sep
Expand Down Expand Up @@ -138,114 +139,218 @@ local job_complete = function(context)
render_context(context)
end


local function create_node(context, node)
local success3, item = pcall(file_items.create_item, context, node.path, node.type)
end

local function process_node(context, path)
on_directory_loaded(context, path)
end

local function get_children_sync(path)
local children = {}
local success, dir = pcall(vim.loop.fs_opendir, path, nil, 1000)
if not success then
log.error("Error opening dir:", dir)
end
local success2, stats = pcall(vim.loop.fs_readdir, dir)
if success2 and stats then
for _, stat in ipairs(stats) do
local child_path = utils.path_join(path, stat.name)
table.insert(children, { path = child_path, type = stat.type })
end
end
vim.loop.fs_closedir(dir)
return children
end

local function get_children_async(path, callback)
uv.fs_opendir(path, function(_, dir)
uv.fs_readdir(dir, function(_, stats)
local children = {}
if stats then
for _, stat in ipairs(stats) do
local child_path = utils.path_join(path, stat.name)
table.insert(children, { path = child_path, type = stat.type })
end
end
uv.fs_closedir(dir)
callback(children)
end)
end, 1000)
end

local function scan_dir_sync(context, path)
process_node(context, path)
local children = get_children_sync(path)
for _, child in ipairs(children) do
create_node(context, child)
if child.type == "directory" then
local grandchild_nodes = get_children_sync(child.path)
if
grandchild_nodes == nil
or #grandchild_nodes == 0
or #grandchild_nodes == 1 and grandchild_nodes[1].type == "directory"
then
scan_dir_sync(context, child.path)
end
end
end
end

local function scan_dir_async(context, path, callback)
get_children_async(path, function(children)
for _, child in ipairs(children) do
create_node(context, child)
if child.type == "directory" then
local grandchild_nodes = get_children_sync(child.path)
if
grandchild_nodes == nil
or #grandchild_nodes == 0
or #grandchild_nodes == 1 and grandchild_nodes[1].type == "directory"
then
scan_dir_sync(context, child.path)
end
end
end
process_node(context, path)
callback(path)
end)
end

-- async_scan scans all the directories in context.paths_to_load
-- and adds them as items to render in the UI.
local function async_scan(context, path)
log.trace("async_scan: ", path)
-- prepend the root path
table.insert(context.paths_to_load, 1, path)
local scan_mode = require("neo-tree").config.filesystem.scan_mode
if scan_mode == "deep" then
local scan_tasks = {}
for _, p in ipairs(context.paths_to_load) do
local scan_task = async.wrap(function(callback)
scan_dir_async(context, p, callback)
end, 1)
table.insert(scan_tasks, scan_task)
end

context.directories_scanned = 0
context.directories_to_scan = #context.paths_to_load
async.util.run_all(
scan_tasks,
vim.schedule_wrap(function()
job_complete(context)
end)
)
else -- scan_mode == "shallow"
context.directories_scanned = 0
context.directories_to_scan = #context.paths_to_load

context.on_exit = vim.schedule_wrap(function()
job_complete(context)
end)
context.on_exit = vim.schedule_wrap(function()
job_complete(context)
end)

-- from https://github.com/nvim-lua/plenary.nvim/blob/master/lua/plenary/scandir.lua
local function read_dir(current_dir, ctx)
local function on_fs_opendir(err, dir)
if err then
log.error(current_dir, ": ", err)
else
local function on_fs_readdir(err, entries)
if err then
log.error(current_dir, ": ", err)
else
if entries then
for _, entry in ipairs(entries) do
local success, item = pcall(
file_items.create_item,
ctx,
utils.path_join(current_dir, entry.name),
entry.type
)
if success then
if ctx.recursive and item.type == "directory" then
ctx.directories_to_scan = ctx.directories_to_scan + 1
table.insert(ctx.paths_to_load, item.path)
-- from https://github.com/nvim-lua/plenary.nvim/blob/master/lua/plenary/scandir.lua
local function read_dir(current_dir, ctx)
local function on_fs_opendir(err, dir)
if err then
log.error(current_dir, ": ", err)
else
local function on_fs_readdir(err, entries)
if err then
log.error(current_dir, ": ", err)
else
if entries then
for _, entry in ipairs(entries) do
local success, item = pcall(
file_items.create_item,
ctx,
utils.path_join(current_dir, entry.name),
entry.type
)
if success then
if ctx.recursive and item.type == "directory" then
ctx.directories_to_scan = ctx.directories_to_scan + 1
table.insert(ctx.paths_to_load, item.path)
end
else
log.error("error creating item for ", path)
end
else
log.error("error creating item for ", path)
end
end

uv.fs_readdir(dir, on_fs_readdir)
else
uv.fs_closedir(dir)
on_directory_loaded(ctx, current_dir)
ctx.directories_scanned = ctx.directories_scanned + 1
if ctx.directories_scanned == #ctx.paths_to_load then
ctx.on_exit()
uv.fs_readdir(dir, on_fs_readdir)
else
uv.fs_closedir(dir)
on_directory_loaded(ctx, current_dir)
ctx.directories_scanned = ctx.directories_scanned + 1
if ctx.directories_scanned == #ctx.paths_to_load then
ctx.on_exit()
end
end
end

--local next_path = dir_complete(ctx, current_dir)
--if next_path then
-- local success, error = pcall(read_dir, next_path)
-- if not success then
-- log.error(next_path, ": ", error)
-- end
--else
-- on_exit()
--end
end

--local next_path = dir_complete(ctx, current_dir)
--if next_path then
-- local success, error = pcall(read_dir, next_path)
-- if not success then
-- log.error(next_path, ": ", error)
-- end
--else
-- on_exit()
--end
uv.fs_readdir(dir, on_fs_readdir)
end

uv.fs_readdir(dir, on_fs_readdir)
end
end

uv.fs_opendir(current_dir, on_fs_opendir)
end
uv.fs_opendir(current_dir, on_fs_opendir)
end

--local first = table.remove(context.paths_to_load)
--local success, err = pcall(read_dir, first)
--if not success then
-- log.error(first, ": ", err)
--end
for i = 1, context.directories_to_scan do
read_dir(context.paths_to_load[i], context)
--local first = table.remove(context.paths_to_load)
--local success, err = pcall(read_dir, first)
--if not success then
-- log.error(first, ": ", err)
--end
for i = 1, context.directories_to_scan do
read_dir(context.paths_to_load[i], context)
end
end
end

local function sync_scan(context, path_to_scan)
log.trace("sync_scan: ", path_to_scan)
local success, dir = pcall(vim.loop.fs_opendir, path_to_scan, nil, 1000)
if not success then
log.error("Error opening dir:", dir)
end
local success2, stats = pcall(vim.loop.fs_readdir, dir)
if success2 and stats then
for _, stat in ipairs(stats) do
local path = utils.path_join(path_to_scan, stat.name)
local success3, item = pcall(file_items.create_item, context, path, stat.type)
if success3 then
if context.recursive and stat.type == "directory" then
table.insert(context.paths_to_load, path)
local scan_mode = require("neo-tree").config.filesystem.scan_mode
if scan_mode == "deep" then
for _, path in ipairs(context.paths_to_load) do
scan_dir_sync(context, path)
-- scan_dir(context, path)
end
job_complete(context)
else -- scan_mode == "shallow"
local success, dir = pcall(vim.loop.fs_opendir, path_to_scan, nil, 1000)
if not success then
log.error("Error opening dir:", dir)
end
local success2, stats = pcall(vim.loop.fs_readdir, dir)
if success2 and stats then
for _, stat in ipairs(stats) do
local path = utils.path_join(path_to_scan, stat.name)
local success3, item = pcall(file_items.create_item, context, path, stat.type)
if success3 then
if context.recursive and stat.type == "directory" then
table.insert(context.paths_to_load, path)
end
else
log.error("error creating item for ", path)
end
else
log.error("error creating item for ", path)
end
end
end
vim.loop.fs_closedir(dir)
vim.loop.fs_closedir(dir)

local next_path = dir_complete(context, path_to_scan)
if next_path then
sync_scan(context, next_path)
else
job_complete(context)
local next_path = dir_complete(context, path_to_scan)
if next_path then
sync_scan(context, next_path)
else
job_complete(context)
end
end
end

Expand Down Expand Up @@ -357,6 +462,7 @@ M.get_items = function(state, parent_id, path_to_reveal, callback, async, recurs
end
return false
end
table.insert(context.paths_to_load, path)
if async then
async_scan(context, path)
else
Expand Down
Loading