diff --git a/lua/nvim-tree/explorer/watch.lua b/lua/nvim-tree/explorer/watch.lua index 2f3b49fa..1b6c9977 100644 --- a/lua/nvim-tree/explorer/watch.lua +++ b/lua/nvim-tree/explorer/watch.lua @@ -45,21 +45,23 @@ function M.create_watcher(absolute_path) return nil end - log.line("watcher", "node start '%s'", absolute_path) - return Watcher.new { - absolute_path = absolute_path, - 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, - } + local function callback(watcher) + log.line("watcher", "node event scheduled %s", watcher.context) + utils.debounce(watcher.context, M.debounce_delay, function() + refresh_path(watcher._path) + end) + end + + M.uid = M.uid + 1 + return Watcher:new(absolute_path, callback, { + context = "explorer:watch:" .. absolute_path .. ":" .. M.uid, + }) end function M.setup(opts) M.enabled = opts.filesystem_watchers.enable M.debounce_delay = opts.filesystem_watchers.debounce_delay + M.uid = 0 end return M diff --git a/lua/nvim-tree/git/init.lua b/lua/nvim-tree/git/init.lua index 89158c07..5c1c796c 100644 --- a/lua/nvim-tree/git/init.lua +++ b/lua/nvim-tree/git/init.lua @@ -143,16 +143,17 @@ function M.load_project_status(cwd) local watcher = nil if M.config.filesystem_watchers.enable then log.line("watcher", "git start") - watcher = Watcher.new { - absolute_path = utils.path_join { project_root, ".git" }, + + local callback = function(w) + log.line("watcher", "git event scheduled '%s'", w.project_root) + utils.debounce("git:watcher:" .. w.project_root, M.config.filesystem_watchers.debounce_delay, function() + reload_tree_at(w.project_root) + end) + end + + watcher = Watcher:new(utils.path_join { project_root, ".git" }, callback, { project_root = project_root, - 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, - } + }) end M.projects[project_root] = { diff --git a/lua/nvim-tree/utils.lua b/lua/nvim-tree/utils.lua index 33f4f475..5168af74 100644 --- a/lua/nvim-tree/utils.lua +++ b/lua/nvim-tree/utils.lua @@ -169,15 +169,17 @@ end ---Matching executable files in Windows. ---@param ext string ---@return boolean -local PATHEXT = vim.env.PATHEXT or "" -local wexe = vim.split(PATHEXT:gsub("%.", ""), ";") -local pathexts = {} -for _, v in pairs(wexe) do - pathexts[v] = true -end - function M.is_windows_exe(ext) - return pathexts[ext:upper()] + if not M.pathexts then + local PATHEXT = vim.env.PATHEXT or "" + local wexe = vim.split(PATHEXT:gsub("%.", ""), ";") + M.pathexts = {} + for _, v in pairs(wexe) do + M.pathexts[v] = true + end + end + + return M.pathexts[ext:upper()] end function M.rename_loaded_buffers(old_path, new_path) @@ -379,4 +381,23 @@ function M.clear_prompt() end end +-- return a new table with values from array +function M.array_shallow_clone(array) + local to = {} + for _, v in ipairs(array) do + table.insert(to, v) + end + return to +end + +-- remove item from array if it exists +function M.array_remove(array, item) + for i, v in ipairs(array) do + if v == item then + table.remove(array, i) + break + end + end +end + return M diff --git a/lua/nvim-tree/watcher.lua b/lua/nvim-tree/watcher.lua index 8f5491ea..25af2059 100644 --- a/lua/nvim-tree/watcher.lua +++ b/lua/nvim-tree/watcher.lua @@ -3,10 +3,16 @@ local uv = vim.loop local log = require "nvim-tree.log" local utils = require "nvim-tree.utils" -local M = { +local M = {} + +local Event = { + _events = {}, +} +Event.__index = Event + +local Watcher = { _watchers = {}, } -local Watcher = {} Watcher.__index = Watcher local FS_EVENT_FLAGS = { @@ -16,87 +22,129 @@ local FS_EVENT_FLAGS = { recursive = false, } -function Watcher.new(opts) - for _, existing in ipairs(M._watchers) do - if existing._opts.absolute_path == opts.absolute_path then - log.line("watcher", "Watcher:new using existing '%s'", opts.absolute_path) - return existing - end +function Event:new(path) + log.line("watcher", "Event:new '%s'", path) + + local e = setmetatable({ + _path = path, + _fs_event = nil, + _listeners = {}, + }, Event) + + if e:start() then + Event._events[path] = e + return e + else + return nil end - - log.line("watcher", "Watcher:new '%s'", opts.absolute_path) - - local watcher = setmetatable({ - _opts = opts, - }, Watcher) - - watcher = watcher:start() - - table.insert(M._watchers, watcher) - - return watcher end -function Watcher:start() - log.line("watcher", "Watcher:start '%s'", self._opts.absolute_path) +function Event:start() + log.line("watcher", "Event:start '%s'", self._path) local rc, _, name - self._e, _, name = uv.new_fs_event() - if not self._e then - self._e = nil - utils.notify.warn( - string.format("Could not initialize an fs_event watcher for path %s : %s", self._opts.absolute_path, name) - ) - return nil + self._fs_event, _, name = uv.new_fs_event() + if not self._fs_event then + self._fs_event = nil + utils.notify.warn(string.format("Could not initialize an fs_event watcher for path %s : %s", self._path, name)) + return false end - local event_cb = vim.schedule_wrap(function(err, filename, events) + local event_cb = vim.schedule_wrap(function(err, filename) if err then - log.line("watcher", "event_cb for %s fail : %s", self._opts.absolute_path, err) + log.line("watcher", "event_cb for %s fail : %s", self._path, err) else - log.line("watcher", "event_cb '%s' '%s' %s", self._opts.absolute_path, filename, vim.inspect(events)) - self._opts.on_event(self._opts) + log.line("watcher", "event_cb '%s' '%s'", self._path, filename) + for _, listener in ipairs(self._listeners) do + listener() + end end end) - rc, _, name = self._e:start(self._opts.absolute_path, FS_EVENT_FLAGS, event_cb) + rc, _, name = self._fs_event:start(self._path, FS_EVENT_FLAGS, event_cb) if rc ~= 0 then - utils.notify.warn( - string.format("Could not start the fs_event watcher for path %s : %s", self._opts.absolute_path, name) - ) + utils.notify.warn(string.format("Could not start the fs_event watcher for path %s : %s", self._path, name)) + return false + end + + return true +end + +function Event:add(listener) + table.insert(self._listeners, listener) +end + +function Event:remove(listener) + utils.array_remove(self._listeners, listener) + if #self._listeners == 0 then + self:destroy() + end +end + +function Event:destroy() + log.line("watcher", "Event:destroy '%s'", self._path) + + if self._fs_event then + local rc, _, name = self._fs_event:stop() + if rc ~= 0 then + utils.notify.warn(string.format("Could not stop the fs_event watcher for path %s : %s", self._path, name)) + end + self._fs_event = nil + end + + Event._events[self._path] = nil +end + +function Watcher:new(path, callback, data) + log.line("watcher", "Watcher:new '%s'", path) + + local w = setmetatable(data, Watcher) + + w._event = Event._events[path] or Event:new(path) + w._listener = nil + w._path = path + w._callback = callback + + if not w._event then return nil end - return self + w:start() + + table.insert(Watcher._watchers, w) + + return w +end + +function Watcher:start() + self._listener = function() + self._callback(self) + end + + self._event:add(self._listener) end function Watcher:destroy() - log.line("watcher", "Watcher:destroy '%s'", self._opts.absolute_path) - if self._e then - local rc, _, name = self._e:stop() - if rc ~= 0 then - utils.notify.warn( - string.format("Could not stop the fs_event watcher for path %s : %s", self._opts.absolute_path, name) - ) - end - self._e = nil - end - for i, w in ipairs(M._watchers) do - if w == self then - table.remove(M._watchers, i) - break - end - end + log.line("watcher", "Watcher:destroy '%s'", self._path) + + self._event:remove(self._listener) + + utils.array_remove(Watcher._watchers, self) end M.Watcher = Watcher function M.purge_watchers() - for _, watcher in pairs(M._watchers) do - watcher:destroy() + log.line("watcher", "purge_watchers") + + for _, w in ipairs(utils.array_shallow_clone(Watcher._watchers)) do + w:destroy() + end + + for _, e in pairs(Event._events) do + e:destroy() end - M._watchers = {} end return M