feat(watcher): debounce FS watchers
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -524,6 +524,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
||||
filesystem_watchers = {
|
||||
enable = false,
|
||||
interval = 100,
|
||||
debounce_delay = 50,
|
||||
},
|
||||
git = {
|
||||
enable = true,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user