Skip to content

Commit fea2e5b

Browse files
committed
Rewrite recursive expand to use plenary.async
1 parent f3ac4a9 commit fea2e5b

File tree

5 files changed

+197
-52
lines changed

5 files changed

+197
-52
lines changed

lua/neo-tree/git/ignored.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ M.mark_ignored = function(state, items, callback)
118118
on_exit = function(self, code, _)
119119
local result
120120
if code ~= 0 then
121-
log.debug("Failed to load ignored files for", state.path, ":", self:stderr_result())
121+
log.debug("Failed to load ignored files for", folder, ":", self:stderr_result())
122122
result = {}
123123
else
124124
result = self:result()

lua/neo-tree/sources/common/commands.lua

+18-28
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
local vim = vim
44
local fs_actions = require("neo-tree.sources.filesystem.lib.fs_actions")
5+
local fs = require("neo-tree.sources.filesystem")
56
local utils = require("neo-tree.utils")
67
local renderer = require("neo-tree.ui.renderer")
78
local events = require("neo-tree.events")
@@ -10,6 +11,7 @@ local popups = require("neo-tree.ui.popups")
1011
local log = require("neo-tree.log")
1112
local help = require("neo-tree.sources.common.help")
1213
local Preview = require("neo-tree.sources.common.preview")
14+
local async = require("plenary.async")
1315

1416
---Gets the node parent folder
1517
---@param state table to look for nodes
@@ -108,35 +110,23 @@ M.add_directory = function(state, callback)
108110
fs_actions.create_directory(in_directory, callback, using_root_directory)
109111
end
110112

111-
M.expand_all_nodes = function(state, toggle_directory)
112-
if toggle_directory == nil then
113-
toggle_directory = function(_, node)
114-
node:expand()
115-
end
116-
end
117-
--state.explicitly_opened_directories = state.explicitly_opened_directories or {}
118-
119-
local expand_node
120-
expand_node = function(node)
121-
local id = node:get_id()
122-
if node.type == "directory" and not node:is_expanded() then
123-
toggle_directory(state, node)
124-
node = state.tree:get_node(id)
125-
end
126-
local children = state.tree:get_nodes(id)
127-
if children then
128-
for _, child in ipairs(children) do
129-
if child.type == "directory" then
130-
expand_node(child)
131-
end
113+
---Expand all nodes
114+
---@param state table The state of the source
115+
---@param node table A node to expand
116+
M.expand_all_nodes = function(state, node)
117+
log.debug("Expanding all nodes under " .. node:get_id())
118+
local task = function ()
119+
fs.expand_directory(state, node)
120+
end
121+
async.run(
122+
task,
123+
function ()
124+
log.debug("All nodes expanded - redrawing")
125+
vim.schedule_wrap(function()
126+
renderer.redraw(state)
127+
end)
132128
end
133-
end
134-
end
135-
136-
for _, node in ipairs(state.tree:get_nodes()) do
137-
expand_node(node)
138-
end
139-
renderer.redraw(state)
129+
)
140130
end
141131

142132
M.close_node = function(state, callback)

lua/neo-tree/sources/filesystem/commands.lua

+1-4
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,7 @@ M.delete_visual = function(state, selected_nodes)
6868
end
6969

7070
M.expand_all_nodes = function(state)
71-
local toggle_dir_no_redraw = function(_state, node)
72-
fs.toggle_directory(_state, node, nil, true, true)
73-
end
74-
cc.expand_all_nodes(state, toggle_dir_no_redraw)
71+
cc.expand_all_nodes(state, state.tree:get_node(state.path))
7572
end
7673

7774
---Shows the filter input, which will filter the tree.

lua/neo-tree/sources/filesystem/init.lua

+73
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ local log = require("neo-tree.log")
1010
local manager = require("neo-tree.sources.manager")
1111
local git = require("neo-tree.git")
1212
local glob = require("neo-tree.sources.filesystem.lib.globtopattern")
13+
local async = require("plenary.async")
1314

