From 3d72e5c28636e55a43ecc80311ea3227446f1d6f Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sun, 8 Sep 2024 08:47:09 +1000 Subject: [PATCH 1/3] refactor(#2883): multi instance explore --- lua/nvim-tree/explorer/init.lua | 123 ++++++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 13 deletions(-) diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua index cdfb0d5aeec..fb2539b2ddc 100644 --- a/lua/nvim-tree/explorer/init.lua +++ b/lua/nvim-tree/explorer/init.lua @@ -30,8 +30,6 @@ local config ---@field clipboard Clipboard local Explorer = {} -Explorer.explore = require("nvim-tree.explorer.explore").explore - ---@param path string|nil ---@return Explorer|nil function Explorer:new(path) @@ -264,17 +262,7 @@ end function Explorer:_load(node) local cwd = node.link_to or node.absolute_path local git_status = git.load_project_status(cwd) - Explorer.explore(node, git_status, self) -end - -function Explorer.setup(opts) - config = opts - require("nvim-tree.explorer.node").setup(opts) - require("nvim-tree.explorer.explore").setup(opts) - require("nvim-tree.explorer.watch").setup(opts) - - Marks = require "nvim-tree.marks" - Clipboard = require "nvim-tree.actions.fs.clipboard" + self:explore(node, git_status, self) end ---@private @@ -337,4 +325,113 @@ function Explorer:update_parent_statuses(node, project, root) end end +---@private +---@param handle uv.uv_fs_t +---@param cwd string +---@param node Node +---@param git_status table +---@param parent Explorer +function Explorer:populate_children(handle, cwd, node, git_status, parent) + local node_ignored = explorer_node.is_git_ignored(node) + local nodes_by_path = utils.bool_record(node.nodes, "absolute_path") + + local filter_status = parent.filters:prepare(git_status) + + node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, { + git = 0, + buf = 0, + dotfile = 0, + custom = 0, + bookmark = 0, + }) + + while true do + local name, t = vim.loop.fs_scandir_next(handle) + if not name then + break + end + + local abs = utils.path_join { cwd, name } + + if Watcher.is_fs_event_capable(abs) then + local profile = log.profile_start("populate_children %s", abs) + + ---@type uv.fs_stat.result|nil + local stat = vim.loop.fs_stat(abs) + local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status) + if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then + local child = nil + if t == "directory" and vim.loop.fs_access(abs, "R") then + child = builders.folder(node, abs, name, stat) + elseif t == "file" then + child = builders.file(node, abs, name, stat) + elseif t == "link" then + local link = builders.link(node, abs, name, stat) + if link.link_to ~= nil then + child = link + end + end + if child then + table.insert(node.nodes, child) + nodes_by_path[child.absolute_path] = true + explorer_node.update_git_status(child, node_ignored, git_status) + end + else + for reason, value in pairs(FILTER_REASON) do + if filter_reason == value then + node.hidden_stats[reason] = node.hidden_stats[reason] + 1 + end + end + end + + log.profile_end(profile) + end + end +end + +---@private +---@param node Node +---@param status table +---@param parent Explorer +---@return Node[]|nil +function Explorer:explore(node, status, parent) + local cwd = node.link_to or node.absolute_path + local handle = vim.loop.fs_scandir(cwd) + if not handle then + return + end + + local profile = log.profile_start("explore %s", node.absolute_path) + + self:populate_children(handle, cwd, node, status, parent) + + local is_root = not node.parent + local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1] + if config.renderer.group_empty and not is_root and child_folder_only then + local child_cwd = child_folder_only.link_to or child_folder_only.absolute_path + local child_status = git.load_project_status(child_cwd) + node.group_next = child_folder_only + local ns = self:explore(child_folder_only, child_status, parent) + node.nodes = ns or {} + + log.profile_end(profile) + return ns + end + + parent.sorters:sort(node.nodes) + parent.live_filter:apply_filter(node) + + log.profile_end(profile) + return node.nodes +end + +function Explorer.setup(opts) + config = opts + require("nvim-tree.explorer.node").setup(opts) + require("nvim-tree.explorer.watch").setup(opts) + + Marks = require "nvim-tree.marks" + Clipboard = require "nvim-tree.actions.fs.clipboard" +end + return Explorer From 5dc7351cea1fe5b64f7d653f5b5172db1482b004 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sun, 8 Sep 2024 12:35:42 +1000 Subject: [PATCH 2/3] refactor(#2882): multi instance reloaders --- lua/nvim-tree.lua | 24 +++++-- lua/nvim-tree/actions/fs/clipboard.lua | 3 +- lua/nvim-tree/actions/fs/remove-file.lua | 6 +- lua/nvim-tree/actions/fs/rename-file.lua | 6 +- lua/nvim-tree/actions/fs/trash.lua | 12 ++-- lua/nvim-tree/actions/init.lua | 1 - lua/nvim-tree/actions/reloaders.lua | 72 ------------------- .../actions/tree/modifiers/toggles.lua | 27 ++++--- lua/nvim-tree/api.lua | 17 ++++- lua/nvim-tree/explorer/init.lua | 48 +++++++++++++ lua/nvim-tree/explorer/node.lua | 21 ++++++ lua/nvim-tree/marks/init.lua | 2 +- 12 files changed, 137 insertions(+), 102 deletions(-) delete mode 100644 lua/nvim-tree/actions/reloaders.lua diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 2ed0723805b..aa48597d2c2 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -201,7 +201,10 @@ local function setup_autocommands(opts) create_nvim_tree_autocmd("BufWritePost", { callback = function() if opts.auto_reload_on_write and not opts.filesystem_watchers.enable then - actions.reloaders.reload_explorer() + local explorer = core.get_explorer() + if explorer then + explorer:reload_explorer() + end end end, }) @@ -217,7 +220,7 @@ local function setup_autocommands(opts) (explorer.filters.config.filter_no_buffer or renderer.config.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then utils.debounce("Buf:filter_buffer", opts.view.debounce_delay, function() - actions.reloaders.reload_explorer() + explorer:reload_explorer() end) end end, @@ -234,7 +237,7 @@ local function setup_autocommands(opts) (explorer.filters.config.filter_no_buffer or renderer.config.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then utils.debounce("Buf:filter_buffer", opts.view.debounce_delay, function() - actions.reloaders.reload_explorer() + explorer:reload_explorer() end) end end, @@ -244,7 +247,10 @@ local function setup_autocommands(opts) pattern = { "FugitiveChanged", "NeogitStatusRefreshed" }, callback = function() if not opts.filesystem_watchers.enable and opts.git.enable then - actions.reloaders.reload_git() + local explorer = core.get_explorer() + if explorer then + explorer:reload_git() + end end end, }) @@ -292,7 +298,10 @@ local function setup_autocommands(opts) callback = function() if utils.is_nvim_tree_buf(0) then if vim.fn.getcwd() ~= core.get_cwd() or (opts.reload_on_bufenter and not opts.filesystem_watchers.enable) then - actions.reloaders.reload_explorer() + local explorer = core.get_explorer() + if explorer then + explorer:reload_explorer() + end end end end, @@ -343,7 +352,10 @@ local function setup_autocommands(opts) callback = function() utils.debounce("Buf:modified", opts.view.debounce_delay, function() buffers.reload_modified() - actions.reloaders.reload_explorer() + local explorer = core.get_explorer() + if explorer then + explorer:reload_explorer() + end end) end, }) diff --git a/lua/nvim-tree/actions/fs/clipboard.lua b/lua/nvim-tree/actions/fs/clipboard.lua index e3714d6437b..fccb7c1159c 100644 --- a/lua/nvim-tree/actions/fs/clipboard.lua +++ b/lua/nvim-tree/actions/fs/clipboard.lua @@ -5,7 +5,6 @@ local core = require "nvim-tree.core" local events = require "nvim-tree.events" local notify = require "nvim-tree.notify" local renderer = require "nvim-tree.renderer" -local reloaders = require "nvim-tree.actions.reloaders" local find_file = require("nvim-tree.actions.finders.find-file").fn @@ -248,7 +247,7 @@ function Clipboard:do_paste(node, action, action_fn) self.data[action] = {} if not self.config.filesystem_watchers.enable then - reloaders.reload_explorer() + self.explorer:reload_explorer() end end diff --git a/lua/nvim-tree/actions/fs/remove-file.lua b/lua/nvim-tree/actions/fs/remove-file.lua index fa17039e72b..14a30d3aa7e 100644 --- a/lua/nvim-tree/actions/fs/remove-file.lua +++ b/lua/nvim-tree/actions/fs/remove-file.lua @@ -1,3 +1,4 @@ +local core = require "nvim-tree.core" local utils = require "nvim-tree.utils" local events = require "nvim-tree.events" local view = require "nvim-tree.view" @@ -116,8 +117,9 @@ function M.fn(node) local function do_remove() M.remove(node) - if not M.config.filesystem_watchers.enable then - require("nvim-tree.actions.reloaders").reload_explorer() + local explorer = core.get_explorer() + if not M.config.filesystem_watchers.enable and explorer then + explorer:reload_explorer() end end diff --git a/lua/nvim-tree/actions/fs/rename-file.lua b/lua/nvim-tree/actions/fs/rename-file.lua index 949d41fe695..b75c511d856 100644 --- a/lua/nvim-tree/actions/fs/rename-file.lua +++ b/lua/nvim-tree/actions/fs/rename-file.lua @@ -1,3 +1,4 @@ +local core = require "nvim-tree.core" local lib = require "nvim-tree.lib" local utils = require "nvim-tree.utils" local events = require "nvim-tree.events" @@ -155,7 +156,10 @@ function M.fn(default_modifier) M.rename(node, prepend .. new_file_path .. append) if not M.config.filesystem_watchers.enable then - require("nvim-tree.actions.reloaders").reload_explorer() + local explorer = core.get_explorer() + if explorer then + explorer:reload_explorer() + end end find_file(utils.path_remove_trailing(new_file_path)) diff --git a/lua/nvim-tree/actions/fs/trash.lua b/lua/nvim-tree/actions/fs/trash.lua index 8188d867172..09d9c19a985 100644 --- a/lua/nvim-tree/actions/fs/trash.lua +++ b/lua/nvim-tree/actions/fs/trash.lua @@ -1,6 +1,6 @@ +local core = require "nvim-tree.core" local lib = require "nvim-tree.lib" local notify = require "nvim-tree.notify" -local reloaders = require "nvim-tree.actions.reloaders" local M = { config = {}, @@ -52,6 +52,8 @@ function M.remove(node) end end + local explorer = core.get_explorer() + if node.nodes ~= nil and not node.link_to then trash_path(function(_, rc) if rc ~= 0 then @@ -59,8 +61,8 @@ function M.remove(node) return end events._dispatch_folder_removed(node.absolute_path) - if not M.config.filesystem_watchers.enable then - reloaders.reload_explorer() + if not M.config.filesystem_watchers.enable and explorer then + explorer:reload_explorer() end end) else @@ -72,8 +74,8 @@ function M.remove(node) end events._dispatch_file_removed(node.absolute_path) clear_buffer(node.absolute_path) - if not M.config.filesystem_watchers.enable then - reloaders.reload_explorer() + if not M.config.filesystem_watchers.enable and explorer then + explorer:reload_explorer() end end) end diff --git a/lua/nvim-tree/actions/init.lua b/lua/nvim-tree/actions/init.lua index f96caf44e5a..72523ba7299 100644 --- a/lua/nvim-tree/actions/init.lua +++ b/lua/nvim-tree/actions/init.lua @@ -4,7 +4,6 @@ M.finders = require "nvim-tree.actions.finders" M.fs = require "nvim-tree.actions.fs" M.moves = require "nvim-tree.actions.moves" M.node = require "nvim-tree.actions.node" -M.reloaders = require "nvim-tree.actions.reloaders" M.root = require "nvim-tree.actions.root" M.tree = require "nvim-tree.actions.tree" diff --git a/lua/nvim-tree/actions/reloaders.lua b/lua/nvim-tree/actions/reloaders.lua deleted file mode 100644 index 02126b515d3..00000000000 --- a/lua/nvim-tree/actions/reloaders.lua +++ /dev/null @@ -1,72 +0,0 @@ -local git = require "nvim-tree.git" -local view = require "nvim-tree.view" -local renderer = require "nvim-tree.renderer" -local core = require "nvim-tree.core" -local explorer_node = require "nvim-tree.explorer.node" -local Iterator = require "nvim-tree.iterators.node-iterator" - -local M = {} - ----@param explorer Explorer|nil ----@param projects table -local function refresh_nodes(explorer, projects) - Iterator.builder({ explorer }) - :applier(function(n) - if n.nodes then - local toplevel = git.get_toplevel(n.cwd or n.link_to or n.absolute_path) - if explorer then - explorer:reload(n, projects[toplevel] or {}) - end - end - end) - :recursor(function(n) - return n.group_next and { n.group_next } or (n.open and n.nodes) - end) - :iterate() -end - ----@param parent_node Node|nil ----@param projects table -function M.reload_node_status(parent_node, projects) - if parent_node == nil then - return - end - - local toplevel = git.get_toplevel(parent_node.absolute_path) - local status = projects[toplevel] or {} - for _, node in ipairs(parent_node.nodes) do - explorer_node.update_git_status(node, explorer_node.is_git_ignored(parent_node), status) - if node.nodes and #node.nodes > 0 then - M.reload_node_status(node, projects) - end - end -end - -local event_running = false -function M.reload_explorer() - if event_running or not core.get_explorer() or vim.v.exiting ~= vim.NIL then - return - end - event_running = true - - local projects = git.reload() - refresh_nodes(core.get_explorer(), projects) - if view.is_visible() then - renderer.draw() - end - event_running = false -end - -function M.reload_git() - if not core.get_explorer() or not git.config.git.enable or event_running then - return - end - event_running = true - - local projects = git.reload() - M.reload_node_status(core.get_explorer(), projects) - renderer.draw() - event_running = false -end - -return M diff --git a/lua/nvim-tree/actions/tree/modifiers/toggles.lua b/lua/nvim-tree/actions/tree/modifiers/toggles.lua index fdcb21d777d..9cf8e5dde65 100644 --- a/lua/nvim-tree/actions/tree/modifiers/toggles.lua +++ b/lua/nvim-tree/actions/tree/modifiers/toggles.lua @@ -1,12 +1,12 @@ local lib = require "nvim-tree.lib" local utils = require "nvim-tree.utils" -local reloaders = require "nvim-tree.actions.reloaders" local core = require "nvim-tree.core" local M = {} -local function reload() +---@param explorer Explorer +local function reload(explorer) local node = lib.get_node_at_cursor() - reloaders.reload_explorer() + explorer:reload_explorer() utils.focus_node_or_parent(node) end @@ -19,39 +19,46 @@ local function wrap_explorer(fn) end end +---@param explorer Explorer local function custom(explorer) explorer.filters.config.filter_custom = not explorer.filters.config.filter_custom - reload() + reload(explorer) end +---@param explorer Explorer local function git_ignored(explorer) explorer.filters.config.filter_git_ignored = not explorer.filters.config.filter_git_ignored - reload() + reload(explorer) end +---@param explorer Explorer local function git_clean(explorer) explorer.filters.config.filter_git_clean = not explorer.filters.config.filter_git_clean - reload() + reload(explorer) end +---@param explorer Explorer local function no_buffer(explorer) explorer.filters.config.filter_no_buffer = not explorer.filters.config.filter_no_buffer - reload() + reload(explorer) end +---@param explorer Explorer local function no_bookmark(explorer) explorer.filters.config.filter_no_bookmark = not explorer.filters.config.filter_no_bookmark - reload() + reload(explorer) end +---@param explorer Explorer local function dotfiles(explorer) explorer.filters.config.filter_dotfiles = not explorer.filters.config.filter_dotfiles - reload() + reload(explorer) end +---@param explorer Explorer local function enable(explorer) explorer.filters.config.enable = not explorer.filters.config.enable - reload() + reload(explorer) end M.custom = wrap_explorer(custom) diff --git a/lua/nvim-tree/api.lua b/lua/nvim-tree/api.lua index 815c1dd7794..7970f430220 100644 --- a/lua/nvim-tree/api.lua +++ b/lua/nvim-tree/api.lua @@ -72,6 +72,19 @@ local function wrap_node_or_nil(fn) end end +---Invoke a method on the singleton explorer. +---Print error when setup not called. +---@param explorer_method string explorer method name +---@return fun(...) : any +local function wrap_explorer(explorer_method) + return wrap(function(...) + local explorer = core.get_explorer() + if explorer then + return explorer[explorer_method](explorer, ...) + end + end) +end + ---Invoke a member's method on the singleton explorer. ---Print error when setup not called. ---@param explorer_member string explorer member name @@ -108,7 +121,7 @@ Api.tree.toggle = wrap(actions.tree.toggle.fn) Api.tree.close = wrap(view.close) Api.tree.close_in_this_tab = wrap(view.close_this_tab_only) Api.tree.close_in_all_tabs = wrap(view.close_all_tabs) -Api.tree.reload = wrap(actions.reloaders.reload_explorer) +Api.tree.reload = wrap_explorer("reload_explorer") ---@class ApiTreeResizeOpts ---@field width string|function|number|table|nil @@ -243,7 +256,7 @@ Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn { Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn { where = "next", what = "opened" }) Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "opened" }) -Api.git.reload = wrap(actions.reloaders.reload_git) +Api.git.reload = wrap_explorer("reload_git") Api.events.subscribe = events.subscribe Api.events.Event = events.Event diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua index fb2539b2ddc..1b6b6d12b3a 100644 --- a/lua/nvim-tree/explorer/init.lua +++ b/lua/nvim-tree/explorer/init.lua @@ -2,10 +2,13 @@ local builders = require "nvim-tree.explorer.node-builders" local git = require "nvim-tree.git" local log = require "nvim-tree.log" local notify = require "nvim-tree.notify" +local renderer = {} -- circular dependency, will become a member local utils = require "nvim-tree.utils" +local view = require "nvim-tree.view" local watch = require "nvim-tree.explorer.watch" local explorer_node = require "nvim-tree.explorer.node" +local Iterator = require "nvim-tree.iterators.node-iterator" local NodeIterator = require "nvim-tree.iterators.node-iterator" local Watcher = require "nvim-tree.watcher" @@ -425,11 +428,56 @@ function Explorer:explore(node, status, parent) return node.nodes end +---@private +---@param projects table +function Explorer:refresh_nodes(projects) + Iterator.builder({ self }) + :applier(function(n) + if n.nodes then + local toplevel = git.get_toplevel(n.cwd or n.link_to or n.absolute_path) + self:reload(n, projects[toplevel] or {}) + end + end) + :recursor(function(n) + return n.group_next and { n.group_next } or (n.open and n.nodes) + end) + :iterate() +end + +local event_running = false +function Explorer:reload_explorer() + if event_running or vim.v.exiting ~= vim.NIL then + return + end + event_running = true + + local projects = git.reload() + self:refresh_nodes(projects) + if view.is_visible() then + renderer.draw() + end + event_running = false +end + +function Explorer:reload_git() + if not git.config.git.enable or event_running then + return + end + event_running = true + + local projects = git.reload() + explorer_node.reload_node_status(self, projects) + renderer.draw() + event_running = false +end + function Explorer.setup(opts) config = opts require("nvim-tree.explorer.node").setup(opts) require("nvim-tree.explorer.watch").setup(opts) + renderer = require "nvim-tree.renderer" + Marks = require "nvim-tree.marks" Clipboard = require "nvim-tree.actions.fs.clipboard" end diff --git a/lua/nvim-tree/explorer/node.lua b/lua/nvim-tree/explorer/node.lua index 16e149f72f8..4380be542ff 100644 --- a/lua/nvim-tree/explorer/node.lua +++ b/lua/nvim-tree/explorer/node.lua @@ -1,3 +1,5 @@ +local git = {} -- circular dependencies + local M = {} ---@class GitStatus @@ -122,6 +124,23 @@ function M.get_git_status(node) end end +---@param parent_node Node|nil +---@param projects table +function M.reload_node_status(parent_node, projects) + if parent_node == nil then + return + end + + local toplevel = git.get_toplevel(parent_node.absolute_path) + local status = projects[toplevel] or {} + for _, node in ipairs(parent_node.nodes) do + M.update_git_status(node, M.is_git_ignored(parent_node), status) + if node.nodes and #node.nodes > 0 then + M.reload_node_status(node, projects) + end + end +end + ---@param node Node ---@return boolean function M.is_git_ignored(node) @@ -157,6 +176,8 @@ function M.setup(opts) M.config = { git = opts.git, } + + git = require "nvim-tree.git" end return M diff --git a/lua/nvim-tree/marks/init.lua b/lua/nvim-tree/marks/init.lua index c639d1c8f69..85d87fdc2b2 100644 --- a/lua/nvim-tree/marks/init.lua +++ b/lua/nvim-tree/marks/init.lua @@ -38,7 +38,7 @@ end function Marks:clear_reload() self:clear() if not self.config.filesystem_watchers.enable then - require("nvim-tree.actions.reloaders").reload_explorer() + self.explorer:reload_explorer() end end From 6451d923dbc65a11a4a897955b46c316fb7ce30e Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sun, 8 Sep 2024 12:43:21 +1000 Subject: [PATCH 3/3] style --- lua/nvim-tree/api.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/nvim-tree/api.lua b/lua/nvim-tree/api.lua index 7970f430220..91054db54e0 100644 --- a/lua/nvim-tree/api.lua +++ b/lua/nvim-tree/api.lua @@ -121,7 +121,7 @@ Api.tree.toggle = wrap(actions.tree.toggle.fn) Api.tree.close = wrap(view.close) Api.tree.close_in_this_tab = wrap(view.close_this_tab_only) Api.tree.close_in_all_tabs = wrap(view.close_all_tabs) -Api.tree.reload = wrap_explorer("reload_explorer") +Api.tree.reload = wrap_explorer "reload_explorer" ---@class ApiTreeResizeOpts ---@field width string|function|number|table|nil @@ -256,7 +256,7 @@ Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn { Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn { where = "next", what = "opened" }) Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "opened" }) -Api.git.reload = wrap_explorer("reload_git") +Api.git.reload = wrap_explorer "reload_git" Api.events.subscribe = events.subscribe Api.events.Event = events.Event