Skip to content

Commit eff1db3

Browse files
authored
chore(watchers): Watcher shares single fs_event from Event, node watchers use unique path prefixed debounce context (#1453)
1 parent e522297 commit eff1db3

File tree

4 files changed

+153
-81
lines changed

4 files changed

+153
-81
lines changed

lua/nvim-tree/explorer/watch.lua

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,23 @@ function M.create_watcher(absolute_path)
4545
return nil
4646
end
4747

48-
log.line("watcher", "node start '%s'", absolute_path)
49-
return Watcher.new {
50-
absolute_path = absolute_path,
51-
on_event = function(opts)
52-
log.line("watcher", "node event scheduled '%s'", opts.absolute_path)
53-
utils.debounce("explorer:watch:" .. opts.absolute_path, M.debounce_delay, function()
54-
refresh_path(opts.absolute_path)
55-
end)
56-
end,
57-
}
48+
local function callback(watcher)
49+
log.line("watcher", "node event scheduled %s", watcher.context)
50+
utils.debounce(watcher.context, M.debounce_delay, function()
51+
refresh_path(watcher._path)
52+
end)
53+
end
54+
55+
M.uid = M.uid + 1
56+
return Watcher:new(absolute_path, callback, {
57+
context = "explorer:watch:" .. absolute_path .. ":" .. M.uid,
58+
})
5859
end
5960

6061
function M.setup(opts)
6162
M.enabled = opts.filesystem_watchers.enable
6263
M.debounce_delay = opts.filesystem_watchers.debounce_delay
64+
M.uid = 0
6365
end
6466

6567
return M

lua/nvim-tree/git/init.lua

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,17 @@ function M.load_project_status(cwd)
143143
local watcher = nil
144144
if M.config.filesystem_watchers.enable then
145145
log.line("watcher", "git start")
146-
watcher = Watcher.new {
147-
absolute_path = utils.path_join { project_root, ".git" },
146+
147+
local callback = function(w)
148+
log.line("watcher", "git event scheduled '%s'", w.project_root)
149+
utils.debounce("git:watcher:" .. w.project_root, M.config.filesystem_watchers.debounce_delay, function()
150+
reload_tree_at(w.project_root)
151+
end)
152+
end
153+
154+
watcher = Watcher:new(utils.path_join { project_root, ".git" }, callback, {
148155
project_root = project_root,
149-
on_event = function(opts)
150-
log.line("watcher", "git event scheduled '%s'", opts.project_root)
151-
utils.debounce("git:watcher:" .. opts.project_root, M.config.filesystem_watchers.debounce_delay, function()
152-
reload_tree_at(opts.project_root)
153-
end)
154-
end,
155-
}
156+
})
156157
end
157158

158159
M.projects[project_root] = {

lua/nvim-tree/utils.lua

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,15 +169,17 @@ end
169169
---Matching executable files in Windows.
170170
---@param ext string
171171
---@return boolean
172-
local PATHEXT = vim.env.PATHEXT or ""
173-
local wexe = vim.split(PATHEXT:gsub("%.", ""), ";")
174-
local pathexts = {}
175-
for _, v in pairs(wexe) do
176-
pathexts[v] = true
177-
end
178-
179172
function M.is_windows_exe(ext)
180-
return pathexts[ext:upper()]
173+
if not M.pathexts then
174+
local PATHEXT = vim.env.PATHEXT or ""
175+
local wexe = vim.split(PATHEXT:gsub("%.", ""), ";")
176+
M.pathexts = {}
177+
for _, v in pairs(wexe) do
178+
M.pathexts[v] = true
179+
end
180+
end
181+
182+
return M.pathexts[ext:upper()]
181183
end
182184

183185
function M.rename_loaded_buffers(old_path, new_path)
@@ -379,4 +381,23 @@ function M.clear_prompt()
379381
end
380382
end
381383

384+
-- return a new table with values from array
385+
function M.array_shallow_clone(array)
386+
local to = {}
387+
for _, v in ipairs(array) do
388+
table.insert(to, v)
389+
end
390+
return to
391+
end
392+
393+
-- remove item from array if it exists
394+
function M.array_remove(array, item)
395+
for i, v in ipairs(array) do
396+
if v == item then
397+
table.remove(array, i)
398+
break
399+
end
400+
end
401+
end
402+
382403
return M

lua/nvim-tree/watcher.lua

Lines changed: 102 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ local uv = vim.loop
33
local log = require "nvim-tree.log"
44
local utils = require "nvim-tree.utils"
55

6-
local M = {
6+
local M = {}
7+
8+
local Event = {
9+
_events = {},
10+
}
11+
Event.__index = Event
12+
13+
local Watcher = {
714
_watchers = {},
815
}
9-
local Watcher = {}
1016
Watcher.__index = Watcher
1117

1218
local FS_EVENT_FLAGS = {
@@ -16,87 +22,129 @@ local FS_EVENT_FLAGS = {
1622
recursive = false,
1723
}
1824

19-
function Watcher.new(opts)
20-
for _, existing in ipairs(M._watchers) do
21-
if existing._opts.absolute_path == opts.absolute_path then
22-
log.line("watcher", "Watcher:new using existing '%s'", opts.absolute_path)
23-
return existing
24-
end
25-
end
26-
27-
log.line("watcher", "Watcher:new '%s'", opts.absolute_path)
25+
function Event:new(path)
26+
log.line("watcher", "Event:new '%s'", path)
2827

29-
local watcher = setmetatable({
30-
_opts = opts,
31-
}, Watcher)
28+
local e = setmetatable({
29+
_path = path,
30+
_fs_event = nil,
31+
_listeners = {},
32+
}, Event)
3233

33-
watcher = watcher:start()
34-
35-
table.insert(M._watchers, watcher)
36-
37-
return watcher
34+
if e:start() then
35+
Event._events[path] = e
36+
return e
37+
else
38+
return nil
39+
end
3840
end
3941

40-
function Watcher:start()
41-
log.line("watcher", "Watcher:start '%s'", self._opts.absolute_path)
42+
function Event:start()
43+
log.line("watcher", "Event:start '%s'", self._path)
4244

4345
local rc, _, name
4446

45-
self._e, _, name = uv.new_fs_event()
46-
if not self._e then
47-
self._e = nil
48-
utils.notify.warn(
49-
string.format("Could not initialize an fs_event watcher for path %s : %s", self._opts.absolute_path, name)
50-
)
51-
return nil
47+
self._fs_event, _, name = uv.new_fs_event()
48+
if not self._fs_event then
49+
self._fs_event = nil
50+
utils.notify.warn(string.format("Could not initialize an fs_event watcher for path %s : %s", self._path, name))
51+
return false
5252
end
5353

54-
local event_cb = vim.schedule_wrap(function(err, filename, events)
54+
local event_cb = vim.schedule_wrap(function(err, filename)
5555
if err then
56-
log.line("watcher", "event_cb for %s fail : %s", self._opts.absolute_path, err)
56+
log.line("watcher", "event_cb for %s fail : %s", self._path, err)
5757
else
58-
log.line("watcher", "event_cb '%s' '%s' %s", self._opts.absolute_path, filename, vim.inspect(events))
59-
self._opts.on_event(self._opts)
58+
log.line("watcher", "event_cb '%s' '%s'", self._path, filename)
59+
for _, listener in ipairs(self._listeners) do
60+
listener()
61+
end
6062
end
6163
end)
6264

63-
rc, _, name = self._e:start(self._opts.absolute_path, FS_EVENT_FLAGS, event_cb)
65+
rc, _, name = self._fs_event:start(self._path, FS_EVENT_FLAGS, event_cb)
6466
if rc ~= 0 then
65-
utils.notify.warn(
66-
string.format("Could not start the fs_event watcher for path %s : %s", self._opts.absolute_path, name)
67-
)
68-
return nil
67+
utils.notify.warn(string.format("Could not start the fs_event watcher for path %s : %s", self._path, name))
68+
return false
6969
end
7070

71-
return self
71+
return true
7272
end
7373

74-
function Watcher:destroy()
75-
log.line("watcher", "Watcher:destroy '%s'", self._opts.absolute_path)
76-
if self._e then
77-
local rc, _, name = self._e:stop()
74+
function Event:add(listener)
75+
table.insert(self._listeners, listener)
76+
end
77+
78+
function Event:remove(listener)
79+
utils.array_remove(self._listeners, listener)
80+
if #self._listeners == 0 then
81+
self:destroy()
82+
end
83+
end
84+
85+
function Event:destroy()
86+
log.line("watcher", "Event:destroy '%s'", self._path)
87+
88+
if self._fs_event then
89+
local rc, _, name = self._fs_event:stop()
7890
if rc ~= 0 then
79-
utils.notify.warn(
80-
string.format("Could not stop the fs_event watcher for path %s : %s", self._opts.absolute_path, name)
81-
)
91+
utils.notify.warn(string.format("Could not stop the fs_event watcher for path %s : %s", self._path, name))
8292
end
83-
self._e = nil
93+
self._fs_event = nil
8494
end
85-
for i, w in ipairs(M._watchers) do
86-
if w == self then
87-
table.remove(M._watchers, i)
88-
break
89-
end
95+
96+
Event._events[self._path] = nil
97+
end
98+
99+
function Watcher:new(path, callback, data)
100+
log.line("watcher", "Watcher:new '%s'", path)
101+
102+
local w = setmetatable(data, Watcher)
103+
104+
w._event = Event._events[path] or Event:new(path)
105+
w._listener = nil
106+
w._path = path
107+
w._callback = callback
108+
109+
if not w._event then
110+
return nil
111+
end
112+
113+
w:start()
114+
115+
table.insert(Watcher._watchers, w)
116+
117+
return w
118+
end
119+
120+
function Watcher:start()
121+
self._listener = function()
122+
self._callback(self)
90123
end
124+
125+
self._event:add(self._listener)
126+
end
127+
128+
function Watcher:destroy()
129+
log.line("watcher", "Watcher:destroy '%s'", self._path)
130+
131+
self._event:remove(self._listener)
132+
133+
utils.array_remove(Watcher._watchers, self)
91134
end
92135

93136
M.Watcher = Watcher
94137

95138
function M.purge_watchers()
96-
for _, watcher in pairs(M._watchers) do
97-
watcher:destroy()
139+
log.line("watcher", "purge_watchers")
140+
141+
for _, w in ipairs(utils.array_shallow_clone(Watcher._watchers)) do
142+
w:destroy()
143+
end
144+
145+
for _, e in pairs(Event._events) do
146+
e:destroy()
98147
end
99-
M._watchers = {}
100148
end
101149

102150
return M

0 commit comments

Comments
 (0)