diff options
Diffstat (limited to 'runtime/lua/vim/lsp/_watchfiles.lua')
-rw-r--r-- | runtime/lua/vim/lsp/_watchfiles.lua | 118 |
1 files changed, 24 insertions, 94 deletions
diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua index 1fd112631d..49328fbe9b 100644 --- a/runtime/lua/vim/lsp/_watchfiles.lua +++ b/runtime/lua/vim/lsp/_watchfiles.lua @@ -1,95 +1,20 @@ local bit = require('bit') -local watch = require('vim._watch') +local glob = vim.glob +local watch = vim._watch local protocol = require('vim.lsp.protocol') local ms = protocol.Methods local lpeg = vim.lpeg local M = {} ---- Parses the raw pattern into an |lpeg| pattern. LPeg patterns natively support the "this" or "that" ---- alternative constructions described in the LSP spec that cannot be expressed in a standard Lua pattern. ---- ----@param pattern string The raw glob pattern ----@return vim.lpeg.Pattern? pattern An |lpeg| representation of the pattern, or nil if the pattern is invalid. -local function parse(pattern) - local l = lpeg - - local P, S, V = lpeg.P, lpeg.S, lpeg.V - local C, Cc, Ct, Cf = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cf - - local pathsep = '/' - - local function class(inv, ranges) - for i, r in ipairs(ranges) do - ranges[i] = r[1] .. r[2] - end - local patt = l.R(unpack(ranges)) - if inv == '!' then - patt = P(1) - patt - end - return patt - end - - local function add(acc, a) - return acc + a - end - - local function mul(acc, m) - return acc * m - end - - local function star(stars, after) - return (-after * (l.P(1) - pathsep)) ^ #stars * after - end - - local function dstar(after) - return (-after * l.P(1)) ^ 0 * after - end - - local p = P({ - 'Pattern', - Pattern = V('Elem') ^ -1 * V('End'), - Elem = Cf( - (V('DStar') + V('Star') + V('Ques') + V('Class') + V('CondList') + V('Literal')) - * (V('Elem') + V('End')), - mul - ), - DStar = P('**') * (P(pathsep) * (V('Elem') + V('End')) + V('End')) / dstar, - Star = C(P('*') ^ 1) * (V('Elem') + V('End')) / star, - Ques = P('?') * Cc(l.P(1) - pathsep), - Class = P('[') * C(P('!') ^ -1) * Ct(Ct(C(1) * '-' * C(P(1) - ']')) ^ 1 * ']') / class, - CondList = P('{') * Cf(V('Cond') * (P(',') * V('Cond')) ^ 0, add) * '}', - -- TODO: '*' inside a {} condition is interpreted literally but should probably have the same - -- wildcard semantics it usually has. - -- Fixing this is non-trivial because '*' should match non-greedily up to "the rest of the - -- pattern" which in all other cases is the entire succeeding part of the pattern, but at the end of a {} - -- condition means "everything after the {}" where several other options separated by ',' may - -- exist in between that should not be matched by '*'. - Cond = Cf((V('Ques') + V('Class') + V('CondList') + (V('Literal') - S(',}'))) ^ 1, mul) - + Cc(l.P(0)), - Literal = P(1) / l.P, - End = P(-1) * Cc(l.P(-1)), - }) - - return p:match(pattern) --[[@as vim.lpeg.Pattern?]] -end - ----@private ---- Implementation of LSP 3.17.0's pattern matching: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#pattern ---- ----@param pattern string|vim.lpeg.Pattern The glob pattern (raw or parsed) to match. ----@param s string The string to match against pattern. ----@return boolean Whether or not pattern matches s. -function M._match(pattern, s) - if type(pattern) == 'string' then - local p = assert(parse(pattern)) - return p:match(s) ~= nil - end - return pattern:match(s) ~= nil +if vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1 then + M._watchfunc = watch.watch +elseif vim.fn.executable('fswatch') == 1 then + M._watchfunc = watch.fswatch +else + M._watchfunc = watch.watchdirs end -M._watchfunc = (vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1) and watch.watch or watch.poll - ---@type table<integer, table<string, function[]>> client id -> registration id -> cancel function local cancels = vim.defaulttable() @@ -112,9 +37,9 @@ local to_lsp_change_type = { --- Default excludes the same as VSCode's `files.watcherExclude` setting. --- https://github.com/microsoft/vscode/blob/eef30e7165e19b33daa1e15e92fa34ff4a5df0d3/src/vs/workbench/contrib/files/browser/files.contribution.ts#L261 ---@type vim.lpeg.Pattern parsed Lpeg pattern -M._poll_exclude_pattern = parse('**/.git/{objects,subtree-cache}/**') - + parse('**/node_modules/*/**') - + parse('**/.hg/store/**') +M._poll_exclude_pattern = glob.to_lpeg('**/.git/{objects,subtree-cache}/**') + + glob.to_lpeg('**/node_modules/*/**') + + glob.to_lpeg('**/.hg/store/**') --- Registers the workspace/didChangeWatchedFiles capability dynamically. --- @@ -125,12 +50,8 @@ function M.register(reg, ctx) local client = assert(vim.lsp.get_client_by_id(client_id), 'Client must be running') -- Ill-behaved servers may not honor the client capability and try to register -- anyway, so ignore requests when the user has opted out of the feature. - local has_capability = vim.tbl_get( - client.config.capabilities or {}, - 'workspace', - 'didChangeWatchedFiles', - 'dynamicRegistration' - ) + local has_capability = + vim.tbl_get(client.capabilities, 'workspace', 'didChangeWatchedFiles', 'dynamicRegistration') if not has_capability or not client.workspace_folders then return end @@ -143,7 +64,7 @@ function M.register(reg, ctx) local glob_pattern = w.globPattern if type(glob_pattern) == 'string' then - local pattern = parse(glob_pattern) + local pattern = glob.to_lpeg(glob_pattern) if not pattern then error('Cannot parse pattern: ' .. glob_pattern) end @@ -155,7 +76,7 @@ function M.register(reg, ctx) 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 = parse(glob_pattern.pattern) + local pattern = glob.to_lpeg(glob_pattern.pattern) if not pattern then error('Cannot parse pattern: ' .. glob_pattern.pattern) end @@ -248,4 +169,13 @@ function M.unregister(unreg, ctx) end end +--- @param client_id integer +function M.cancel(client_id) + for _, reg_cancels in pairs(cancels[client_id]) do + for _, cancel in pairs(reg_cancels) do + cancel() + end + end +end + return M |