diff options
Diffstat (limited to 'runtime/lua/vim/_watch.lua')
| -rw-r--r-- | runtime/lua/vim/_watch.lua | 95 |
1 files changed, 63 insertions, 32 deletions
diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 34490437b8..2cd61b36ab 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -1,4 +1,5 @@ local uv = vim.uv +local log = vim.log.new({ name = 'nvim-watch' }) local M = {} @@ -36,6 +37,9 @@ end --- --- @field debounce? integer ms --- +--- Handles watcher failures. Defaults to logging at ERROR in nvim-watch.log. +--- @field on_error? fun(err: string) +--- --- An |lpeg| pattern. Only changes to files whose full paths match the pattern --- will be reported. Only matches against non-directoriess, all directories will --- be watched for new potentially-matching files. exclude_pattern can be used to @@ -87,6 +91,7 @@ function M.watch(path, opts, callback) vim.validate('callback', callback, 'function') opts = opts or {} + local on_error = opts.on_error or log.error path = vim.fs.normalize(path) local uvflags = opts and opts.uvflags or {} @@ -94,8 +99,10 @@ function M.watch(path, opts, callback) local watching_dir = (uv.fs_stat(path) or {}).type == 'directory' - local _, start_err, start_errname = handle:start(path, uvflags, function(err, filename, events) - assert(not err, err) + local _, start_err = handle:start(path, uvflags, function(err, filename, events) + if err then + return on_error(err) + end local fullpath = path if filename and watching_dir then fullpath = vim.fs.normalize(vim.fs.joinpath(fullpath, filename)) @@ -112,7 +119,9 @@ function M.watch(path, opts, callback) if staterrname == 'ENOENT' then change_type = M.FileChangeType.Deleted else - assert(not staterr, staterr) + if staterr then + return on_error(staterr) + end change_type = M.FileChangeType.Created end elseif events.change then @@ -122,13 +131,8 @@ function M.watch(path, opts, callback) end) 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 handle:close() - -- TODO(justinmk): log important errors once we have `nvim_log` API. + on_error(start_err) return function() end end @@ -156,6 +160,7 @@ function M.watchdirs(path, opts, callback) vim.validate('callback', callback, 'function') opts = opts or {} + local on_error = opts.on_error or log.error local debounce = opts.debounce or 500 ---@type table<string, uv.uv_fs_event_t> handle by fullpath @@ -174,7 +179,9 @@ function M.watchdirs(path, opts, callback) --- @return uv.fs_event_start.callback local function create_on_change(filepath) return function(err, filename, events) - assert(not err, err) + if err then + return on_error(err) + end local fullpath = vim.fs.joinpath(filepath, filename) if skip(fullpath, opts) then return @@ -204,7 +211,15 @@ function M.watchdirs(path, opts, callback) if not handle then handle = assert(uv.new_fs_event()) handles[fullpath] = handle - handle:start(fullpath, {}, create_on_change(fullpath)) + local _, err, errname = handle:start(fullpath, {}, create_on_change(fullpath)) + if err then + handle:close() + handles[fullpath] = nil + -- The directory may have disappeared since fs_stat(). + if errname ~= 'ENOENT' then + on_error(err) + end + end end end else @@ -225,17 +240,13 @@ function M.watchdirs(path, opts, callback) local root_handle = assert(uv.new_fs_event()) handles[path] = root_handle - local _, start_err, start_errname = root_handle:start(path, {}, create_on_change(path)) + local _, start_err = 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. + root_handle:close() + timer:close() + on_error(start_err) + return function() end end --- "640K ought to be enough for anyone" @@ -256,7 +267,15 @@ function M.watchdirs(path, opts, callback) if not skip(filepath, opts) then local handle = assert(uv.new_fs_event()) handles[filepath] = handle - handle:start(filepath, {}, create_on_change(filepath)) + local _, err, errname = handle:start(filepath, {}, create_on_change(filepath)) + if err then + handle:close() + handles[filepath] = nil + -- The directory may have disappeared since it was listed. + if errname ~= 'ENOENT' then + on_error(err) + end + end end end end @@ -314,7 +333,10 @@ end --- @param callback vim._watch.Callback Callback for new events --- @return fun() cancel Stops the watcher function M.inotify(path, opts, callback) - local obj = vim.system({ + opts = opts or {} + local on_error = opts.on_error or log.error + local cancelled = false + local ok, obj = pcall(vim.system, { 'inotifywait', '--quiet', -- suppress startup messages '--no-dereference', -- don't follow symlinks @@ -333,22 +355,21 @@ function M.inotify(path, opts, callback) }, { stderr = function(err, data) if err then - error(err) + on_error(err) + return end if data and #vim.trim(data) > 0 then - vim.schedule(function() - 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('inotify: ' .. data, vim.log.levels.ERROR) - end) + 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 + on_error(data) end end, stdout = function(err, data) if err then - error(err) + on_error(err) + return end for line in vim.gsplit(data or '', '\n', { plain = true, trimempty = true }) do @@ -357,9 +378,19 @@ function M.inotify(path, opts, callback) end, -- --latency is locale dependent but tostring() isn't and will always have '.' as decimal point. env = { LC_NUMERIC = 'C' }, - }) + }, function(result) + if not cancelled then + on_error(('inotifywait exited with code %d'):format(result.code)) + end + end) + + if not ok then + on_error(obj) + return function() end + end return tracked_cancel('inotify', function() + cancelled = true obj:kill(2) end) end |