diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 7b2550ae..3d95ad54 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -273,6 +273,7 @@ Setup may only be run once; subsequent calls will result in a warning. filesystem_watchers = { enable = false, interval = 100, + debounce_delay = 50, }, git = { enable = true, @@ -524,6 +525,10 @@ This will be experimental for a few weeks and will become the default. https://github.com/luvit/luv/blob/master/docs.md#uvfs_poll_startfs_poll-path-interval-callback Type: `number`, Default: `100` (ms) + *nvim-tree.filesystem_watchers.debounce_delay* + Idle milliseconds between filesystem change and action. + Type: `number`, Default: `50` (ms) + *nvim-tree.view* Window / buffer setup. diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 5855f62c..c1e05338 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -524,6 +524,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS filesystem_watchers = { enable = false, interval = 100, + debounce_delay = 50, }, git = { enable = true, diff --git a/lua/nvim-tree/explorer/watch.lua b/lua/nvim-tree/explorer/watch.lua index d084fb18..2243df2a 100644 --- a/lua/nvim-tree/explorer/watch.lua +++ b/lua/nvim-tree/explorer/watch.lua @@ -22,6 +22,21 @@ local function is_git(path) return path:match "%.git$" ~= nil or path:match(utils.path_add_trailing ".git") ~= nil end +local function refresh_path(path) + log.line("watcher", "node event executing '%s'", path) + local n = utils.get_node_from_path(path) + if not n then + return + end + + local node = utils.get_parent_of_group(n) + local project_root, project = reload_and_get_git_project(path) + require("nvim-tree.explorer.reload").reload(node, project) + update_parent_statuses(node, project, project_root) + + require("nvim-tree.renderer").draw() +end + function M.create_watcher(absolute_path) if not M.enabled then return nil @@ -34,19 +49,11 @@ function M.create_watcher(absolute_path) Watcher.new { absolute_path = absolute_path, interval = M.interval, - on_event = function(path) - local n = utils.get_node_from_path(absolute_path) - if not n then - return - end - log.line("watcher", "node event '%s'", path) - - local node = utils.get_parent_of_group(n) - local project_root, project = reload_and_get_git_project(path) - require("nvim-tree.explorer.reload").reload(node, project) - update_parent_statuses(node, project, project_root) - - require("nvim-tree.renderer").draw() + on_event = function(opts) + log.line("watcher", "node event scheduled '%s'", opts.absolute_path) + utils.debounce("explorer:watch:" .. opts.absolute_path, M.debounce_delay, function() + refresh_path(opts.absolute_path) + end) end, } end @@ -54,6 +61,7 @@ end function M.setup(opts) M.enabled = opts.filesystem_watchers.enable M.interval = opts.filesystem_watchers.interval + M.debounce_delay = opts.filesystem_watchers.debounce_delay end return M diff --git a/lua/nvim-tree/git/init.lua b/lua/nvim-tree/git/init.lua index 0a587f83..a8f765ea 100644 --- a/lua/nvim-tree/git/init.lua +++ b/lua/nvim-tree/git/init.lua @@ -5,13 +5,13 @@ local Runner = require "nvim-tree.git.runner" local Watcher = require("nvim-tree.watcher").Watcher local M = { - config = nil, + config = {}, projects = {}, cwd_to_project_root = {}, } function M.reload() - if not M.config.enable then + if not M.config.git.enable then return {} end @@ -24,7 +24,7 @@ end function M.reload_project(project_root, path) local project = M.projects[project_root] - if not project or not M.config.enable then + if not project or not M.config.git.enable then return end @@ -37,7 +37,7 @@ function M.reload_project(project_root, path) path = path, list_untracked = git_utils.should_show_untracked(project_root), list_ignored = true, - timeout = M.config.timeout, + timeout = M.config.git.timeout, } if path then @@ -71,7 +71,8 @@ function M.get_project_root(cwd) return project_root end -function M.reload_tree_at(project_root) +local function reload_tree_at(project_root) + log.line("watcher", "git event executing '%s'", project_root) local root_node = utils.get_node_from_path(project_root) if not root_node then return @@ -98,10 +99,12 @@ function M.reload_tree_at(project_root) end iterate(root_node) + + require("nvim-tree.renderer").draw() end function M.load_project_status(cwd) - if not M.config.enable then + if not M.config.git.enable then return {} end @@ -120,19 +123,25 @@ function M.load_project_status(cwd) project_root = project_root, list_untracked = git_utils.should_show_untracked(project_root), list_ignored = true, - timeout = M.config.timeout, + timeout = M.config.git.timeout, } local watcher = nil - if M.config.watcher.enable then + if M.config.filesystem_watchers.enable then log.line("watcher", "git start") watcher = Watcher.new { absolute_path = utils.path_join { project_root, ".git" }, - interval = M.config.watcher.interval, - on_event = function() + project_root = project_root, + interval = M.config.filesystem_watchers.interval, + on_event = function(opts) + log.line("watcher", "git event scheduled '%s'", opts.project_root) + utils.debounce("git:watcher:" .. opts.project_root, M.config.filesystem_watchers.debounce_delay, function() + reload_tree_at(opts.project_root) + end) + end, + on_event0 = function() log.line("watcher", "git event") M.reload_tree_at(project_root) - require("nvim-tree.renderer").draw() end, } end @@ -151,8 +160,8 @@ function M.purge_state() end function M.setup(opts) - M.config = opts.git - M.config.watcher = opts.filesystem_watchers + M.config.git = opts.git + M.config.filesystem_watchers = opts.filesystem_watchers end return M diff --git a/lua/nvim-tree/utils.lua b/lua/nvim-tree/utils.lua index 59fa757a..adeeb604 100644 --- a/lua/nvim-tree/utils.lua +++ b/lua/nvim-tree/utils.lua @@ -3,7 +3,9 @@ local has_notify, notify = pcall(require, "notify") local a = vim.api local uv = vim.loop -local M = {} +local M = { + debouncers = {}, +} M.is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1 @@ -328,4 +330,25 @@ function M.key_by(tbl, key) return keyed end +---Execute callback timeout ms after the lastest invocation with context. Waiting invocations for that context will be discarded. Caller should this ensure that callback performs the same or functionally equivalent actions. +---@param context string identifies the callback to debounce +---@param timeout number ms to wait +---@param callback function to execute on completion +function M.debounce(context, timeout, callback) + if M.debouncers[context] then + pcall(uv.close, M.debouncers[context]) + end + + M.debouncers[context] = uv.new_timer() + M.debouncers[context]:start( + timeout, + 0, + vim.schedule_wrap(function() + M.debouncers[context]:close() + M.debouncers[context] = nil + callback() + end) + ) +end + return M diff --git a/lua/nvim-tree/watcher.lua b/lua/nvim-tree/watcher.lua index 6baf21c3..15ebeb90 100644 --- a/lua/nvim-tree/watcher.lua +++ b/lua/nvim-tree/watcher.lua @@ -48,7 +48,7 @@ function Watcher:start() if err then log.line("watcher", "poll_cb for %s fail : %s", self._opts.absolute_path, err) else - self._opts.on_event(self._opts.absolute_path) + self._opts.on_event(self._opts) end end)