aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2024-10-02 08:05:03 -0700
committerGitHub <noreply@github.com>2024-10-02 08:05:03 -0700
commitd3b4772ddcd4c890dc5bc38e1f1b36a08aed0c04 (patch)
treea2ba739b5d7588ca36516421188fac5c7850d246
parent391c5084d1181a6742ebd8551efda4db7e8ca46e (diff)
parentbbf208784ca279178ba0075b60d3e9c80f11da7a (diff)
downloadrneovim-d3b4772ddcd4c890dc5bc38e1f1b36a08aed0c04.tar.gz
rneovim-d3b4772ddcd4c890dc5bc38e1f1b36a08aed0c04.tar.bz2
rneovim-d3b4772ddcd4c890dc5bc38e1f1b36a08aed0c04.zip
Merge #30595 from justinmk/fixwatch
-rw-r--r--runtime/lua/vim/_watch.lua27
-rw-r--r--test/functional/lua/watch_spec.lua95
2 files changed, 91 insertions, 31 deletions
diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua
index 3c090af3ff..11f6742941 100644
--- a/runtime/lua/vim/_watch.lua
+++ b/runtime/lua/vim/_watch.lua
@@ -30,6 +30,8 @@ M.FileChangeType = {
--- @class vim._watch.watch.Opts : vim._watch.Opts
--- @field uvflags? uv.fs_event_start.flags
+--- Decides if `path` should be skipped.
+---
--- @param path string
--- @param opts? vim._watch.Opts
local function skip(path, opts)
@@ -69,7 +71,7 @@ function M.watch(path, opts, callback)
local uvflags = opts and opts.uvflags or {}
local handle = assert(uv.new_fs_event())
- local _, start_err = handle:start(path, uvflags, function(err, filename, events)
+ local _, start_err, start_errname = handle:start(path, uvflags, function(err, filename, events)
assert(not err, err)
local fullpath = path
if filename then
@@ -96,7 +98,15 @@ function M.watch(path, opts, callback)
callback(fullpath, change_type)
end)
- assert(not start_err, start_err)
+ if start_err then
+ if start_errname == 'ENOENT' then
+ -- Server may send "workspace/didChangeWatchedFiles" with nonexistent `baseUri` path.
+ -- This is mostly a placeholder until we have `nvim_log` API.
+ vim.notify_once(('watch.watch: %s'):format(start_err), vim.log.levels.INFO)
+ end
+ -- TODO(justinmk): log important errors once we have `nvim_log` API.
+ return function() end
+ end
return function()
local _, stop_err = handle:stop()
@@ -193,7 +203,18 @@ function M.watchdirs(path, opts, callback)
local root_handle = assert(uv.new_fs_event())
handles[path] = root_handle
- root_handle:start(path, {}, create_on_change(path))
+ local _, start_err, start_errname = root_handle:start(path, {}, create_on_change(path))
+
+ if start_err then
+ if start_errname == 'ENOENT' then
+ -- Server may send "workspace/didChangeWatchedFiles" with nonexistent `baseUri` path.
+ -- This is mostly a placeholder until we have `nvim_log` API.
+ vim.notify_once(('watch.watchdirs: %s'):format(start_err), vim.log.levels.INFO)
+ end
+ -- TODO(justinmk): log important errors once we have `nvim_log` API.
+
+ -- Continue. vim.fs.dir() will return nothing, so the code below is harmless.
+ end
--- "640K ought to be enough for anyone"
--- Who has folders this deep?
diff --git a/test/functional/lua/watch_spec.lua b/test/functional/lua/watch_spec.lua
index ab6b1416aa..0c575900d3 100644
--- a/test/functional/lua/watch_spec.lua
+++ b/test/functional/lua/watch_spec.lua
@@ -21,19 +21,78 @@ describe('vim._watch', function()
end)
local function run(watchfunc)
- it('detects file changes (watchfunc=' .. watchfunc .. '())', function()
+ -- Monkey-patches vim.notify_once so we can "spy" on it.
+ local function spy_notify_once()
+ exec_lua [[
+ _G.__notify_once_msgs = {}
+ vim.notify_once = (function(overridden)
+ return function(msg, level, opts)
+ table.insert(_G.__notify_once_msgs, msg)
+ return overridden(msg, level, opts)
+ end
+ end)(vim.notify_once)
+ ]]
+ end
+
+ local function last_notify_once_msg()
+ return exec_lua 'return _G.__notify_once_msgs[#_G.__notify_once_msgs]'
+ end
+
+ local function do_watch(root_dir, watchfunc_)
+ exec_lua(
+ [[
+ local root_dir, watchfunc = ...
+
+ _G.events = {}
+
+ _G.stop_watch = vim._watch[watchfunc](root_dir, {
+ debounce = 100,
+ include_pattern = vim.lpeg.P(root_dir) * vim.lpeg.P("/file") ^ -1,
+ exclude_pattern = vim.lpeg.P(root_dir .. '/file.unwatched'),
+ }, function(path, change_type)
+ table.insert(_G.events, { path = path, change_type = change_type })
+ end)
+ ]],
+ root_dir,
+ watchfunc_
+ )
+ end
+
+ it(watchfunc .. '() ignores nonexistent paths', function()
+ if watchfunc == 'inotify' then
+ skip(n.fn.executable('inotifywait') == 0, 'inotifywait not found')
+ skip(is_os('bsd'), 'inotifywait on bsd CI seems to expect path to exist?')
+ end
+
+ local msg = ('watch.%s: ENOENT: no such file or directory'):format(watchfunc)
+
+ spy_notify_once()
+ do_watch('/i am /very/funny.go', watchfunc)
+
+ if watchfunc ~= 'inotify' then -- watch.inotify() doesn't (currently) call vim.notify_once.
+ t.retry(nil, 2000, function()
+ t.eq(msg, last_notify_once_msg())
+ end)
+ end
+ eq(0, exec_lua [[return #_G.events]])
+
+ exec_lua [[_G.stop_watch()]]
+ end)
+
+ it(watchfunc .. '() detects file changes', function()
if watchfunc == 'inotify' then
skip(is_os('win'), 'not supported on windows')
skip(is_os('mac'), 'flaky test on mac')
- skip(
- not is_ci() and n.fn.executable('inotifywait') == 0,
- 'inotify-tools not installed and not on CI'
- )
+ skip(not is_ci() and n.fn.executable('inotifywait') == 0, 'inotifywait not found')
end
+ -- Note: because this is not `elseif`, BSD is skipped for *all* cases...?
if watchfunc == 'watch' then
skip(is_os('mac'), 'flaky test on mac')
skip(is_os('bsd'), 'Stopped working on bsd after 3ca967387c49c754561c3b11a574797504d40f38')
+ elseif watchfunc == 'watchdirs' and is_os('mac') then
+ -- Bump this (or fix the bug) if CI continues to fail in future versions of macos CI.
+ skip(is_ci() and vim.uv.os_uname().release == '23.6.0', 'weird failure for macOS arm 14 CI')
else
skip(
is_os('bsd'),
@@ -41,10 +100,8 @@ describe('vim._watch', function()
)
end
- local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX')
-
local expected_events = 0
-
+ --- Waits for a new event, or fails if no events are triggered.
local function wait_for_event()
expected_events = expected_events + 1
exec_lua(
@@ -65,26 +122,11 @@ describe('vim._watch', function()
)
end
+ local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX')
local unwatched_path = root_dir .. '/file.unwatched'
local watched_path = root_dir .. '/file'
- exec_lua(
- [[
- local root_dir, watchfunc = ...
-
- _G.events = {}
-
- _G.stop_watch = vim._watch[watchfunc](root_dir, {
- debounce = 100,
- include_pattern = vim.lpeg.P(root_dir) * vim.lpeg.P("/file") ^ -1,
- exclude_pattern = vim.lpeg.P(root_dir .. '/file.unwatched'),
- }, function(path, change_type)
- table.insert(_G.events, { path = path, change_type = change_type })
- end)
- ]],
- root_dir,
- watchfunc
- )
+ do_watch(root_dir, watchfunc)
if watchfunc ~= 'watch' then
vim.uv.sleep(200)
@@ -92,16 +134,13 @@ describe('vim._watch', function()
touch(watched_path)
touch(unwatched_path)
-
wait_for_event()
os.remove(watched_path)
os.remove(unwatched_path)
-
wait_for_event()
exec_lua [[_G.stop_watch()]]
-
-- No events should come through anymore
vim.uv.sleep(100)