feat(watcher): debounce FS watchers

This commit is contained in:
Alexander Courtis
2022-06-28 11:18:22 +10:00
parent 7a795d78fa
commit e401a4c957
6 changed files with 74 additions and 28 deletions

View File

@@ -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.

View File

@@ -524,6 +524,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
filesystem_watchers = {
enable = false,
interval = 100,
debounce_delay = 50,
},
git = {
enable = true,

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)