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 = { filesystem_watchers = {
enable = false, enable = false,
interval = 100, interval = 100,
debounce_delay = 50,
}, },
git = { git = {
enable = true, 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 https://github.com/luvit/luv/blob/master/docs.md#uvfs_poll_startfs_poll-path-interval-callback
Type: `number`, Default: `100` (ms) 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* *nvim-tree.view*
Window / buffer setup. Window / buffer setup.

View File

@@ -524,6 +524,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
filesystem_watchers = { filesystem_watchers = {
enable = false, enable = false,
interval = 100, interval = 100,
debounce_delay = 50,
}, },
git = { git = {
enable = true, 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 return path:match "%.git$" ~= nil or path:match(utils.path_add_trailing ".git") ~= nil
end 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) function M.create_watcher(absolute_path)
if not M.enabled then if not M.enabled then
return nil return nil
@@ -34,19 +49,11 @@ function M.create_watcher(absolute_path)
Watcher.new { Watcher.new {
absolute_path = absolute_path, absolute_path = absolute_path,
interval = M.interval, interval = M.interval,
on_event = function(path) on_event = function(opts)
local n = utils.get_node_from_path(absolute_path) log.line("watcher", "node event scheduled '%s'", opts.absolute_path)
if not n then utils.debounce("explorer:watch:" .. opts.absolute_path, M.debounce_delay, function()
return refresh_path(opts.absolute_path)
end 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()
end, end,
} }
end end
@@ -54,6 +61,7 @@ end
function M.setup(opts) function M.setup(opts)
M.enabled = opts.filesystem_watchers.enable M.enabled = opts.filesystem_watchers.enable
M.interval = opts.filesystem_watchers.interval M.interval = opts.filesystem_watchers.interval
M.debounce_delay = opts.filesystem_watchers.debounce_delay
end end
return M return M

View File

@@ -5,13 +5,13 @@ local Runner = require "nvim-tree.git.runner"
local Watcher = require("nvim-tree.watcher").Watcher local Watcher = require("nvim-tree.watcher").Watcher
local M = { local M = {
config = nil, config = {},
projects = {}, projects = {},
cwd_to_project_root = {}, cwd_to_project_root = {},
} }
function M.reload() function M.reload()
if not M.config.enable then if not M.config.git.enable then
return {} return {}
end end
@@ -24,7 +24,7 @@ end
function M.reload_project(project_root, path) function M.reload_project(project_root, path)
local project = M.projects[project_root] 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 return
end end
@@ -37,7 +37,7 @@ function M.reload_project(project_root, path)
path = path, path = path,
list_untracked = git_utils.should_show_untracked(project_root), list_untracked = git_utils.should_show_untracked(project_root),
list_ignored = true, list_ignored = true,
timeout = M.config.timeout, timeout = M.config.git.timeout,
} }
if path then if path then
@@ -71,7 +71,8 @@ function M.get_project_root(cwd)
return project_root return project_root
end 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) local root_node = utils.get_node_from_path(project_root)
if not root_node then if not root_node then
return return
@@ -98,10 +99,12 @@ function M.reload_tree_at(project_root)
end end
iterate(root_node) iterate(root_node)
require("nvim-tree.renderer").draw()
end end
function M.load_project_status(cwd) function M.load_project_status(cwd)
if not M.config.enable then if not M.config.git.enable then
return {} return {}
end end
@@ -120,19 +123,25 @@ function M.load_project_status(cwd)
project_root = project_root, project_root = project_root,
list_untracked = git_utils.should_show_untracked(project_root), list_untracked = git_utils.should_show_untracked(project_root),
list_ignored = true, list_ignored = true,
timeout = M.config.timeout, timeout = M.config.git.timeout,
} }
local watcher = nil local watcher = nil
if M.config.watcher.enable then if M.config.filesystem_watchers.enable then
log.line("watcher", "git start") log.line("watcher", "git start")
watcher = Watcher.new { watcher = Watcher.new {
absolute_path = utils.path_join { project_root, ".git" }, absolute_path = utils.path_join { project_root, ".git" },
interval = M.config.watcher.interval, project_root = project_root,
on_event = function() 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") log.line("watcher", "git event")
M.reload_tree_at(project_root) M.reload_tree_at(project_root)
require("nvim-tree.renderer").draw()
end, end,
} }
end end
@@ -151,8 +160,8 @@ function M.purge_state()
end end
function M.setup(opts) function M.setup(opts)
M.config = opts.git M.config.git = opts.git
M.config.watcher = opts.filesystem_watchers M.config.filesystem_watchers = opts.filesystem_watchers
end end
return M return M

View File

@@ -3,7 +3,9 @@ local has_notify, notify = pcall(require, "notify")
local a = vim.api local a = vim.api
local uv = vim.loop local uv = vim.loop
local M = {} local M = {
debouncers = {},
}
M.is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1 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 return keyed
end 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 return M

View File

@@ -48,7 +48,7 @@ function Watcher:start()
if err then if err then
log.line("watcher", "poll_cb for %s fail : %s", self._opts.absolute_path, err) log.line("watcher", "poll_cb for %s fail : %s", self._opts.absolute_path, err)
else else
self._opts.on_event(self._opts.absolute_path) self._opts.on_event(self._opts)
end end
end) end)