From 55e4301036bb938474fc9768c41e28df867d9286 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Sat, 6 Jul 2024 11:44:19 +0200 Subject: feat(lsp): drop fswatch, use inotifywait (#29374) This patch replaces fswatch with inotifywait from inotify-toools: https://github.com/inotify-tools/inotify-tools fswatch takes ~1min to set up recursively for the Samba source code directory. inotifywait needs less than a second to do the same thing. https://github.com/emcrisostomo/fswatch/issues/321 Also it fswatch seems to be unmaintained in the meantime. Signed-off-by: Andreas Schneider --- runtime/lua/vim/_watch.lua | 61 +++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 33 deletions(-) (limited to 'runtime/lua/vim/_watch.lua') diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 02b3f536c2..40f18ce5b0 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -227,11 +227,12 @@ end --- @param data string --- @param opts vim._watch.Opts? --- @param callback vim._watch.Callback -local function fswatch_output_handler(data, opts, callback) +local function on_inotifywait_output(data, opts, callback) local d = vim.split(data, '%s+') -- only consider the last reported event - local fullpath, event = d[1], d[#d] + local path, event, file = d[1], d[2], d[#d] + local fullpath = vim.fs.joinpath(path, file) if skip(fullpath, opts) then return @@ -240,20 +241,16 @@ local function fswatch_output_handler(data, opts, callback) --- @type integer local change_type - if event == 'Created' then + if event == 'CREATE' then change_type = M.FileChangeType.Created - elseif event == 'Removed' then + elseif event == 'DELETE' then change_type = M.FileChangeType.Deleted - elseif event == 'Updated' then + elseif event == 'MODIFY' then change_type = M.FileChangeType.Changed - elseif event == 'Renamed' then - local _, staterr, staterrname = uv.fs_stat(fullpath) - if staterrname == 'ENOENT' then - change_type = M.FileChangeType.Deleted - else - assert(not staterr, staterr) - change_type = M.FileChangeType.Created - end + elseif event == 'MOVED_FROM' then + change_type = M.FileChangeType.Deleted + elseif event == 'MOVED_TO' then + change_type = M.FileChangeType.Created end if change_type then @@ -265,24 +262,22 @@ end --- @param opts vim._watch.Opts? --- @param callback vim._watch.Callback Callback for new events --- @return fun() cancel Stops the watcher -function M.fswatch(path, opts, callback) - -- debounce isn't the same as latency but close enough - local latency = 0.5 -- seconds - if opts and opts.debounce then - latency = opts.debounce / 1000 - end - +function M.inotify(path, opts, callback) local obj = vim.system({ - 'fswatch', - '--event=Created', - '--event=Removed', - '--event=Updated', - '--event=Renamed', - '--event-flags', + 'inotifywait', + '--quiet', -- suppress startup messages + '--no-dereference', -- don't follow symlinks + '--monitor', -- keep listening for events forever '--recursive', - '--latency=' .. tostring(latency), - '--exclude', - '/.git/', + '--event', + 'create', + '--event', + 'delete', + '--event', + 'modify', + '--event', + 'move', + '@.git', -- ignore git directory path, }, { stderr = function(err, data) @@ -292,11 +287,11 @@ function M.fswatch(path, opts, callback) if data and #vim.trim(data) > 0 then vim.schedule(function() - if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then - data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.' + if vim.fn.has('linux') == 1 and vim.startswith(data, 'Failed to watch') then + data = 'inotify(7) limit reached, see :h inotify-limitations for more info.' end - vim.notify('fswatch: ' .. data, vim.log.levels.ERROR) + vim.notify('inotify: ' .. data, vim.log.levels.ERROR) end) end end, @@ -306,7 +301,7 @@ function M.fswatch(path, opts, callback) end for line in vim.gsplit(data or '', '\n', { plain = true, trimempty = true }) do - fswatch_output_handler(line, opts, callback) + on_inotifywait_output(line, opts, callback) end end, -- --latency is locale dependent but tostring() isn't and will always have '.' as decimal point. -- cgit From 32e128f20992e350b3e39c7469baa1f692418203 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 1 Aug 2024 16:00:48 +0200 Subject: fix(watch): exclude .git when using inotifywait (#29914) inotifywait man page specifies: The file must be specified with a relative or absolute path according to whether a relative or absolute path is given for watched directories. So it would only work this way in case the path is relative (which at least for gopls it is not) --- runtime/lua/vim/_watch.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/_watch.lua') diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 40f18ce5b0..3c090af3ff 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -277,7 +277,7 @@ function M.inotify(path, opts, callback) 'modify', '--event', 'move', - '@.git', -- ignore git directory + string.format('@%s/.git', path), -- ignore git directory path, }, { stderr = function(err, data) -- cgit From 9e23b4e1852f9ad6418b45f827d1fb160611d8cf Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 30 Sep 2024 15:25:44 +0200 Subject: fix(watch): ignore nonexistent paths (ENOENT) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: The `_watch.watch()` strategy may fail if the given path does not exist: …/vim/_watch.lua:101: ENOENT: no such file or directory stack traceback: [C]: in function 'assert' …/vim/_watch.lua:101: in function <…/vim/_watch.lua:61> [string ""]:5: in main chunk - `_watch.watch()` actively asserts any error returned by `handle:start()`. - whereas `_watch.watchdirs()` just ignores the result of `root_handle:start()`. Servers may send "client/registerCapability" with "workspace/didChangeWatchedFiles" item(s) (`baseUri`) which do not actually exist on the filesystem: https://github.com/neovim/neovim/issues/28058#issuecomment-2189929424 { method = "client/registerCapability", params = { registrations = { { method = "workspace/didChangeWatchedFiles", registerOptions = { watchers = { { globPattern = { baseUri = "file:///Users/does/not/exist", pattern = "**/*.{ts,js,mts,mjs,cjs,cts,json,svelte}" } }, ... } Solution: - Remove the assert in `_watch.watch()`. - Show a once-only message for both cases. - More detailed logging is blocked until we have `nvim_log` / `vim.log`. fix #28058 --- runtime/lua/vim/_watch.lua | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/_watch.lua') 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? -- cgit