feat(fswatch): report filewatchers in :checkhealth

This commit is contained in:
Justin M. Keyes
2026-05-19 00:03:36 +02:00
parent 2e82e0bcf6
commit 735a7d0c9e
4 changed files with 67 additions and 5 deletions

View File

@@ -257,6 +257,7 @@ TUI
UI
• |:checkhealth| shows filewatcher info in the Performance section.
• These builtin "picker" menus delegate to |vim.ui.select()|:
• :browse oldfiles
• |:recover|

View File

@@ -10,6 +10,28 @@ M.FileChangeType = {
Deleted = 3,
}
--- Count of currently-active watchers. Each "watcher" here is one call to vim._watch.watch(),
--- vim._watch.watchdirs(), or vim._watch.inotify() that hasn't been cancelled yet.
M.active = { watch = 0, watchdirs = 0, inotify = 0 }
--- Wraps a cancel function so the global counter is decremented exactly once, even if the caller
--- invokes the cancel function multiple times.
--- @param backend 'watch'|'watchdirs'|'inotify'
--- @param cancel fun()
--- @return fun()
local function tracked_cancel(backend, cancel)
M.active[backend] = M.active[backend] + 1
local done = false
return function()
if done then
return
end
done = true
M.active[backend] = M.active[backend] - 1
cancel()
end
end
--- @class vim._watch.Opts
---
--- @field debounce? integer ms
@@ -109,7 +131,7 @@ function M.watch(path, opts, callback)
return function() end
end
return function()
return tracked_cancel('watch', function()
local _, stop_err = handle:stop()
assert(not stop_err, stop_err)
local is_closing, close_err = handle:is_closing()
@@ -117,7 +139,7 @@ function M.watch(path, opts, callback)
if not is_closing then
handle:close()
end
end
end)
end
--- Initializes and starts a |uv_fs_event_t| recursively watching every directory underneath the
@@ -241,7 +263,7 @@ function M.watchdirs(path, opts, callback)
timer:close()
end
return cancel
return tracked_cancel('watchdirs', cancel)
end
--- @param data string
@@ -328,9 +350,9 @@ function M.inotify(path, opts, callback)
env = { LC_NUMERIC = 'C' },
})
return function()
return tracked_cancel('inotify', function()
obj:kill(2)
end
end)
end
return M

View File

@@ -169,6 +169,42 @@ local function check_config()
end
end
-- Note: this is part of check_performance().
local function check_watchers()
health.start('Filewatchers')
local a = assert(vim._watch.active)
local total = a.watch + a.watchdirs + a.inotify
health.info(
('filewatchers (vim._watch): %d (watch=%d, watchdirs=%d, inotify=%d)'):format(
total,
a.watch,
a.watchdirs,
a.inotify
)
)
-- Walk libuv for an independent view. These counts include handles created outside `vim._watch`
-- (e.g. plugins calling `vim.uv.new_fs_event()` directly).
local libuv = { fs_event = 0, fs_poll = 0, process = 0, timer = 0 }
vim.uv.walk(function(handle)
if handle:is_closing() then
return
end
local t = handle:get_type()
if libuv[t] ~= nil then
libuv[t] = libuv[t] + 1
end
end)
health.info(
('libuv handles: fs_event=%d, fs_poll=%d, process=%d, timer=%d'):format(
libuv.fs_event,
libuv.fs_poll,
libuv.process,
libuv.timer
)
)
end
local function check_performance()
health.start('Performance')
@@ -199,6 +235,8 @@ local function check_performance()
'Slow shell invocation (took ' .. vim.fn.printf('%.2f', elapsed_time) .. ' seconds).'
)
end
check_watchers()
end
-- Load the remote plugin manifest file and check for unregistered plugins

View File

@@ -112,6 +112,7 @@ local function check_active_clients()
end
end
-- See also runtime/lua/vim/health/health.lua:check_watchers()
local function check_watcher()
vim.health.start('vim.lsp: File Watcher')