fix(lsp): skip invalid file watcher globs #40376 #40396

Some servers register `workspace/didChangeWatchedFiles` watchers for URI
schemes that cannot be watched locally. Skipping the unsupported glob
and keep the rest of the registration batch active.

(cherry picked from commit 4d9e5acfb5)
This commit is contained in:
Barrett Ruth
2026-06-25 02:44:33 -05:00
committed by github-actions[bot]
parent 1b9159a695
commit fd27246f67
2 changed files with 81 additions and 16 deletions

View File

@@ -1,11 +1,53 @@
local bit = require('bit')
local glob = vim.glob
local watch = vim._watch
local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
local lpeg = vim.lpeg
local M = {}
-- Use a bounded set to suppress repeat warnings without unbounded growth.
---@type table<string, true>
local logged_invalid_patterns = {}
---@type string[]
local logged_invalid_pattern_keys = {}
local logged_invalid_pattern_key_index = 1
---@param key string
local function remember_invalid_pattern(key)
local old_key = logged_invalid_pattern_keys[logged_invalid_pattern_key_index]
if old_key then
logged_invalid_patterns[old_key] = nil
end
logged_invalid_patterns[key] = true
logged_invalid_pattern_keys[logged_invalid_pattern_key_index] = key
logged_invalid_pattern_key_index = logged_invalid_pattern_key_index % 20 + 1
end
--- Parses a glob pattern and logs invalid patterns once.
---@param pattern string
---@param client vim.lsp.Client
---@return vim.lpeg.Pattern?
local function glob_to_lpeg(pattern, client)
local ok, pattern_or_err = pcall(glob.to_lpeg, pattern)
if ok then
return pattern_or_err
end
local key = string.format('%d:%s', client.id, pattern)
if not logged_invalid_patterns[key] then
log.error(
string.format(
'LSP[%s] skipping invalid workspace/didChangeWatchedFiles globPattern',
client.name
),
pattern,
pattern_or_err
)
remember_invalid_pattern(key)
end
end
if vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1 then
M._watchfunc = watch.watch
elseif vim.fn.executable('inotifywait') == 1 then
@@ -62,24 +104,22 @@ function M.register(reg, client_id)
local glob_pattern = w.globPattern
if type(glob_pattern) == 'string' then
local pattern = glob.to_lpeg(glob_pattern)
if not pattern then
error('Cannot parse pattern: ' .. glob_pattern)
end
for _, folder in ipairs(client.workspace_folders) do
local base_dir = vim.uri_to_fname(folder.uri)
table.insert(watch_regs[base_dir], { pattern = pattern, kind = kind })
local pattern = glob_to_lpeg(glob_pattern, client)
if pattern then
for _, folder in ipairs(client.workspace_folders) do
local base_dir = vim.uri_to_fname(folder.uri)
table.insert(watch_regs[base_dir], { pattern = pattern, kind = kind })
end
end
else
local base_uri = glob_pattern.baseUri
local uri = type(base_uri) == 'string' and base_uri or base_uri.uri
local base_dir = vim.uri_to_fname(uri)
local pattern = glob.to_lpeg(glob_pattern.pattern)
if not pattern then
error('Cannot parse pattern: ' .. glob_pattern.pattern)
local pattern = glob_to_lpeg(glob_pattern.pattern, client)
if pattern then
local relative_pattern = lpeg.P(base_dir .. '/') * pattern --[[@as vim.lpeg.Pattern]]
table.insert(watch_regs[base_dir], { pattern = relative_pattern, kind = kind })
end
pattern = lpeg.P(base_dir .. '/') * pattern
table.insert(watch_regs[base_dir], { pattern = pattern, kind = kind })
end
end

View File

@@ -3683,6 +3683,8 @@ describe('LSP', function()
exec_lua(create_server_definition)
local result = exec_lua(function()
local logfile = vim.lsp.log.get_filename()
vim.fn.writefile({ '' }, logfile)
local server = _G._create_server()
local client_id = assert(vim.lsp.start({
name = 'watchfiles-test',
@@ -3723,6 +3725,17 @@ describe('LSP', function()
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = {
{
globPattern = 'a/**b',
kind = 7,
},
{
globPattern = {
baseUri = vim.uri_from_fname(root_dir),
pattern = '{foo}',
},
kind = 7,
},
{
globPattern = '**/watch',
kind = 7,
@@ -3750,12 +3763,13 @@ describe('LSP', function()
vim.lsp.get_client_by_id(client_id):stop()
return server.messages
return { logfile = logfile, messages = server.messages }
end)
local uri = vim.uri_from_fname(root_dir .. '/watch')
local messages = result.messages
eq(6, #result)
eq(6, #messages)
eq({
method = 'workspace/didChangeWatchedFiles',
@@ -3767,7 +3781,7 @@ describe('LSP', function()
},
},
},
}, result[3])
}, messages[3])
eq({
method = 'workspace/didChangeWatchedFiles',
@@ -3779,7 +3793,18 @@ describe('LSP', function()
},
},
},
}, result[4])
}, messages[4])
t.assert_log(
'%[ERROR%].-skipping invalid workspace/didChangeWatchedFiles globPattern.-'
.. pesc('a/**b'),
result.logfile
)
t.assert_log(
'%[ERROR%].-skipping invalid workspace/didChangeWatchedFiles globPattern.-'
.. pesc('{foo}'),
result.logfile
)
end
)
end