Revert "feat(lsp): implement workspace/didChangeWatchedFiles (#21293)"

This reverts commit 5732aa706c.

Causes editor to freeze in projects with many watcher registrations
This commit is contained in:
Mathias Fussenegger
2023-02-25 11:17:28 +01:00
parent 5732aa706c
commit f0f27e9aef
9 changed files with 9 additions and 1279 deletions

View File

@@ -1,195 +0,0 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local exec_lua = helpers.exec_lua
local clear = helpers.clear
local is_os = helpers.is_os
local lfs = require('lfs')
describe('vim._watch', function()
before_each(function()
clear()
end)
describe('watch', function()
it('detects file changes', function()
local root_dir = helpers.tmpname()
os.remove(root_dir)
lfs.mkdir(root_dir)
local result = exec_lua(
[[
local root_dir = ...
local events = {}
local expected_events = 0
local function wait_for_events()
assert(vim.wait(100, function() return #events == expected_events end), 'Timed out waiting for expected number of events. Current events seen so far: ' .. vim.inspect(events))
end
local stop = vim._watch.watch(root_dir, {}, function(path, change_type)
table.insert(events, { path = path, change_type = change_type })
end)
-- Only BSD seems to need some extra time for the watch to be ready to respond to events
if vim.fn.has('bsd') then
vim.wait(50)
end
local watched_path = root_dir .. '/file'
local watched, err = io.open(watched_path, 'w')
assert(not err, err)
expected_events = expected_events + 1
wait_for_events()
watched:close()
os.remove(watched_path)
expected_events = expected_events + 1
wait_for_events()
stop()
-- No events should come through anymore
local watched_path = root_dir .. '/file'
local watched, err = io.open(watched_path, 'w')
assert(not err, err)
vim.wait(50)
watched:close()
os.remove(watched_path)
vim.wait(50)
return events
]],
root_dir
)
local expected = {
{
change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
path = root_dir .. '/file',
},
{
change_type = exec_lua([[return vim._watch.FileChangeType.Deleted]]),
path = root_dir .. '/file',
},
}
-- kqueue only reports events on the watched path itself, so creating a file within a
-- watched directory results in a "rename" libuv event on the directory.
if is_os('bsd') then
expected = {
{
change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
path = root_dir,
},
{
change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
path = root_dir,
},
}
end
eq(expected, result)
end)
end)
describe('poll', function()
it('detects file changes', function()
local root_dir = helpers.tmpname()
os.remove(root_dir)
lfs.mkdir(root_dir)
local result = exec_lua(
[[
local root_dir = ...
local events = {}
local poll_interval_ms = 1000
local poll_wait_ms = poll_interval_ms+200
local expected_events = 0
local function wait_for_events()
assert(vim.wait(poll_wait_ms, function() return #events == expected_events end), 'Timed out waiting for expected number of events. Current events seen so far: ' .. vim.inspect(events))
end
local stop = vim._watch.poll(root_dir, { interval = poll_interval_ms }, function(path, change_type)
table.insert(events, { path = path, change_type = change_type })
end)
-- polling generates Created events for the existing entries when it starts.
expected_events = expected_events + 1
wait_for_events()
local watched_path = root_dir .. '/file'
local watched, err = io.open(watched_path, 'w')
assert(not err, err)
expected_events = expected_events + 2
wait_for_events()
watched:close()
os.remove(watched_path)
expected_events = expected_events + 2
wait_for_events()
stop()
-- No events should come through anymore
local watched_path = root_dir .. '/file'
local watched, err = io.open(watched_path, 'w')
assert(not err, err)
vim.wait(poll_wait_ms)
watched:close()
os.remove(watched_path)
return events
]],
root_dir
)
eq(5, #result)
eq({
change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
path = root_dir,
}, result[1])
eq({
change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
path = root_dir .. '/file',
}, result[2])
eq({
change_type = exec_lua([[return vim._watch.FileChangeType.Changed]]),
path = root_dir,
}, result[3])
-- The file delete and corresponding directory change events do not happen in any
-- particular order, so allow either
if result[4].path == root_dir then
eq({
change_type = exec_lua([[return vim._watch.FileChangeType.Changed]]),
path = root_dir,
}, result[4])
eq({
change_type = exec_lua([[return vim._watch.FileChangeType.Deleted]]),
path = root_dir .. '/file',
}, result[5])
else
eq({
change_type = exec_lua([[return vim._watch.FileChangeType.Deleted]]),
path = root_dir .. '/file',
}, result[4])
eq({
change_type = exec_lua([[return vim._watch.FileChangeType.Changed]]),
path = root_dir,
}, result[5])
end
end)
end)
end)

View File

@@ -1,173 +0,0 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local exec_lua = helpers.exec_lua
local has_err = require('luassert').has.errors
describe('vim.lsp._watchfiles', function()
before_each(helpers.clear)
after_each(helpers.clear)
local match = function(...)
return exec_lua('return require("vim.lsp._watchfiles")._match(...)', ...)
end
describe('glob matching', function()
it('should match literal strings', function()
eq(true, match('', ''))
eq(false, match('', 'a'))
eq(true, match('a', 'a'))
eq(true, match('abc', 'abc'))
eq(false, match('abc', 'abcdef'))
eq(false, match('abc', 'a'))
eq(false, match('a', 'b'))
eq(false, match('.', 'a'))
eq(true, match('$', '$'))
eq(false, match('dir/subdir', 'dir/subdir/file'))
end)
it('should match * wildcards', function()
-- eq(false, match('*', '')) -- TODO: this fails
eq(true, match('*', 'a'))
eq(false, match('*', '/a'))
eq(false, match('*', 'a/'))
eq(true, match('*', 'aaa'))
eq(true, match('*.txt', 'file.txt'))
eq(false, match('*.txt', 'file.txtxt'))
eq(false, match('*.txt', 'dir/file.txt'))
eq(false, match('*.txt', '/dir/file.txt'))
eq(false, match('*.txt', 'C:/dir/file.txt'))
eq(false, match('*.dir', 'test.dir/file'))
eq(true, match('file.*', 'file.txt'))
eq(false, match('file.*', 'not-file.txt'))
eq(false, match('dir/*.txt', 'file.txt'))
eq(true, match('dir/*.txt', 'dir/file.txt'))
eq(false, match('dir/*.txt', 'dir/subdir/file.txt'))
end)
it('should match ? wildcards', function()
eq(false, match('?', ''))
eq(true, match('?', 'a'))
eq(false, match('??', 'a'))
eq(false, match('?', 'ab'))
eq(true, match('??', 'ab'))
eq(true, match('a?c', 'abc'))
eq(false, match('a?c', 'a/c'))
end)
it('should match ** wildcards', function()
eq(true, match('**', ''))
eq(true, match('**', 'a'))
eq(true, match('**', 'a/'))
eq(true, match('**', '/a'))
eq(true, match('**', 'C:/a'))
eq(true, match('**', 'a/a'))
eq(true, match('**', 'a/a/a'))
eq(false, match('a**', ''))
eq(true, match('a**', 'a'))
eq(true, match('a**', 'abcd'))
eq(false, match('a**', 'ba'))
eq(false, match('a**', 'a/b'))
eq(false, match('**a', ''))
eq(true, match('**a', 'a'))
eq(true, match('**a', 'dcba'))
eq(false, match('**a', 'ab'))
eq(false, match('**a', 'b/a'))
eq(false, match('a/**', ''))
eq(true, match('a/**', 'a'))
eq(true, match('a/**', 'a/b'))
eq(false, match('a/**', 'b/a'))
eq(false, match('a/**', '/a'))
eq(false, match('**/a', ''))
eq(true, match('**/a', 'a'))
eq(false, match('**/a', 'a/b'))
eq(true, match('**/a', '/a'))
eq(false, match('a/**/c', 'a'))
eq(false, match('a/**/c', 'c'))
eq(true, match('a/**/c', 'a/c'))
eq(true, match('a/**/c', 'a/b/c'))
eq(true, match('a/**/c', 'a/b/b/c'))
eq(true, match('**/a/**', 'a'))
eq(true, match('**/a/**', '/dir/a'))
eq(true, match('**/a/**', 'a/dir'))
eq(true, match('**/a/**', 'dir/a/dir'))
eq(true, match('**/a/**', '/a/dir'))
eq(true, match('**/a/**', 'C:/a/dir'))
-- eq(false, match('**/a/**', 'a.txt')) -- TODO: this fails
end)
it('should match {} groups', function()
eq(false, match('{}', ''))
eq(true, match('{,}', ''))
eq(false, match('{}', 'a'))
eq(true, match('{a}', 'a'))
eq(false, match('{a}', 'aa'))
eq(false, match('{a}', 'ab'))
eq(false, match('{ab}', 'a'))
eq(true, match('{ab}', 'ab'))
eq(true, match('{a,b}', 'a'))
eq(true, match('{a,b}', 'b'))
eq(false, match('{a,b}', 'ab'))
eq(true, match('{ab,cd}', 'ab'))
eq(false, match('{ab,cd}', 'a'))
eq(true, match('{ab,cd}', 'cd'))
eq(true, match('{a,b,c}', 'c'))
eq(false, match('{a,{b,c}}', 'c')) -- {} can't nest
end)
it('should match [] groups', function()
eq(true, match('[]', ''))
eq(false, match('[a-z]', ''))
eq(true, match('[a-z]', 'a'))
eq(false, match('[a-z]', 'ab'))
eq(true, match('[a-z]', 'z'))
eq(true, match('[a-z]', 'j'))
eq(false, match('[a-f]', 'j'))
eq(false, match('[a-z]', '`')) -- 'a' - 1
eq(false, match('[a-z]', '{')) -- 'z' + 1
eq(false, match('[a-z]', 'A'))
eq(false, match('[a-z]', '5'))
eq(true, match('[A-Z]', 'A'))
eq(true, match('[A-Z]', 'Z'))
eq(true, match('[A-Z]', 'J'))
eq(false, match('[A-Z]', '@')) -- 'A' - 1
eq(false, match('[A-Z]', '[')) -- 'Z' + 1
eq(false, match('[A-Z]', 'a'))
eq(false, match('[A-Z]', '5'))
eq(true, match('[a-zA-Z0-9]', 'z'))
eq(true, match('[a-zA-Z0-9]', 'Z'))
eq(true, match('[a-zA-Z0-9]', '9'))
eq(false, match('[a-zA-Z0-9]', '&'))
end)
it('should match [!...] groups', function()
has_err(function() match('[!]', '') end) -- not a valid pattern
eq(false, match('[!a-z]', ''))
eq(false, match('[!a-z]', 'a'))
eq(false, match('[!a-z]', 'z'))
eq(false, match('[!a-z]', 'j'))
eq(true, match('[!a-f]', 'j'))
eq(false, match('[!a-f]', 'jj'))
eq(true, match('[!a-z]', '`')) -- 'a' - 1
eq(true, match('[!a-z]', '{')) -- 'z' + 1
eq(false, match('[!a-zA-Z0-9]', 'a'))
eq(false, match('[!a-zA-Z0-9]', 'A'))
eq(false, match('[!a-zA-Z0-9]', '0'))
eq(true, match('[!a-zA-Z0-9]', '!'))
end)
it('should match complex patterns', function()
eq(false, match('**/*.{c,h}', ''))
eq(false, match('**/*.{c,h}', 'c'))
eq(true, match('**/*.{c,h}', 'file.c'))
eq(true, match('**/*.{c,h}', 'file.h'))
eq(true, match('**/*.{c,h}', '/file.c'))
eq(true, match('**/*.{c,h}', 'dir/subdir/file.c'))
eq(true, match('**/*.{c,h}', 'dir/subdir/file.h'))
eq(true, match('{[0-9],[a-z]}', '0'))
eq(true, match('{[0-9],[a-z]}', 'a'))
eq(false, match('{[0-9],[a-z]}', 'A'))
end)
end)
end)

View File

@@ -1,6 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
local lsp_helpers = require('test.functional.plugin.lsp.helpers')
local lfs = require('lfs')
local assert_log = helpers.assert_log
local buf_lines = helpers.buf_lines
@@ -3590,424 +3589,4 @@ describe('LSP', function()
eq(expected, result)
end)
end)
describe('vim.lsp._watchfiles', function()
it('sends notifications when files change', function()
local root_dir = helpers.tmpname()
os.remove(root_dir)
lfs.mkdir(root_dir)
exec_lua(create_server_definition)
local result = exec_lua([[
local root_dir = ...
local server = _create_server()
local client_id = vim.lsp.start({
name = 'watchfiles-test',
cmd = server.cmd,
root_dir = root_dir,
})
local expected_messages = 2 -- initialize, initialized
local msg_wait_timeout = require('vim.lsp._watchfiles')._watchfunc == vim._watch.poll and 2500 or 200
local function wait_for_messages()
assert(vim.wait(msg_wait_timeout, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
end
wait_for_messages()
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-0',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = {
{
globPattern = '**/watch',
kind = 7,
},
},
},
},
},
}, { client_id = client_id })
local path = root_dir .. '/watch'
local file = io.open(path, 'w')
file:close()
expected_messages = expected_messages + 1
wait_for_messages()
os.remove(path)
expected_messages = expected_messages + 1
wait_for_messages()
return server.messages
]], root_dir)
local function watched_uri(fname)
return exec_lua([[
local root_dir, fname = ...
return vim.uri_from_fname(root_dir .. '/' .. fname)
]], root_dir, fname)
end
eq(4, #result)
eq('workspace/didChangeWatchedFiles', result[3].method)
eq({
changes = {
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
uri = watched_uri('watch'),
},
},
}, result[3].params)
eq('workspace/didChangeWatchedFiles', result[4].method)
eq({
changes = {
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
uri = watched_uri('watch'),
},
},
}, result[4].params)
end)
it('correctly registers and unregisters', function()
local root_dir = 'some_dir'
exec_lua(create_server_definition)
local result = exec_lua([[
local root_dir = ...
local server = _create_server()
local client_id = vim.lsp.start({
name = 'watchfiles-test',
cmd = server.cmd,
root_dir = root_dir,
})
local expected_messages = 2 -- initialize, initialized
local function wait_for_messages()
assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
end
wait_for_messages()
local send_event
require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
local stoppped = false
send_event = function(...)
if not stoppped then
callback(...)
end
end
return function()
stoppped = true
end
end
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-0',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = {
{
globPattern = '**/*.watch0',
},
},
},
},
},
}, { client_id = client_id })
send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created)
send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created)
expected_messages = expected_messages + 1
wait_for_messages()
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-1',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = {
{
globPattern = '**/*.watch1',
},
},
},
},
},
}, { client_id = client_id })
vim.lsp.handlers['client/unregisterCapability'](nil, {
unregisterations = {
{
id = 'watchfiles-test-0',
method = 'workspace/didChangeWatchedFiles',
},
},
}, { client_id = client_id })
send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created)
send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created)
expected_messages = expected_messages + 1
wait_for_messages()
return server.messages
]], root_dir)
local function watched_uri(fname)
return exec_lua([[
local root_dir, fname = ...
return vim.uri_from_fname(root_dir .. '/' .. fname)
]], root_dir, fname)
end
eq(4, #result)
eq('workspace/didChangeWatchedFiles', result[3].method)
eq({
changes = {
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
uri = watched_uri('file.watch0'),
},
},
}, result[3].params)
eq('workspace/didChangeWatchedFiles', result[4].method)
eq({
changes = {
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
uri = watched_uri('file.watch1'),
},
},
}, result[4].params)
end)
it('correctly handles the registered watch kind', function()
local root_dir = 'some_dir'
exec_lua(create_server_definition)
local result = exec_lua([[
local root_dir = ...
local server = _create_server()
local client_id = vim.lsp.start({
name = 'watchfiles-test',
cmd = server.cmd,
root_dir = root_dir,
})
local expected_messages = 2 -- initialize, initialized
local function wait_for_messages()
assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
end
wait_for_messages()
local watch_callbacks = {}
local function send_event(...)
for _, cb in ipairs(watch_callbacks) do
cb(...)
end
end
require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
table.insert(watch_callbacks, callback)
return function()
-- noop because this test never stops the watch
end
end
local protocol = require('vim.lsp.protocol')
local watchers = {}
local max_kind = protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete
for i = 0, max_kind do
local j = i
table.insert(watchers, {
globPattern = {
baseUri = vim.uri_from_fname('/dir'..tostring(i)),
pattern = 'watch'..tostring(i),
},
kind = i,
})
end
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-kind',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = watchers,
},
},
},
}, { client_id = client_id })
for i = 0, max_kind do
local filename = 'watch'..tostring(i)
send_event(filename, vim._watch.FileChangeType.Created)
send_event(filename, vim._watch.FileChangeType.Changed)
send_event(filename, vim._watch.FileChangeType.Deleted)
end
expected_messages = expected_messages + 1
wait_for_messages()
return server.messages
]], root_dir)
local function watched_uri(fname)
return exec_lua([[
return vim.uri_from_fname(...)
]], fname)
end
eq(3, #result)
eq('workspace/didChangeWatchedFiles', result[3].method)
eq({
changes = {
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
uri = watched_uri('watch1'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]),
uri = watched_uri('watch2'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
uri = watched_uri('watch3'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]),
uri = watched_uri('watch3'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
uri = watched_uri('watch4'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
uri = watched_uri('watch5'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
uri = watched_uri('watch5'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]),
uri = watched_uri('watch6'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
uri = watched_uri('watch6'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
uri = watched_uri('watch7'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]),
uri = watched_uri('watch7'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
uri = watched_uri('watch7'),
},
},
}, result[3].params)
end)
it('prunes duplicate events', function()
local root_dir = 'some_dir'
exec_lua(create_server_definition)
local result = exec_lua([[
local root_dir = ...
local server = _create_server()
local client_id = vim.lsp.start({
name = 'watchfiles-test',
cmd = server.cmd,
root_dir = root_dir,
})
local expected_messages = 2 -- initialize, initialized
local function wait_for_messages()
assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
end
wait_for_messages()
local send_event
require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
send_event = callback
return function()
-- noop because this test never stops the watch
end
end
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-kind',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = {
{
globPattern = '**/*',
},
},
},
},
},
}, { client_id = client_id })
send_event('file1', vim._watch.FileChangeType.Created)
send_event('file1', vim._watch.FileChangeType.Created) -- pruned
send_event('file1', vim._watch.FileChangeType.Changed)
send_event('file2', vim._watch.FileChangeType.Created)
send_event('file1', vim._watch.FileChangeType.Changed) -- pruned
expected_messages = expected_messages + 1
wait_for_messages()
return server.messages
]], root_dir)
local function watched_uri(fname)
return exec_lua([[
return vim.uri_from_fname(...)
]], fname)
end
eq(3, #result)
eq('workspace/didChangeWatchedFiles', result[3].method)
eq({
changes = {
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
uri = watched_uri('file1'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]),
uri = watched_uri('file1'),
},
{
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
uri = watched_uri('file2'),
},
},
}, result[3].params)
end)
end)
end)