mirror of
https://github.com/neovim/neovim.git
synced 2024-12-23 20:55:18 -07:00
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 <asn@cryptomilk.org>
This commit is contained in:
parent
91e5dcae3d
commit
55e4301036
2
.github/scripts/install_deps.sh
vendored
2
.github/scripts/install_deps.sh
vendored
@ -30,7 +30,7 @@ if [[ $os == Linux ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n $TEST ]]; then
|
if [[ -n $TEST ]]; then
|
||||||
sudo apt-get install -y locales-all cpanminus attr libattr1-dev gdb fswatch
|
sudo apt-get install -y locales-all cpanminus attr libattr1-dev gdb inotify-tools
|
||||||
|
|
||||||
# Use default CC to avoid compilation problems when installing Python modules
|
# Use default CC to avoid compilation problems when installing Python modules
|
||||||
CC=cc python3 -m pip -q install --user --upgrade pynvim
|
CC=cc python3 -m pip -q install --user --upgrade pynvim
|
||||||
|
@ -543,16 +543,19 @@ Example: File-change detection *watch-file*
|
|||||||
vim.api.nvim_command(
|
vim.api.nvim_command(
|
||||||
"command! -nargs=1 Watch call luaeval('watch_file(_A)', expand('<args>'))")
|
"command! -nargs=1 Watch call luaeval('watch_file(_A)', expand('<args>'))")
|
||||||
<
|
<
|
||||||
*fswatch-limitations*
|
*inotify-limitations*
|
||||||
When on Linux and using fswatch, you may need to increase the maximum number
|
When on Linux you may need to increase the maximum number of `inotify` watches
|
||||||
of `inotify` watches and queued events as the default limit can be too low. To
|
and queued events as the default limit can be too low. To increase the limit,
|
||||||
increase the limit, run: >sh
|
run: >sh
|
||||||
sysctl fs.inotify.max_user_watches=100000
|
sysctl fs.inotify.max_user_watches=494462
|
||||||
sysctl fs.inotify.max_queued_events=100000
|
|
||||||
<
|
<
|
||||||
This will increase the limit to 100000 watches and queued events. These lines
|
This will increase the limit to 494462 watches and queued events. These lines
|
||||||
can be added to `/etc/sysctl.conf` to make the changes persistent.
|
can be added to `/etc/sysctl.conf` to make the changes persistent.
|
||||||
|
|
||||||
|
Note that each watch is a structure in the Kernel, thus available memory is
|
||||||
|
also a bottleneck for using inotify. In fact, a watch can take up to 1KB of
|
||||||
|
space. This means a million watches could result in 1GB of extra RAM usage.
|
||||||
|
|
||||||
Example: TCP echo-server *tcp-server*
|
Example: TCP echo-server *tcp-server*
|
||||||
1. Save this code to a file.
|
1. Save this code to a file.
|
||||||
2. Execute it with ":luafile %".
|
2. Execute it with ":luafile %".
|
||||||
|
@ -227,11 +227,12 @@ end
|
|||||||
--- @param data string
|
--- @param data string
|
||||||
--- @param opts vim._watch.Opts?
|
--- @param opts vim._watch.Opts?
|
||||||
--- @param callback vim._watch.Callback
|
--- @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+')
|
local d = vim.split(data, '%s+')
|
||||||
|
|
||||||
-- only consider the last reported event
|
-- 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
|
if skip(fullpath, opts) then
|
||||||
return
|
return
|
||||||
@ -240,20 +241,16 @@ local function fswatch_output_handler(data, opts, callback)
|
|||||||
--- @type integer
|
--- @type integer
|
||||||
local change_type
|
local change_type
|
||||||
|
|
||||||
if event == 'Created' then
|
if event == 'CREATE' then
|
||||||
change_type = M.FileChangeType.Created
|
change_type = M.FileChangeType.Created
|
||||||
elseif event == 'Removed' then
|
elseif event == 'DELETE' then
|
||||||
change_type = M.FileChangeType.Deleted
|
change_type = M.FileChangeType.Deleted
|
||||||
elseif event == 'Updated' then
|
elseif event == 'MODIFY' then
|
||||||
change_type = M.FileChangeType.Changed
|
change_type = M.FileChangeType.Changed
|
||||||
elseif event == 'Renamed' then
|
elseif event == 'MOVED_FROM' then
|
||||||
local _, staterr, staterrname = uv.fs_stat(fullpath)
|
change_type = M.FileChangeType.Deleted
|
||||||
if staterrname == 'ENOENT' then
|
elseif event == 'MOVED_TO' then
|
||||||
change_type = M.FileChangeType.Deleted
|
change_type = M.FileChangeType.Created
|
||||||
else
|
|
||||||
assert(not staterr, staterr)
|
|
||||||
change_type = M.FileChangeType.Created
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if change_type then
|
if change_type then
|
||||||
@ -265,24 +262,22 @@ end
|
|||||||
--- @param opts vim._watch.Opts?
|
--- @param opts vim._watch.Opts?
|
||||||
--- @param callback vim._watch.Callback Callback for new events
|
--- @param callback vim._watch.Callback Callback for new events
|
||||||
--- @return fun() cancel Stops the watcher
|
--- @return fun() cancel Stops the watcher
|
||||||
function M.fswatch(path, opts, callback)
|
function M.inotify(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
|
|
||||||
|
|
||||||
local obj = vim.system({
|
local obj = vim.system({
|
||||||
'fswatch',
|
'inotifywait',
|
||||||
'--event=Created',
|
'--quiet', -- suppress startup messages
|
||||||
'--event=Removed',
|
'--no-dereference', -- don't follow symlinks
|
||||||
'--event=Updated',
|
'--monitor', -- keep listening for events forever
|
||||||
'--event=Renamed',
|
|
||||||
'--event-flags',
|
|
||||||
'--recursive',
|
'--recursive',
|
||||||
'--latency=' .. tostring(latency),
|
'--event',
|
||||||
'--exclude',
|
'create',
|
||||||
'/.git/',
|
'--event',
|
||||||
|
'delete',
|
||||||
|
'--event',
|
||||||
|
'modify',
|
||||||
|
'--event',
|
||||||
|
'move',
|
||||||
|
'@.git', -- ignore git directory
|
||||||
path,
|
path,
|
||||||
}, {
|
}, {
|
||||||
stderr = function(err, data)
|
stderr = function(err, data)
|
||||||
@ -292,11 +287,11 @@ function M.fswatch(path, opts, callback)
|
|||||||
|
|
||||||
if data and #vim.trim(data) > 0 then
|
if data and #vim.trim(data) > 0 then
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then
|
if vim.fn.has('linux') == 1 and vim.startswith(data, 'Failed to watch') then
|
||||||
data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.'
|
data = 'inotify(7) limit reached, see :h inotify-limitations for more info.'
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.notify('fswatch: ' .. data, vim.log.levels.ERROR)
|
vim.notify('inotify: ' .. data, vim.log.levels.ERROR)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@ -306,7 +301,7 @@ function M.fswatch(path, opts, callback)
|
|||||||
end
|
end
|
||||||
|
|
||||||
for line in vim.gsplit(data or '', '\n', { plain = true, trimempty = true }) do
|
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
|
||||||
end,
|
end,
|
||||||
-- --latency is locale dependent but tostring() isn't and will always have '.' as decimal point.
|
-- --latency is locale dependent but tostring() isn't and will always have '.' as decimal point.
|
||||||
|
@ -9,8 +9,8 @@ local M = {}
|
|||||||
|
|
||||||
if vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1 then
|
if vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1 then
|
||||||
M._watchfunc = watch.watch
|
M._watchfunc = watch.watch
|
||||||
elseif vim.fn.executable('fswatch') == 1 then
|
elseif vim.fn.executable('inotifywait') == 1 then
|
||||||
M._watchfunc = watch.fswatch
|
M._watchfunc = watch.inotify
|
||||||
else
|
else
|
||||||
M._watchfunc = watch.watchdirs
|
M._watchfunc = watch.watchdirs
|
||||||
end
|
end
|
||||||
|
@ -90,8 +90,8 @@ local function check_watcher()
|
|||||||
watchfunc_name = 'libuv-watch'
|
watchfunc_name = 'libuv-watch'
|
||||||
elseif watchfunc == vim._watch.watchdirs then
|
elseif watchfunc == vim._watch.watchdirs then
|
||||||
watchfunc_name = 'libuv-watchdirs'
|
watchfunc_name = 'libuv-watchdirs'
|
||||||
elseif watchfunc == vim._watch.fswatch then
|
elseif watchfunc == vim._watch.inotifywait then
|
||||||
watchfunc_name = 'fswatch'
|
watchfunc_name = 'inotifywait'
|
||||||
else
|
else
|
||||||
local nm = debug.getinfo(watchfunc, 'S').source
|
local nm = debug.getinfo(watchfunc, 'S').source
|
||||||
watchfunc_name = string.format('Custom (%s)', nm)
|
watchfunc_name = string.format('Custom (%s)', nm)
|
||||||
@ -99,7 +99,7 @@ local function check_watcher()
|
|||||||
|
|
||||||
report_info('File watch backend: ' .. watchfunc_name)
|
report_info('File watch backend: ' .. watchfunc_name)
|
||||||
if watchfunc_name == 'libuv-watchdirs' then
|
if watchfunc_name == 'libuv-watchdirs' then
|
||||||
report_warn('libuv-watchdirs has known performance issues. Consider installing fswatch.')
|
report_warn('libuv-watchdirs has known performance issues. Consider installing inotify-tools.')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -23,10 +23,13 @@ describe('vim._watch', function()
|
|||||||
|
|
||||||
local function run(watchfunc)
|
local function run(watchfunc)
|
||||||
it('detects file changes (watchfunc=' .. watchfunc .. '())', function()
|
it('detects file changes (watchfunc=' .. watchfunc .. '())', function()
|
||||||
if watchfunc == 'fswatch' then
|
if watchfunc == 'inotify' then
|
||||||
skip(is_os('win'), 'not supported on windows')
|
skip(is_os('win'), 'not supported on windows')
|
||||||
skip(is_os('mac'), 'flaky test on mac')
|
skip(is_os('mac'), 'flaky test on mac')
|
||||||
skip(not is_ci() and n.fn.executable('fswatch') == 0, 'fswatch not installed and not on CI')
|
skip(
|
||||||
|
not is_ci() and n.fn.executable('inotifywait') == 0,
|
||||||
|
'inotify-tools not installed and not on CI'
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if watchfunc == 'watch' then
|
if watchfunc == 'watch' then
|
||||||
@ -123,5 +126,5 @@ describe('vim._watch', function()
|
|||||||
|
|
||||||
run('watch')
|
run('watch')
|
||||||
run('watchdirs')
|
run('watchdirs')
|
||||||
run('fswatch')
|
run('inotify')
|
||||||
end)
|
end)
|
||||||
|
@ -5128,12 +5128,12 @@ describe('LSP', function()
|
|||||||
it(
|
it(
|
||||||
string.format('sends notifications when files change (watchfunc=%s)', watchfunc),
|
string.format('sends notifications when files change (watchfunc=%s)', watchfunc),
|
||||||
function()
|
function()
|
||||||
if watchfunc == 'fswatch' then
|
if watchfunc == 'inotify' then
|
||||||
skip(is_os('win'), 'not supported on windows')
|
skip(is_os('win'), 'not supported on windows')
|
||||||
skip(is_os('mac'), 'flaky test on mac')
|
skip(is_os('mac'), 'flaky test on mac')
|
||||||
skip(
|
skip(
|
||||||
not is_ci() and fn.executable('fswatch') == 0,
|
not is_ci() and fn.executable('inotifywait') == 0,
|
||||||
'fswatch not installed and not on CI'
|
'inotify-tools not installed and not on CI'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -5265,7 +5265,7 @@ describe('LSP', function()
|
|||||||
|
|
||||||
test_filechanges('watch')
|
test_filechanges('watch')
|
||||||
test_filechanges('watchdirs')
|
test_filechanges('watchdirs')
|
||||||
test_filechanges('fswatch')
|
test_filechanges('inotify')
|
||||||
|
|
||||||
it('correctly registers and unregisters', function()
|
it('correctly registers and unregisters', function()
|
||||||
local root_dir = '/some_dir'
|
local root_dir = '/some_dir'
|
||||||
|
Loading…
Reference in New Issue
Block a user