mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge pull request #27347 from lewis6991/fswatch
feat(lsp): add fswatch watchfunc backend
This commit is contained in:
commit
39928a7f24
4
.github/scripts/install_deps.sh
vendored
4
.github/scripts/install_deps.sh
vendored
@ -30,12 +30,12 @@ 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
|
sudo apt-get install -y locales-all cpanminus attr libattr1-dev gdb fswatch
|
||||||
fi
|
fi
|
||||||
elif [[ $os == Darwin ]]; then
|
elif [[ $os == Darwin ]]; then
|
||||||
brew update --quiet
|
brew update --quiet
|
||||||
brew install ninja
|
brew install ninja
|
||||||
if [[ -n $TEST ]]; then
|
if [[ -n $TEST ]]; then
|
||||||
brew install cpanminus
|
brew install cpanminus fswatch
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
@ -369,6 +369,9 @@ The following changes to existing APIs or features add new behavior.
|
|||||||
|
|
||||||
• The `workspace/didChangeWatchedFiles` LSP client capability is now enabled
|
• The `workspace/didChangeWatchedFiles` LSP client capability is now enabled
|
||||||
by default.
|
by default.
|
||||||
|
• On Mac or Windows, `libuv.fs_watch` is used as the backend.
|
||||||
|
• On Linux, `fswatch` (recommended) is used as the backend if available,
|
||||||
|
otherwise `libuv.fs_event` is used on each subdirectory.
|
||||||
|
|
||||||
• |LspRequest| autocmd callbacks now contain additional information about the LSP
|
• |LspRequest| autocmd callbacks now contain additional information about the LSP
|
||||||
request status update that occurred.
|
request status update that occurred.
|
||||||
|
@ -1,45 +1,61 @@
|
|||||||
local M = {}
|
|
||||||
local uv = vim.uv
|
local uv = vim.uv
|
||||||
|
|
||||||
---@enum vim._watch.FileChangeType
|
local M = {}
|
||||||
local FileChangeType = {
|
|
||||||
|
--- @enum vim._watch.FileChangeType
|
||||||
|
--- Types of events watchers will emit.
|
||||||
|
M.FileChangeType = {
|
||||||
Created = 1,
|
Created = 1,
|
||||||
Changed = 2,
|
Changed = 2,
|
||||||
Deleted = 3,
|
Deleted = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Enumeration describing the types of events watchers will emit.
|
--- @class vim._watch.Opts
|
||||||
M.FileChangeType = vim.tbl_add_reverse_lookup(FileChangeType)
|
|
||||||
|
|
||||||
--- Joins filepath elements by static '/' separator
|
|
||||||
---
|
---
|
||||||
---@param ... (string) The path elements.
|
--- @field debounce? integer ms
|
||||||
---@return string
|
|
||||||
local function filepath_join(...)
|
|
||||||
return table.concat({ ... }, '/')
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Stops and closes a libuv |uv_fs_event_t| or |uv_fs_poll_t| handle
|
|
||||||
---
|
---
|
||||||
---@param handle (uv.uv_fs_event_t|uv.uv_fs_poll_t) The handle to stop
|
--- An |lpeg| pattern. Only changes to files whose full paths match the pattern
|
||||||
local function stop(handle)
|
--- will be reported. Only matches against non-directoriess, all directories will
|
||||||
local _, stop_err = handle:stop()
|
--- be watched for new potentially-matching files. exclude_pattern can be used to
|
||||||
assert(not stop_err, stop_err)
|
--- filter out directories. When nil, matches any file name.
|
||||||
local is_closing, close_err = handle:is_closing()
|
--- @field include_pattern? vim.lpeg.Pattern
|
||||||
assert(not close_err, close_err)
|
---
|
||||||
if not is_closing then
|
--- An |lpeg| pattern. Only changes to files and directories whose full path does
|
||||||
handle:close()
|
--- not match the pattern will be reported. Matches against both files and
|
||||||
|
--- directories. When nil, matches nothing.
|
||||||
|
--- @field exclude_pattern? vim.lpeg.Pattern
|
||||||
|
|
||||||
|
--- @alias vim._watch.Callback fun(path: string, change_type: vim._watch.FileChangeType)
|
||||||
|
|
||||||
|
--- @class vim._watch.watch.Opts : vim._watch.Opts
|
||||||
|
--- @field uvflags? uv.fs_event_start.flags
|
||||||
|
|
||||||
|
--- @param path string
|
||||||
|
--- @param opts? vim._watch.Opts
|
||||||
|
local function skip(path, opts)
|
||||||
|
if not opts then
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if opts.include_pattern and opts.include_pattern:match(path) == nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.exclude_pattern and opts.exclude_pattern:match(path) ~= nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Initializes and starts a |uv_fs_event_t|
|
--- Initializes and starts a |uv_fs_event_t|
|
||||||
---
|
---
|
||||||
---@param path (string) The path to watch
|
--- @param path string The path to watch
|
||||||
---@param opts (table|nil) Additional options
|
--- @param opts vim._watch.watch.Opts? Additional options:
|
||||||
--- - uvflags (table|nil)
|
--- - uvflags (table|nil)
|
||||||
--- Same flags as accepted by |uv.fs_event_start()|
|
--- Same flags as accepted by |uv.fs_event_start()|
|
||||||
---@param callback (function) The function called when new events
|
--- @param callback vim._watch.Callback Callback for new events
|
||||||
---@return (function) Stops the watcher
|
--- @return fun() cancel Stops the watcher
|
||||||
function M.watch(path, opts, callback)
|
function M.watch(path, opts, callback)
|
||||||
vim.validate({
|
vim.validate({
|
||||||
path = { path, 'string', false },
|
path = { path, 'string', false },
|
||||||
@ -47,111 +63,120 @@ function M.watch(path, opts, callback)
|
|||||||
callback = { callback, 'function', false },
|
callback = { callback, 'function', false },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
path = vim.fs.normalize(path)
|
path = vim.fs.normalize(path)
|
||||||
local uvflags = opts and opts.uvflags or {}
|
local uvflags = opts and opts.uvflags or {}
|
||||||
local handle, new_err = vim.uv.new_fs_event()
|
local handle = assert(uv.new_fs_event())
|
||||||
assert(not new_err, new_err)
|
|
||||||
handle = assert(handle)
|
|
||||||
local _, start_err = handle:start(path, uvflags, function(err, filename, events)
|
local _, start_err = handle:start(path, uvflags, function(err, filename, events)
|
||||||
assert(not err, err)
|
assert(not err, err)
|
||||||
local fullpath = path
|
local fullpath = path
|
||||||
if filename then
|
if filename then
|
||||||
filename = filename:gsub('\\', '/')
|
fullpath = vim.fs.normalize(vim.fs.joinpath(fullpath, filename))
|
||||||
fullpath = filepath_join(fullpath, filename)
|
|
||||||
end
|
end
|
||||||
local change_type = events.change and M.FileChangeType.Changed or 0
|
|
||||||
|
if skip(fullpath, opts) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @type vim._watch.FileChangeType
|
||||||
|
local change_type
|
||||||
if events.rename then
|
if events.rename then
|
||||||
local _, staterr, staterrname = vim.uv.fs_stat(fullpath)
|
local _, staterr, staterrname = uv.fs_stat(fullpath)
|
||||||
if staterrname == 'ENOENT' then
|
if staterrname == 'ENOENT' then
|
||||||
change_type = M.FileChangeType.Deleted
|
change_type = M.FileChangeType.Deleted
|
||||||
else
|
else
|
||||||
assert(not staterr, staterr)
|
assert(not staterr, staterr)
|
||||||
change_type = M.FileChangeType.Created
|
change_type = M.FileChangeType.Created
|
||||||
end
|
end
|
||||||
|
elseif events.change then
|
||||||
|
change_type = M.FileChangeType.Changed
|
||||||
end
|
end
|
||||||
callback(fullpath, change_type)
|
callback(fullpath, change_type)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert(not start_err, start_err)
|
assert(not start_err, start_err)
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
stop(handle)
|
local _, stop_err = handle:stop()
|
||||||
|
assert(not stop_err, stop_err)
|
||||||
|
local is_closing, close_err = handle:is_closing()
|
||||||
|
assert(not close_err, close_err)
|
||||||
|
if not is_closing then
|
||||||
|
handle:close()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @class watch.PollOpts
|
--- Initializes and starts a |uv_fs_event_t| recursively watching every directory underneath the
|
||||||
--- @field debounce? integer
|
--- directory at path.
|
||||||
--- @field include_pattern? vim.lpeg.Pattern
|
---
|
||||||
--- @field exclude_pattern? vim.lpeg.Pattern
|
--- @param path string The path to watch. Must refer to a directory.
|
||||||
|
--- @param opts vim._watch.Opts? Additional options
|
||||||
|
--- @param callback vim._watch.Callback Callback for new events
|
||||||
|
--- @return fun() cancel Stops the watcher
|
||||||
|
function M.watchdirs(path, opts, callback)
|
||||||
|
vim.validate({
|
||||||
|
path = { path, 'string', false },
|
||||||
|
opts = { opts, 'table', true },
|
||||||
|
callback = { callback, 'function', false },
|
||||||
|
})
|
||||||
|
|
||||||
---@param path string
|
|
||||||
---@param opts watch.PollOpts
|
|
||||||
---@param callback function Called on new events
|
|
||||||
---@return function cancel stops the watcher
|
|
||||||
local function recurse_watch(path, opts, callback)
|
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local debounce = opts.debounce or 500
|
local debounce = opts.debounce or 500
|
||||||
local uvflags = {}
|
|
||||||
---@type table<string, uv.uv_fs_event_t> handle by fullpath
|
---@type table<string, uv.uv_fs_event_t> handle by fullpath
|
||||||
local handles = {}
|
local handles = {}
|
||||||
|
|
||||||
local timer = assert(uv.new_timer())
|
local timer = assert(uv.new_timer())
|
||||||
|
|
||||||
---@type table[]
|
--- Map of file path to boolean indicating if the file has been changed
|
||||||
local changesets = {}
|
--- at some point within the debounce cycle.
|
||||||
|
--- @type table<string, boolean>
|
||||||
|
local filechanges = {}
|
||||||
|
|
||||||
local function is_included(filepath)
|
local process_changes --- @type fun()
|
||||||
return opts.include_pattern and opts.include_pattern:match(filepath)
|
|
||||||
end
|
|
||||||
local function is_excluded(filepath)
|
|
||||||
return opts.exclude_pattern and opts.exclude_pattern:match(filepath)
|
|
||||||
end
|
|
||||||
|
|
||||||
local process_changes = function()
|
|
||||||
assert(false, "Replaced later. I'm only here as forward reference")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
--- @param filepath string
|
||||||
|
--- @return uv.fs_event_start.callback
|
||||||
local function create_on_change(filepath)
|
local function create_on_change(filepath)
|
||||||
return function(err, filename, events)
|
return function(err, filename, events)
|
||||||
assert(not err, err)
|
assert(not err, err)
|
||||||
local fullpath = vim.fs.joinpath(filepath, filename)
|
local fullpath = vim.fs.joinpath(filepath, filename)
|
||||||
if is_included(fullpath) and not is_excluded(filepath) then
|
if skip(fullpath, opts) then
|
||||||
table.insert(changesets, {
|
return
|
||||||
fullpath = fullpath,
|
|
||||||
events = events,
|
|
||||||
})
|
|
||||||
timer:start(debounce, 0, process_changes)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not filechanges[fullpath] then
|
||||||
|
filechanges[fullpath] = events.change or false
|
||||||
|
end
|
||||||
|
timer:start(debounce, 0, process_changes)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
process_changes = function()
|
process_changes = function()
|
||||||
---@type table<string, table[]>
|
-- Since the callback is debounced it may have also been deleted later on
|
||||||
local filechanges = vim.defaulttable()
|
-- so we always need to check the existence of the file:
|
||||||
for i, change in ipairs(changesets) do
|
-- stat succeeds, changed=true -> Changed
|
||||||
changesets[i] = nil
|
-- stat succeeds, changed=false -> Created
|
||||||
if is_included(change.fullpath) and not is_excluded(change.fullpath) then
|
-- stat fails -> Removed
|
||||||
table.insert(filechanges[change.fullpath], change.events)
|
for fullpath, changed in pairs(filechanges) do
|
||||||
end
|
|
||||||
end
|
|
||||||
for fullpath, events_list in pairs(filechanges) do
|
|
||||||
uv.fs_stat(fullpath, function(_, stat)
|
uv.fs_stat(fullpath, function(_, stat)
|
||||||
---@type vim._watch.FileChangeType
|
---@type vim._watch.FileChangeType
|
||||||
local change_type
|
local change_type
|
||||||
if stat then
|
if stat then
|
||||||
change_type = FileChangeType.Created
|
change_type = changed and M.FileChangeType.Changed or M.FileChangeType.Created
|
||||||
for _, event in ipairs(events_list) do
|
|
||||||
if event.change then
|
|
||||||
change_type = FileChangeType.Changed
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if stat.type == 'directory' then
|
if stat.type == 'directory' then
|
||||||
local handle = handles[fullpath]
|
local handle = handles[fullpath]
|
||||||
if not handle then
|
if not handle then
|
||||||
handle = assert(uv.new_fs_event())
|
handle = assert(uv.new_fs_event())
|
||||||
handles[fullpath] = handle
|
handles[fullpath] = handle
|
||||||
handle:start(fullpath, uvflags, create_on_change(fullpath))
|
handle:start(fullpath, {}, create_on_change(fullpath))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
change_type = M.FileChangeType.Deleted
|
||||||
local handle = handles[fullpath]
|
local handle = handles[fullpath]
|
||||||
if handle then
|
if handle then
|
||||||
if not handle:is_closing() then
|
if not handle:is_closing() then
|
||||||
@ -159,15 +184,16 @@ local function recurse_watch(path, opts, callback)
|
|||||||
end
|
end
|
||||||
handles[fullpath] = nil
|
handles[fullpath] = nil
|
||||||
end
|
end
|
||||||
change_type = FileChangeType.Deleted
|
|
||||||
end
|
end
|
||||||
callback(fullpath, change_type)
|
callback(fullpath, change_type)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
filechanges = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local root_handle = assert(uv.new_fs_event())
|
local root_handle = assert(uv.new_fs_event())
|
||||||
handles[path] = root_handle
|
handles[path] = root_handle
|
||||||
root_handle:start(path, uvflags, create_on_change(path))
|
root_handle:start(path, {}, create_on_change(path))
|
||||||
|
|
||||||
--- "640K ought to be enough for anyone"
|
--- "640K ought to be enough for anyone"
|
||||||
--- Who has folders this deep?
|
--- Who has folders this deep?
|
||||||
@ -175,12 +201,13 @@ local function recurse_watch(path, opts, callback)
|
|||||||
|
|
||||||
for name, type in vim.fs.dir(path, { depth = max_depth }) do
|
for name, type in vim.fs.dir(path, { depth = max_depth }) do
|
||||||
local filepath = vim.fs.joinpath(path, name)
|
local filepath = vim.fs.joinpath(path, name)
|
||||||
if type == 'directory' and not is_excluded(filepath) then
|
if type == 'directory' and not skip(filepath, opts) then
|
||||||
local handle = assert(uv.new_fs_event())
|
local handle = assert(uv.new_fs_event())
|
||||||
handles[filepath] = handle
|
handles[filepath] = handle
|
||||||
handle:start(filepath, uvflags, create_on_change(filepath))
|
handle:start(filepath, {}, create_on_change(filepath))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function cancel()
|
local function cancel()
|
||||||
for fullpath, handle in pairs(handles) do
|
for fullpath, handle in pairs(handles) do
|
||||||
if not handle:is_closing() then
|
if not handle:is_closing() then
|
||||||
@ -191,34 +218,85 @@ local function recurse_watch(path, opts, callback)
|
|||||||
timer:stop()
|
timer:stop()
|
||||||
timer:close()
|
timer:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
return cancel
|
return cancel
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Initializes and starts a |uv_fs_poll_t| recursively watching every file underneath the
|
--- @param data string
|
||||||
--- directory at path.
|
--- @param opts vim._watch.Opts?
|
||||||
---
|
--- @param callback vim._watch.Callback
|
||||||
---@param path (string) The path to watch. Must refer to a directory.
|
local function fswatch_output_handler(data, opts, callback)
|
||||||
---@param opts (table|nil) Additional options
|
local d = vim.split(data, '%s+')
|
||||||
--- - debounce (number|nil)
|
|
||||||
--- Time events are debounced in ms. Defaults to 500
|
-- only consider the last reported event
|
||||||
--- - include_pattern (LPeg pattern|nil)
|
local fullpath, event = d[1], d[#d]
|
||||||
--- An |lpeg| pattern. Only changes to files whose full paths match the pattern
|
|
||||||
--- will be reported. Only matches against non-directoriess, all directories will
|
if skip(fullpath, opts) then
|
||||||
--- be watched for new potentially-matching files. exclude_pattern can be used to
|
return
|
||||||
--- filter out directories. When nil, matches any file name.
|
end
|
||||||
--- - exclude_pattern (LPeg pattern|nil)
|
|
||||||
--- An |lpeg| pattern. Only changes to files and directories whose full path does
|
--- @type integer
|
||||||
--- not match the pattern will be reported. Matches against both files and
|
local change_type
|
||||||
--- directories. When nil, matches nothing.
|
|
||||||
---@param callback (function) The function called when new events
|
if event == 'Created' then
|
||||||
---@return function Stops the watcher
|
change_type = M.FileChangeType.Created
|
||||||
function M.poll(path, opts, callback)
|
elseif event == 'Removed' then
|
||||||
vim.validate({
|
change_type = M.FileChangeType.Deleted
|
||||||
path = { path, 'string', false },
|
elseif event == 'Updated' then
|
||||||
opts = { opts, 'table', true },
|
change_type = M.FileChangeType.Changed
|
||||||
callback = { callback, 'function', false },
|
elseif event == 'Renamed' then
|
||||||
|
local _, staterr, staterrname = uv.fs_stat(fullpath)
|
||||||
|
if staterrname == 'ENOENT' then
|
||||||
|
change_type = M.FileChangeType.Deleted
|
||||||
|
else
|
||||||
|
assert(not staterr, staterr)
|
||||||
|
change_type = M.FileChangeType.Created
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if change_type then
|
||||||
|
callback(fullpath, change_type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param path string The path to watch. Must refer to a directory.
|
||||||
|
--- @param opts vim._watch.Opts?
|
||||||
|
--- @param callback vim._watch.Callback Callback for new events
|
||||||
|
--- @return fun() cancel Stops the watcher
|
||||||
|
function M.fswatch(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({
|
||||||
|
'fswatch',
|
||||||
|
'--event=Created',
|
||||||
|
'--event=Removed',
|
||||||
|
'--event=Updated',
|
||||||
|
'--event=Renamed',
|
||||||
|
'--event-flags',
|
||||||
|
'--recursive',
|
||||||
|
'--latency=' .. tostring(latency),
|
||||||
|
'--exclude',
|
||||||
|
'/.git/',
|
||||||
|
path,
|
||||||
|
}, {
|
||||||
|
stdout = function(err, data)
|
||||||
|
if err then
|
||||||
|
error(err)
|
||||||
|
end
|
||||||
|
|
||||||
|
for line in vim.gsplit(data or '', '\n', { plain = true, trimempty = true }) do
|
||||||
|
fswatch_output_handler(line, opts, callback)
|
||||||
|
end
|
||||||
|
end,
|
||||||
})
|
})
|
||||||
return recurse_watch(path, opts, callback)
|
|
||||||
|
return function()
|
||||||
|
obj:kill(2)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@ -7,7 +7,13 @@ local lpeg = vim.lpeg
|
|||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M._watchfunc = (vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1) and watch.watch or watch.poll
|
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
|
||||||
|
|
||||||
---@type table<integer, table<string, function[]>> client id -> registration id -> cancel function
|
---@type table<integer, table<string, function[]>> client id -> registration id -> cancel function
|
||||||
local cancels = vim.defaulttable()
|
local cancels = vim.defaulttable()
|
||||||
@ -163,4 +169,13 @@ function M.unregister(unreg, ctx)
|
|||||||
end
|
end
|
||||||
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
|
return M
|
||||||
|
@ -815,6 +815,7 @@ function Client:_stop(force)
|
|||||||
rpc.terminate()
|
rpc.terminate()
|
||||||
self._graceful_shutdown_failed = true
|
self._graceful_shutdown_failed = true
|
||||||
end
|
end
|
||||||
|
vim.lsp._watchfiles.cancel(self.id)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
--- Performs a healthcheck for LSP
|
local report_info = vim.health.info
|
||||||
function M.check()
|
local report_warn = vim.health.warn
|
||||||
local report_info = vim.health.info
|
|
||||||
local report_warn = vim.health.warn
|
|
||||||
|
|
||||||
|
local function check_log()
|
||||||
local log = vim.lsp.log
|
local log = vim.lsp.log
|
||||||
local current_log_level = log.get_level()
|
local current_log_level = log.get_level()
|
||||||
local log_level_string = log.levels[current_log_level] ---@type string
|
local log_level_string = log.levels[current_log_level] ---@type string
|
||||||
@ -27,9 +26,11 @@ function M.check()
|
|||||||
|
|
||||||
local report_fn = (log_size / 1000000 > 100 and report_warn or report_info)
|
local report_fn = (log_size / 1000000 > 100 and report_warn or report_info)
|
||||||
report_fn(string.format('Log size: %d KB', log_size / 1000))
|
report_fn(string.format('Log size: %d KB', log_size / 1000))
|
||||||
|
end
|
||||||
|
|
||||||
local clients = vim.lsp.get_clients()
|
local function check_active_clients()
|
||||||
vim.health.start('vim.lsp: Active Clients')
|
vim.health.start('vim.lsp: Active Clients')
|
||||||
|
local clients = vim.lsp.get_clients()
|
||||||
if next(clients) then
|
if next(clients) then
|
||||||
for _, client in pairs(clients) do
|
for _, client in pairs(clients) do
|
||||||
local attached_to = table.concat(vim.tbl_keys(client.attached_buffers or {}), ',')
|
local attached_to = table.concat(vim.tbl_keys(client.attached_buffers or {}), ',')
|
||||||
@ -48,4 +49,33 @@ function M.check()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function check_watcher()
|
||||||
|
vim.health.start('vim.lsp: File watcher')
|
||||||
|
local watchfunc = vim.lsp._watchfiles._watchfunc
|
||||||
|
assert(watchfunc)
|
||||||
|
local watchfunc_name --- @type string
|
||||||
|
if watchfunc == vim._watch.watch then
|
||||||
|
watchfunc_name = 'libuv-watch'
|
||||||
|
elseif watchfunc == vim._watch.watchdirs then
|
||||||
|
watchfunc_name = 'libuv-watchdirs'
|
||||||
|
elseif watchfunc == vim._watch.fswatch then
|
||||||
|
watchfunc_name = 'fswatch'
|
||||||
|
else
|
||||||
|
local nm = debug.getinfo(watchfunc, 'S').source
|
||||||
|
watchfunc_name = string.format('Custom (%s)', nm)
|
||||||
|
end
|
||||||
|
|
||||||
|
report_info('File watch backend: ' .. watchfunc_name)
|
||||||
|
if watchfunc_name == 'libuv-watchdirs' then
|
||||||
|
report_warn('libuv-watchdirs has known performance issues. Consider installing fswatch.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Performs a healthcheck for LSP
|
||||||
|
function M.check()
|
||||||
|
check_log()
|
||||||
|
check_active_clients()
|
||||||
|
check_watcher()
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@ -2,72 +2,113 @@ local helpers = require('test.functional.helpers')(after_each)
|
|||||||
local eq = helpers.eq
|
local eq = helpers.eq
|
||||||
local exec_lua = helpers.exec_lua
|
local exec_lua = helpers.exec_lua
|
||||||
local clear = helpers.clear
|
local clear = helpers.clear
|
||||||
|
local is_ci = helpers.is_ci
|
||||||
local is_os = helpers.is_os
|
local is_os = helpers.is_os
|
||||||
local skip = helpers.skip
|
local skip = helpers.skip
|
||||||
|
|
||||||
|
-- Create a file via a rename to avoid multiple
|
||||||
|
-- events which can happen with some backends on some platforms
|
||||||
|
local function touch(path)
|
||||||
|
local tmp = helpers.tmpname()
|
||||||
|
io.open(tmp, 'w'):close()
|
||||||
|
assert(vim.uv.fs_rename(tmp, path))
|
||||||
|
end
|
||||||
|
|
||||||
describe('vim._watch', function()
|
describe('vim._watch', function()
|
||||||
before_each(function()
|
before_each(function()
|
||||||
clear()
|
clear()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('watch', function()
|
local function run(watchfunc)
|
||||||
it('detects file changes', function()
|
it('detects file changes (watchfunc=' .. watchfunc .. '())', function()
|
||||||
skip(is_os('bsd'), 'Stopped working on bsd after 3ca967387c49c754561c3b11a574797504d40f38')
|
if watchfunc == 'fswatch' then
|
||||||
|
skip(is_os('mac'), 'flaky test on mac')
|
||||||
|
skip(
|
||||||
|
not is_ci() and helpers.fn.executable('fswatch') == 0,
|
||||||
|
'fswatch not installed and not on CI'
|
||||||
|
)
|
||||||
|
skip(is_os('win'), 'not supported on windows')
|
||||||
|
end
|
||||||
|
|
||||||
|
if watchfunc == 'watch' then
|
||||||
|
skip(is_os('bsd'), 'Stopped working on bsd after 3ca967387c49c754561c3b11a574797504d40f38')
|
||||||
|
else
|
||||||
|
skip(
|
||||||
|
is_os('bsd'),
|
||||||
|
'kqueue only reports events on watched folder itself, not contained files #26110'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(helpers.tmpname()) .. '/nvim_XXXXXXXXXX')
|
local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(helpers.tmpname()) .. '/nvim_XXXXXXXXXX')
|
||||||
|
|
||||||
local result = exec_lua(
|
local expected_events = 0
|
||||||
|
|
||||||
|
local function wait_for_event()
|
||||||
|
expected_events = expected_events + 1
|
||||||
|
exec_lua(
|
||||||
|
[[
|
||||||
|
local expected_events = ...
|
||||||
|
assert(
|
||||||
|
vim.wait(3000, function()
|
||||||
|
return #_G.events == expected_events
|
||||||
|
end),
|
||||||
|
string.format(
|
||||||
|
'Timed out waiting for expected event no. %d. Current events seen so far: %s',
|
||||||
|
expected_events,
|
||||||
|
vim.inspect(events)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]],
|
||||||
|
expected_events
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local unwatched_path = root_dir .. '/file.unwatched'
|
||||||
|
local watched_path = root_dir .. '/file'
|
||||||
|
|
||||||
|
exec_lua(
|
||||||
[[
|
[[
|
||||||
local root_dir = ...
|
local root_dir, watchfunc = ...
|
||||||
|
|
||||||
local events = {}
|
_G.events = {}
|
||||||
|
|
||||||
local expected_events = 0
|
_G.stop_watch = vim._watch[watchfunc](root_dir, {
|
||||||
local function wait_for_events()
|
debounce = 100,
|
||||||
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))
|
include_pattern = vim.lpeg.P(root_dir) * vim.lpeg.P("/file") ^ -1,
|
||||||
end
|
exclude_pattern = vim.lpeg.P(root_dir .. '/file.unwatched'),
|
||||||
|
}, function(path, change_type)
|
||||||
local stop = vim._watch.watch(root_dir, {}, function(path, change_type)
|
table.insert(_G.events, { path = path, change_type = change_type })
|
||||||
table.insert(events, { path = path, change_type = change_type })
|
end)
|
||||||
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
|
root_dir,
|
||||||
|
watchfunc
|
||||||
)
|
)
|
||||||
|
|
||||||
local expected = {
|
if watchfunc ~= 'watch' then
|
||||||
|
vim.uv.sleep(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
touch(watched_path)
|
||||||
|
touch(unwatched_path)
|
||||||
|
|
||||||
|
wait_for_event()
|
||||||
|
|
||||||
|
os.remove(watched_path)
|
||||||
|
os.remove(unwatched_path)
|
||||||
|
|
||||||
|
wait_for_event()
|
||||||
|
|
||||||
|
exec_lua [[_G.stop_watch()]]
|
||||||
|
|
||||||
|
-- No events should come through anymore
|
||||||
|
|
||||||
|
vim.uv.sleep(100)
|
||||||
|
touch(watched_path)
|
||||||
|
vim.uv.sleep(100)
|
||||||
|
os.remove(watched_path)
|
||||||
|
vim.uv.sleep(100)
|
||||||
|
|
||||||
|
eq({
|
||||||
{
|
{
|
||||||
change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
|
change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
|
||||||
path = root_dir .. '/file',
|
path = root_dir .. '/file',
|
||||||
@ -76,106 +117,11 @@ describe('vim._watch', function()
|
|||||||
change_type = exec_lua([[return vim._watch.FileChangeType.Deleted]]),
|
change_type = exec_lua([[return vim._watch.FileChangeType.Deleted]]),
|
||||||
path = root_dir .. '/file',
|
path = root_dir .. '/file',
|
||||||
},
|
},
|
||||||
}
|
}, exec_lua [[return _G.events]])
|
||||||
|
|
||||||
-- 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)
|
||||||
end)
|
end
|
||||||
|
|
||||||
describe('poll', function()
|
run('watch')
|
||||||
it('detects file changes', function()
|
run('watchdirs')
|
||||||
skip(
|
run('fswatch')
|
||||||
is_os('bsd'),
|
|
||||||
'kqueue only reports events on watched folder itself, not contained files #26110'
|
|
||||||
)
|
|
||||||
local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(helpers.tmpname()) .. '/nvim_XXXXXXXXXX')
|
|
||||||
|
|
||||||
local result = exec_lua(
|
|
||||||
[[
|
|
||||||
local root_dir = ...
|
|
||||||
local lpeg = vim.lpeg
|
|
||||||
|
|
||||||
local events = {}
|
|
||||||
|
|
||||||
local debounce = 100
|
|
||||||
local wait_ms = debounce + 200
|
|
||||||
|
|
||||||
local expected_events = 0
|
|
||||||
local function wait_for_events()
|
|
||||||
assert(vim.wait(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 incl = lpeg.P(root_dir) * lpeg.P("/file")^-1
|
|
||||||
local excl = lpeg.P(root_dir..'/file.unwatched')
|
|
||||||
local stop = vim._watch.poll(root_dir, {
|
|
||||||
debounce = debounce,
|
|
||||||
include_pattern = incl,
|
|
||||||
exclude_pattern = excl,
|
|
||||||
}, function(path, change_type)
|
|
||||||
table.insert(events, { path = path, change_type = change_type })
|
|
||||||
end)
|
|
||||||
|
|
||||||
local watched_path = root_dir .. '/file'
|
|
||||||
local watched, err = io.open(watched_path, 'w')
|
|
||||||
assert(not err, err)
|
|
||||||
local unwatched_path = root_dir .. '/file.unwatched'
|
|
||||||
local unwatched, err = io.open(unwatched_path, 'w')
|
|
||||||
assert(not err, err)
|
|
||||||
|
|
||||||
expected_events = expected_events + 1
|
|
||||||
wait_for_events()
|
|
||||||
|
|
||||||
watched:close()
|
|
||||||
os.remove(watched_path)
|
|
||||||
unwatched:close()
|
|
||||||
os.remove(unwatched_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)
|
|
||||||
|
|
||||||
watched:close()
|
|
||||||
os.remove(watched_path)
|
|
||||||
|
|
||||||
return events
|
|
||||||
]],
|
|
||||||
root_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
local created = exec_lua([[return vim._watch.FileChangeType.Created]])
|
|
||||||
local deleted = exec_lua([[return vim._watch.FileChangeType.Deleted]])
|
|
||||||
local expected = {
|
|
||||||
{
|
|
||||||
change_type = created,
|
|
||||||
path = root_dir .. '/file',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
change_type = deleted,
|
|
||||||
path = root_dir .. '/file',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
eq(expected, result)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
end)
|
||||||
|
@ -205,8 +205,8 @@ describe('LSP', function()
|
|||||||
client.stop()
|
client.stop()
|
||||||
end,
|
end,
|
||||||
on_exit = function(code, signal)
|
on_exit = function(code, signal)
|
||||||
eq(0, code, 'exit code', fake_lsp_logfile)
|
eq(0, code, 'exit code')
|
||||||
eq(0, signal, 'exit signal', fake_lsp_logfile)
|
eq(0, signal, 'exit signal')
|
||||||
end,
|
end,
|
||||||
settings = {
|
settings = {
|
||||||
dummy = 1,
|
dummy = 1,
|
||||||
@ -4490,113 +4490,140 @@ describe('LSP', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe('vim.lsp._watchfiles', function()
|
describe('vim.lsp._watchfiles', function()
|
||||||
it('sends notifications when files change', function()
|
local function test_filechanges(watchfunc)
|
||||||
skip(
|
it(
|
||||||
is_os('bsd'),
|
string.format('sends notifications when files change (watchfunc=%s)', watchfunc),
|
||||||
'kqueue only reports events on watched folder itself, not contained files #26110'
|
function()
|
||||||
)
|
if watchfunc == 'fswatch' then
|
||||||
local root_dir = tmpname()
|
skip(
|
||||||
os.remove(root_dir)
|
not is_ci() and fn.executable('fswatch') == 0,
|
||||||
mkdir(root_dir)
|
'fswatch not installed and not on CI'
|
||||||
|
)
|
||||||
|
skip(is_os('win'), 'not supported on windows')
|
||||||
|
skip(is_os('mac'), 'flaky')
|
||||||
|
end
|
||||||
|
|
||||||
exec_lua(create_server_definition)
|
skip(
|
||||||
local result = exec_lua(
|
is_os('bsd'),
|
||||||
[[
|
'kqueue only reports events on watched folder itself, not contained files #26110'
|
||||||
local root_dir = ...
|
)
|
||||||
|
|
||||||
local server = _create_server()
|
local root_dir = tmpname()
|
||||||
local client_id = vim.lsp.start({
|
os.remove(root_dir)
|
||||||
name = 'watchfiles-test',
|
mkdir(root_dir)
|
||||||
cmd = server.cmd,
|
|
||||||
root_dir = root_dir,
|
exec_lua(create_server_definition)
|
||||||
capabilities = {
|
local result = exec_lua(
|
||||||
workspace = {
|
[[
|
||||||
didChangeWatchedFiles = {
|
local root_dir, watchfunc = ...
|
||||||
dynamicRegistration = true,
|
|
||||||
|
local server = _create_server()
|
||||||
|
local client_id = vim.lsp.start({
|
||||||
|
name = 'watchfiles-test',
|
||||||
|
cmd = server.cmd,
|
||||||
|
root_dir = root_dir,
|
||||||
|
capabilities = {
|
||||||
|
workspace = {
|
||||||
|
didChangeWatchedFiles = {
|
||||||
|
dynamicRegistration = true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
|
|
||||||
local expected_messages = 2 -- initialize, initialized
|
require('vim.lsp._watchfiles')._watchfunc = require('vim._watch')[watchfunc]
|
||||||
|
|
||||||
local watchfunc = require('vim.lsp._watchfiles')._watchfunc
|
local expected_messages = 0
|
||||||
local msg_wait_timeout = 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()
|
local msg_wait_timeout = watchfunc == 'watch' and 200 or 2500
|
||||||
|
|
||||||
vim.lsp.handlers['client/registerCapability'](nil, {
|
local function wait_for_message(incr)
|
||||||
registrations = {
|
expected_messages = expected_messages + (incr or 1)
|
||||||
{
|
assert(
|
||||||
id = 'watchfiles-test-0',
|
vim.wait(msg_wait_timeout, function()
|
||||||
method = 'workspace/didChangeWatchedFiles',
|
return #server.messages == expected_messages
|
||||||
registerOptions = {
|
end),
|
||||||
watchers = {
|
'Timed out waiting for expected number of messages. Current messages seen so far: '
|
||||||
{
|
.. vim.inspect(server.messages)
|
||||||
globPattern = '**/watch',
|
)
|
||||||
kind = 7,
|
end
|
||||||
|
|
||||||
|
wait_for_message(2) -- initialize, initialized
|
||||||
|
|
||||||
|
vim.lsp.handlers['client/registerCapability'](nil, {
|
||||||
|
registrations = {
|
||||||
|
{
|
||||||
|
id = 'watchfiles-test-0',
|
||||||
|
method = 'workspace/didChangeWatchedFiles',
|
||||||
|
registerOptions = {
|
||||||
|
watchers = {
|
||||||
|
{
|
||||||
|
globPattern = '**/watch',
|
||||||
|
kind = 7,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}, { client_id = client_id })
|
||||||
}, { client_id = client_id })
|
|
||||||
|
|
||||||
if watchfunc == vim._watch.poll then
|
if watchfunc ~= 'watch' then
|
||||||
vim.wait(100)
|
vim.wait(100)
|
||||||
|
end
|
||||||
|
|
||||||
|
local path = root_dir .. '/watch'
|
||||||
|
local tmp = vim.fn.tempname()
|
||||||
|
io.open(tmp, 'w'):close()
|
||||||
|
vim.uv.fs_rename(tmp, path)
|
||||||
|
|
||||||
|
wait_for_message()
|
||||||
|
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
wait_for_message()
|
||||||
|
|
||||||
|
vim.lsp.stop_client(client_id)
|
||||||
|
|
||||||
|
return server.messages
|
||||||
|
]],
|
||||||
|
root_dir,
|
||||||
|
watchfunc
|
||||||
|
)
|
||||||
|
|
||||||
|
local uri = vim.uri_from_fname(root_dir .. '/watch')
|
||||||
|
|
||||||
|
eq(6, #result)
|
||||||
|
|
||||||
|
eq({
|
||||||
|
method = 'workspace/didChangeWatchedFiles',
|
||||||
|
params = {
|
||||||
|
changes = {
|
||||||
|
{
|
||||||
|
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
|
||||||
|
uri = uri,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, result[3])
|
||||||
|
|
||||||
|
eq({
|
||||||
|
method = 'workspace/didChangeWatchedFiles',
|
||||||
|
params = {
|
||||||
|
changes = {
|
||||||
|
{
|
||||||
|
type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
|
||||||
|
uri = uri,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, result[4])
|
||||||
end
|
end
|
||||||
|
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
|
||||||
local function watched_uri(fname)
|
test_filechanges('watch')
|
||||||
return exec_lua(
|
test_filechanges('watchdirs')
|
||||||
[[
|
test_filechanges('fswatch')
|
||||||
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()
|
it('correctly registers and unregisters', function()
|
||||||
local root_dir = '/some_dir'
|
local root_dir = '/some_dir'
|
||||||
|
@ -372,6 +372,8 @@ function module.sysname()
|
|||||||
return uv.os_uname().sysname:lower()
|
return uv.os_uname().sysname:lower()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- @param s 'win'|'mac'|'freebsd'|'openbsd'|'bsd'
|
||||||
|
--- @return boolean
|
||||||
function module.is_os(s)
|
function module.is_os(s)
|
||||||
if not (s == 'win' or s == 'mac' or s == 'freebsd' or s == 'openbsd' or s == 'bsd') then
|
if not (s == 'win' or s == 'mac' or s == 'freebsd' or s == 'openbsd' or s == 'bsd') then
|
||||||
error('unknown platform: ' .. tostring(s))
|
error('unknown platform: ' .. tostring(s))
|
||||||
@ -396,33 +398,32 @@ local function tmpdir_is_local(dir)
|
|||||||
return not not (dir and dir:find('Xtest'))
|
return not not (dir and dir:find('Xtest'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local tmpname_id = 0
|
||||||
|
local tmpdir = tmpdir_get()
|
||||||
|
|
||||||
--- Creates a new temporary file for use by tests.
|
--- Creates a new temporary file for use by tests.
|
||||||
module.tmpname = (function()
|
function module.tmpname()
|
||||||
local seq = 0
|
if tmpdir_is_local(tmpdir) then
|
||||||
local tmpdir = tmpdir_get()
|
-- Cannot control os.tmpname() dir, so hack our own tmpname() impl.
|
||||||
return function()
|
tmpname_id = tmpname_id + 1
|
||||||
if tmpdir_is_local(tmpdir) then
|
-- "…/Xtest_tmpdir/T42.7"
|
||||||
-- Cannot control os.tmpname() dir, so hack our own tmpname() impl.
|
local fname = ('%s/%s.%d'):format(tmpdir, (_G._nvim_test_id or 'nvim-test'), tmpname_id)
|
||||||
seq = seq + 1
|
io.open(fname, 'w'):close()
|
||||||
-- "…/Xtest_tmpdir/T42.7"
|
return fname
|
||||||
local fname = ('%s/%s.%d'):format(tmpdir, (_G._nvim_test_id or 'nvim-test'), seq)
|
|
||||||
io.open(fname, 'w'):close()
|
|
||||||
return fname
|
|
||||||
else
|
|
||||||
local fname = os.tmpname()
|
|
||||||
if module.is_os('win') and fname:sub(1, 2) == '\\s' then
|
|
||||||
-- In Windows tmpname() returns a filename starting with
|
|
||||||
-- special sequence \s, prepend $TEMP path
|
|
||||||
return tmpdir .. fname
|
|
||||||
elseif fname:match('^/tmp') and module.is_os('mac') then
|
|
||||||
-- In OS X /tmp links to /private/tmp
|
|
||||||
return '/private' .. fname
|
|
||||||
else
|
|
||||||
return fname
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end)()
|
|
||||||
|
local fname = os.tmpname()
|
||||||
|
if module.is_os('win') and fname:sub(1, 2) == '\\s' then
|
||||||
|
-- In Windows tmpname() returns a filename starting with
|
||||||
|
-- special sequence \s, prepend $TEMP path
|
||||||
|
return tmpdir .. fname
|
||||||
|
elseif module.is_os('mac') and fname:match('^/tmp') then
|
||||||
|
-- In OS X /tmp links to /private/tmp
|
||||||
|
return '/private' .. fname
|
||||||
|
end
|
||||||
|
|
||||||
|
return fname
|
||||||
|
end
|
||||||
|
|
||||||
local function deps_prefix()
|
local function deps_prefix()
|
||||||
local env = os.getenv('DEPS_PREFIX')
|
local env = os.getenv('DEPS_PREFIX')
|
||||||
|
Loading…
Reference in New Issue
Block a user