1415
local M = {
1516
name = "filesystem",
@@ -428,4 +429,76 @@ M.toggle_directory = function(state, node, path_to_reveal, skip_redraw, recursiv
428429
end
429430
end
430431

432+
--- Recursively expand all loaded nodes under the given node
433+
--- returns table with all discovered nodes that need to be loaded
434+
---@param node table a node to expand
435+
---@param state table current state of the source
436+
---@return table discovered nodes that need to be loaded
437+
local function expand_loaded(node, state)
438+
local function rec(_node, to_load)
439+
if _node.loaded == false then
440+
table.insert(to_load, _node)
441+
else
442+
if not _node:is_expanded() then
443+
_node:expand()
444+
state.explicitly_opened_directories[_node:get_id()] = true
445+
end
446+
local children = state.tree:get_nodes(_node:get_id())
447+
log.debug("Expanding childrens of " .. _node:get_id())
448+
for _, child in ipairs(children) do
449+
if child.type == "directory" then
450+
rec(child, to_load)
451+
else
452+
log.trace("Child: " .. child.name .. " is not a directory, skipping")
453+
end
454+
end
455+
end
456+
end
457+
458+
local to_load = {}
459+
rec(node, to_load)
460+
return to_load
461+
end
462+
463+
--- Recursively expands all nodes under the given node
464+
--- loading nodes if necessary.
465+
--- asyn method
466+
---@param node table a node to expand
467+
---@param state table current state of the source
468+
local function expand_and_load(node, state)
469+
local function rec(to_load, progress)
470+
local to_load_current = expand_loaded(node, state)
471+
for _,v in ipairs(to_load_current) do
472+
table.insert(to_load, v)
473+
end
474+
if progress <= #to_load then
475+
M.expand_directory(state, to_load[progress])
476+
rec(to_load, progress + 1)
477+
end
478+
end
479+
rec({}, 1)
480+
end
481+
482+
---Expands given node recursively loading all descendant nodes if needed
483+
---async method
484+
---@param state table current state of the source
485+
---@param node table a node to expand
486+
M.expand_directory = function(state, node)
487+
log.debug("Expanding directory " .. node:get_id())
488+
if node.type ~= "directory" then
489+
return
490+
end
491+
state.explicitly_opened_directories = state.explicitly_opened_directories or {}
492+
if node.loaded == false then
493+
local id = node:get_id()
494+
state.explicitly_opened_directories[id] = true
495+
renderer.position.set(state, nil)
496+
fs_scan.get_dir_items_async(state, id, true)
497+
-- ignore results as we know here that all descendant nodes have been already loaded
498+
expand_loaded(node ,state)
499+
else
500+
expand_and_load(node, state)
501+
end
502+
end
503+
431504
return M

lua/neo-tree/sources/filesystem/lib/fs_scan.lua

+104-19
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,26 @@ local render_context = function(context)
106106
context = nil
107107
end
108108

109+
local job_complete_async = function(context)
110+
local state = context.state
111+
local parent_id = context.parent_id
112+
if #context.all_items == 0 then
113+
log.info("No items, skipping git ignored/status lookups")
114+
elseif state.filtered_items.hide_gitignored or state.enable_git_status then
115+
local mark_ignored_async = async.wrap(function (_state, _all_items, _callback)
116+
git.mark_ignored(_state, _all_items, _callback)
117+
end, 3)
118+
local all_items = mark_ignored_async(state, context.all_items)
119+
120+
if parent_id then
121+
vim.list_extend(state.git_ignored, all_items)
122+
else
123+
state.git_ignored = all_items
124+
end
125+
end
126+
return context
127+
end
128+
109129
local job_complete = function(context)
110130
local state = context.state
111131
local parent_id = context.parent_id
@@ -135,8 +155,8 @@ local job_complete = function(context)
135155
state.git_ignored = all_items
136156
end
137157
end
158+
render_context(context)
138159
end
139-
render_context(context)
140160
end
141161

142162
local function create_node(context, node)
@@ -199,26 +219,34 @@ local function scan_dir_sync(context, path)
199219
end
200220
end
201221

202-
local function scan_dir_async(context, path, callback)
203-
get_children_async(path, function(children)
204-
for _, child in ipairs(children) do
205-
create_node(context, child)
206-
if child.type == "directory" then
207-
local grandchild_nodes = get_children_sync(child.path)
208-
if
209-
grandchild_nodes == nil
210-
or #grandchild_nodes == 0
211-
or #grandchild_nodes == 1 and grandchild_nodes[1].type == "directory"
212-
then
213-
scan_dir_sync(context, child.path)
214-
end
222+
local function scan_dir_async(context, path)
223+
log.debug("scan_dir_async - start " .. path)
224+
225+
local get_children = async.wrap(function (callback)
226+
return get_children_async(path, callback)
227+
end, 1)
228+
229+
local children = get_children()
230+
for _, child in ipairs(children) do
231+
create_node(context, child)
232+
if child.type == "directory" then
233+
local grandchild_nodes = get_children_sync(child.path)
234+
if
235+
grandchild_nodes == nil
236+
or #grandchild_nodes == 0
237+
or #grandchild_nodes == 1 and grandchild_nodes[1].type == "directory"
238+
then
239+
scan_dir_sync(context, child.path)
215240
end
216241
end
217-
process_node(context, path)
218-
callback(path)
219-
end)
242+
end
243+
244+
log.debug("scan_dir_async - finish " .. path)
245+
process_node(context, path)
246+
return path
220247
end
221248

249+
222250
-- async_scan scans all the directories in context.paths_to_load
223251
-- and adds them as items to render in the UI.
224252
local function async_scan(context, path)
@@ -229,7 +257,9 @@ local function async_scan(context, path)
229257
local scan_tasks = {}
230258
for _, p in ipairs(context.paths_to_load) do
231259
local scan_task = async.wrap(function(callback)
232-
scan_dir_async(context, p, callback)
260+
async.run(function ()
261+
scan_dir_async(context, p)
262+
end, callback)
233263
end, 1)
234264
table.insert(scan_tasks, scan_task)
235265
end
@@ -471,7 +501,6 @@ M.get_items = function(state, parent_id, path_to_reveal, callback, async, recurs
471501
context.path_to_reveal = path_to_reveal
472502
context.recursive = recursive
473503
context.callback = callback
474-
475504
-- Create root folder
476505
local root = file_items.create_item(context, parent_id or state.path, "directory")
477506
root.name = vim.fn.fnamemodify(root.path, ":~")
@@ -490,6 +519,62 @@ M.get_items = function(state, parent_id, path_to_reveal, callback, async, recurs
490519
end
491520
end
492521

522+
-- async method
523+
M.get_dir_items_async = function(state, parent_id, recursive)
524+
local context = file_items.create_context()
525+
context.state = state
526+
context.parent_id = parent_id
527+
context.path_to_reveal = nil
528+
context.recursive = recursive
529+
context.callback = nil
530+
context.paths_to_load = {}
531+
532+
-- Create root folder
533+
local root = file_items.create_item(context, parent_id or state.path, "directory")
534+
root.name = vim.fn.fnamemodify(root.path, ":~")
535+
root.loaded = true
536+
root.search_pattern = state.search_pattern
537+
context.root = root
538+
context.folders[root.path] = root
539+
state.default_expanded_nodes = state.force_open_folders or { state.path }
540+
541+
local filtered_items = state.filtered_items or {}
542+
context.is_a_never_show_file = function(fname)
543+
if fname then
544+
local _, name = utils.split_path(fname)
545+
if name then
546+
if filtered_items.never_show and filtered_items.never_show[name] then
547+
return true
548+
end
549+
if utils.is_filtered_by_pattern(filtered_items.never_show_by_pattern, fname, name) then
550+
return true
551+
end
552+
end
553+
end
554+
return false
555+
end
556+
table.insert(context.paths_to_load, parent_id)
557+
558+
local scan_tasks = {}
559+
for _, p in ipairs(context.paths_to_load) do
560+
local scan_task = function ()
561+
scan_dir_async(context, p)
562+
end
563+
table.insert(scan_tasks, scan_task)
564+
end
565+
async.util.join(scan_tasks)
566+
567+
job_complete_async(context)
568+
569+
local finalize = async.wrap(function (_context, _callback)
570+
vim.schedule(function ()
571+
render_context(_context)
572+
_callback()
573+
end)
574+
end, 2)
575+
finalize(context)
576+
end
577+
493578
M.stop_watchers = function(state)
494579
if state.use_libuv_file_watcher and state.tree then
495580
-- We are loaded a new root or refreshing, unwatch any folders that were

0 commit comments

Comments
 (0)