mirror of
https://github.com/neovim/neovim.git
synced 2024-12-28 14:31:13 -07:00
a2749482d9
* send vim.NIL instead of not sending workspaceFolders * read fallback rootPath and rootUri from workspaceFolders * update documentation
2589 lines
88 KiB
Lua
2589 lines
88 KiB
Lua
local helpers = require('test.functional.helpers')(after_each)
|
||
|
||
local assert_log = helpers.assert_log
|
||
local clear = helpers.clear
|
||
local buf_lines = helpers.buf_lines
|
||
local command = helpers.command
|
||
local dedent = helpers.dedent
|
||
local exec_lua = helpers.exec_lua
|
||
local eq = helpers.eq
|
||
local eval = helpers.eval
|
||
local matches = helpers.matches
|
||
local pcall_err = helpers.pcall_err
|
||
local pesc = helpers.pesc
|
||
local insert = helpers.insert
|
||
local funcs = helpers.funcs
|
||
local retry = helpers.retry
|
||
local NIL = helpers.NIL
|
||
local read_file = require('test.helpers').read_file
|
||
local write_file = require('test.helpers').write_file
|
||
local isCI = helpers.isCI
|
||
|
||
-- Use these to get access to a coroutine so that I can run async tests and use
|
||
-- yield.
|
||
local run, stop = helpers.run, helpers.stop
|
||
|
||
-- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837
|
||
if helpers.pending_win32(pending) then return end
|
||
|
||
-- Fake LSP server.
|
||
local fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua'
|
||
local fake_lsp_logfile = 'Xtest-fake-lsp.log'
|
||
|
||
teardown(function()
|
||
os.remove(fake_lsp_logfile)
|
||
end)
|
||
|
||
local function clear_notrace()
|
||
-- problem: here be dragons
|
||
-- solution: don't look for dragons to closely
|
||
clear {env={
|
||
NVIM_LUA_NOTRACK="1";
|
||
VIMRUNTIME=os.getenv"VIMRUNTIME";
|
||
}}
|
||
end
|
||
|
||
|
||
local function fake_lsp_server_setup(test_name, timeout_ms, options)
|
||
exec_lua([=[
|
||
lsp = require('vim.lsp')
|
||
local test_name, fixture_filename, logfile, timeout, options = ...
|
||
TEST_RPC_CLIENT_ID = lsp.start_client {
|
||
cmd_env = {
|
||
NVIM_LOG_FILE = logfile;
|
||
NVIM_LUA_NOTRACK = "1";
|
||
};
|
||
cmd = {
|
||
vim.v.progpath, '-Es', '-u', 'NONE', '--headless',
|
||
"-c", string.format("lua TEST_NAME = %q", test_name),
|
||
"-c", string.format("lua TIMEOUT = %d", timeout),
|
||
"-c", "luafile "..fixture_filename,
|
||
};
|
||
handlers = setmetatable({}, {
|
||
__index = function(t, method)
|
||
return function(...)
|
||
return vim.rpcrequest(1, 'handler', ...)
|
||
end
|
||
end;
|
||
});
|
||
workspace_folders = {{
|
||
uri = 'file://' .. vim.loop.cwd(),
|
||
name = 'test_folder',
|
||
}};
|
||
on_init = function(client, result)
|
||
TEST_RPC_CLIENT = client
|
||
vim.rpcrequest(1, "init", result)
|
||
client.config.flags.allow_incremental_sync = options.allow_incremental_sync or false
|
||
end;
|
||
on_exit = function(...)
|
||
vim.rpcnotify(1, "exit", ...)
|
||
end;
|
||
}
|
||
]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {})
|
||
end
|
||
|
||
local function test_rpc_server(config)
|
||
if config.test_name then
|
||
clear_notrace()
|
||
fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options)
|
||
end
|
||
local client = setmetatable({}, {
|
||
__index = function(_, name)
|
||
-- Workaround for not being able to yield() inside __index for Lua 5.1 :(
|
||
-- Otherwise I would just return the value here.
|
||
return function(...)
|
||
return exec_lua([=[
|
||
local name = ...
|
||
if type(TEST_RPC_CLIENT[name]) == 'function' then
|
||
return TEST_RPC_CLIENT[name](select(2, ...))
|
||
else
|
||
return TEST_RPC_CLIENT[name]
|
||
end
|
||
]=], name, ...)
|
||
end
|
||
end;
|
||
})
|
||
local code, signal
|
||
local function on_request(method, args)
|
||
if method == "init" then
|
||
if config.on_init then
|
||
config.on_init(client, unpack(args))
|
||
end
|
||
return NIL
|
||
end
|
||
if method == 'handler' then
|
||
if config.on_handler then
|
||
config.on_handler(unpack(args))
|
||
end
|
||
end
|
||
return NIL
|
||
end
|
||
local function on_notify(method, args)
|
||
if method == 'exit' then
|
||
code, signal = unpack(args)
|
||
return stop()
|
||
end
|
||
end
|
||
-- TODO specify timeout?
|
||
-- run(on_request, on_notify, config.on_setup, 1000)
|
||
run(on_request, on_notify, config.on_setup)
|
||
if config.on_exit then
|
||
config.on_exit(code, signal)
|
||
end
|
||
stop()
|
||
if config.test_name then
|
||
exec_lua("lsp._vim_exit_handler()")
|
||
end
|
||
end
|
||
|
||
describe('LSP', function()
|
||
before_each(function()
|
||
clear_notrace()
|
||
|
||
-- Run an instance of nvim on the file which contains our "scripts".
|
||
-- Pass TEST_NAME to pick the script.
|
||
local test_name = "basic_init"
|
||
exec_lua([=[
|
||
lsp = require('vim.lsp')
|
||
local test_name, fixture_filename, logfile = ...
|
||
function test__start_client()
|
||
return lsp.start_client {
|
||
cmd_env = {
|
||
NVIM_LOG_FILE = logfile;
|
||
};
|
||
cmd = {
|
||
vim.v.progpath, '-Es', '-u', 'NONE', '--headless',
|
||
"-c", string.format("lua TEST_NAME = %q", test_name),
|
||
"-c", "luafile "..fixture_filename;
|
||
};
|
||
workspace_folders = {{
|
||
uri = 'file://' .. vim.loop.cwd(),
|
||
name = 'test_folder',
|
||
}};
|
||
}
|
||
end
|
||
TEST_CLIENT1 = test__start_client()
|
||
]=], test_name, fake_lsp_code, fake_lsp_logfile)
|
||
end)
|
||
|
||
after_each(function()
|
||
exec_lua("lsp._vim_exit_handler()")
|
||
-- exec_lua("lsp.stop_all_clients(true)")
|
||
end)
|
||
|
||
describe('server_name specified', function()
|
||
it('start_client(), stop_client()', function()
|
||
retry(nil, 4000, function()
|
||
eq(1, exec_lua('return #lsp.get_active_clients()'))
|
||
end)
|
||
eq(2, exec_lua([[
|
||
TEST_CLIENT2 = test__start_client()
|
||
return TEST_CLIENT2
|
||
]]))
|
||
eq(3, exec_lua([[
|
||
TEST_CLIENT3 = test__start_client()
|
||
return TEST_CLIENT3
|
||
]]))
|
||
retry(nil, 4000, function()
|
||
eq(3, exec_lua('return #lsp.get_active_clients()'))
|
||
end)
|
||
|
||
eq(false, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil'))
|
||
eq(false, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1).is_stopped()'))
|
||
exec_lua('return lsp.get_client_by_id(TEST_CLIENT1).stop()')
|
||
retry(nil, 4000, function()
|
||
eq(2, exec_lua('return #lsp.get_active_clients()'))
|
||
end)
|
||
eq(true, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil'))
|
||
|
||
exec_lua('lsp.stop_client({TEST_CLIENT2, TEST_CLIENT3})')
|
||
retry(nil, 4000, function()
|
||
eq(0, exec_lua('return #lsp.get_active_clients()'))
|
||
end)
|
||
end)
|
||
|
||
it('stop_client() also works on client objects', function()
|
||
exec_lua([[
|
||
TEST_CLIENT2 = test__start_client()
|
||
TEST_CLIENT3 = test__start_client()
|
||
]])
|
||
retry(nil, 4000, function()
|
||
eq(3, exec_lua('return #lsp.get_active_clients()'))
|
||
end)
|
||
-- Stop all clients.
|
||
exec_lua('lsp.stop_client(lsp.get_active_clients())')
|
||
retry(nil, 4000, function()
|
||
eq(0, exec_lua('return #lsp.get_active_clients()'))
|
||
end)
|
||
end)
|
||
end)
|
||
|
||
describe('basic_init test', function()
|
||
after_each(function()
|
||
stop()
|
||
exec_lua("lsp.stop_client(lsp.get_active_clients())")
|
||
exec_lua("lsp._vim_exit_handler()")
|
||
end)
|
||
|
||
it('should run correctly', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="test", client_id=1}};
|
||
}
|
||
test_rpc_server {
|
||
test_name = "basic_init";
|
||
on_init = function(client, _)
|
||
-- client is a dummy object which will queue up commands to be run
|
||
-- once the server initializes. It can't accept lua callbacks or
|
||
-- other types that may be unserializable for now.
|
||
client.stop()
|
||
end;
|
||
-- If the program timed out, then code will be nil.
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
-- Note that NIL must be used here.
|
||
-- on_handler(err, method, result, client_id)
|
||
on_handler = function(...)
|
||
eq(table.remove(expected_handlers), {...})
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should fail', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="test", client_id=1}};
|
||
}
|
||
test_rpc_server {
|
||
test_name = "basic_init";
|
||
on_init = function(client)
|
||
client.notify('test')
|
||
client.stop()
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(101, code, "exit code", fake_lsp_logfile) -- See fake-lsp-server.lua
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
assert_log(pesc([[assert_eq failed: left == "\"shutdown\"", right == "\"test\""]]),
|
||
fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(...)
|
||
eq(table.remove(expected_handlers), {...}, "expected handler")
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should succeed with manual shutdown', function()
|
||
if isCI() then
|
||
pending('hangs the build on CI #14028, re-enable with freeze timeout #14204')
|
||
return
|
||
elseif helpers.skip_fragile(pending) then
|
||
return
|
||
end
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", bufnr=1, client_id=1}};
|
||
{NIL, {}, {method="test", client_id=1}};
|
||
}
|
||
test_rpc_server {
|
||
test_name = "basic_init";
|
||
on_init = function(client)
|
||
eq(0, client.resolved_capabilities().text_document_did_change)
|
||
client.request('shutdown')
|
||
client.notify('exit')
|
||
client.stop()
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(...)
|
||
eq(table.remove(expected_handlers), {...}, "expected handler")
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('client should return settings via workspace/configuration handler', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, { items = {
|
||
{ section = "testSetting1" };
|
||
{ section = "testSetting2" };
|
||
}}, { method="workspace/configuration", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "check_workspace_configuration";
|
||
on_init = function(_client)
|
||
client = _client
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'start' then
|
||
exec_lua([=[
|
||
local client = vim.lsp.get_client_by_id(TEST_RPC_CLIENT_ID)
|
||
client.config.settings = {
|
||
testSetting1 = true;
|
||
testSetting2 = false;
|
||
}]=])
|
||
end
|
||
if ctx.method == 'workspace/configuration' then
|
||
local server_result = exec_lua([=[
|
||
local method, params = ...
|
||
return require'vim.lsp.handlers'['workspace/configuration'](err, params, {method=method, client_id=TEST_RPC_CLIENT_ID})]=], ctx.method, result)
|
||
client.notify('workspace/configuration', server_result)
|
||
end
|
||
if ctx.method == 'shutdown' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
it('workspace/configuration returns NIL per section if client was started without config.settings', function()
|
||
fake_lsp_server_setup('workspace/configuration no settings')
|
||
eq({ NIL, NIL, }, exec_lua [[
|
||
local result = {
|
||
items = {
|
||
{section = 'foo'},
|
||
{section = 'bar'},
|
||
}
|
||
}
|
||
return vim.lsp.handlers['workspace/configuration'](nil, result, {client_id=TEST_RPC_CLIENT_ID})
|
||
]])
|
||
end)
|
||
|
||
it('should verify capabilities sent', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
}
|
||
test_rpc_server {
|
||
test_name = "basic_check_capabilities";
|
||
on_init = function(client)
|
||
client.stop()
|
||
local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
|
||
eq(full_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().text_document_save)
|
||
eq(false, client.resolved_capabilities().code_lens)
|
||
eq(false, client.resolved_capabilities().code_lens_resolve)
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(...)
|
||
eq(table.remove(expected_handlers), {...}, "expected handler")
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('client.supports_methods() should validate capabilities', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
}
|
||
test_rpc_server {
|
||
test_name = "capabilities_for_client_supports_method";
|
||
on_init = function(client)
|
||
client.stop()
|
||
local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
|
||
eq(full_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().completion)
|
||
eq(true, client.resolved_capabilities().hover)
|
||
eq(false, client.resolved_capabilities().goto_definition)
|
||
eq(false, client.resolved_capabilities().rename)
|
||
eq(true, client.resolved_capabilities().code_lens)
|
||
eq(true, client.resolved_capabilities().code_lens_resolve)
|
||
|
||
-- known methods for resolved capabilities
|
||
eq(true, client.supports_method("textDocument/hover"))
|
||
eq(false, client.supports_method("textDocument/definition"))
|
||
|
||
-- unknown methods are assumed to be supported.
|
||
eq(true, client.supports_method("unknown-method"))
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(...)
|
||
eq(table.remove(expected_handlers), {...}, "expected handler")
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should not call unsupported_method when trying to call an unsupported method', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
}
|
||
test_rpc_server {
|
||
test_name = "capabilities_for_client_supports_method";
|
||
on_setup = function()
|
||
exec_lua([=[
|
||
BUFFER = vim.api.nvim_get_current_buf()
|
||
lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
|
||
vim.lsp.handlers['textDocument/typeDefinition'] = function() end
|
||
]=])
|
||
end;
|
||
on_init = function(client)
|
||
client.stop()
|
||
exec_lua("vim.lsp.buf.type_definition()")
|
||
exec_lua [[
|
||
vim.api.nvim_command(BUFFER.."bwipeout")
|
||
]]
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(...)
|
||
eq(table.remove(expected_handlers), {...}, "expected handler")
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should not call unsupported_method when no client and trying to call an unsupported method', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
}
|
||
test_rpc_server {
|
||
test_name = "capabilities_for_client_supports_method";
|
||
on_setup = function()
|
||
exec_lua([=[
|
||
vim.lsp.handlers['textDocument/typeDefinition'] = function() end
|
||
]=])
|
||
end;
|
||
on_init = function(client)
|
||
client.stop()
|
||
exec_lua("vim.lsp.buf.type_definition()")
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(...)
|
||
eq(table.remove(expected_handlers), {...}, "expected handler")
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should not forward RequestCancelled to callback', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "check_forward_request_cancelled";
|
||
on_init = function(_client)
|
||
_client.request("error_code_test")
|
||
client = _client
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
eq(0, #expected_handlers, "did not call expected handler")
|
||
end;
|
||
on_handler = function(err, _, ctx)
|
||
eq(table.remove(expected_handlers), {err, {}, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then client.stop() end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should forward ContentModified to callback', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{{code = -32801}, NIL, {method = "error_code_test", bufnr=1, client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "check_forward_content_modified";
|
||
on_init = function(_client)
|
||
_client.request("error_code_test")
|
||
client = _client
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
eq(0, #expected_handlers, "did not call expected handler")
|
||
end;
|
||
on_handler = function(err, _, ctx)
|
||
eq(table.remove(expected_handlers), {err, _, ctx}, "expected handler")
|
||
-- if ctx.method == 'error_code_test' then client.notify("finish") end
|
||
if ctx.method ~= 'finish' then client.notify('finish') end
|
||
if ctx.method == 'finish' then client.stop() end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should track pending requests to the language server', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="slow_request", bufnr=1, client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "check_pending_request_tracked";
|
||
on_init = function(_client)
|
||
client = _client
|
||
client.request("slow_request")
|
||
local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=])
|
||
eq("slow_request", request.method)
|
||
eq("pending", request.type)
|
||
client.notify("release")
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
eq(0, #expected_handlers, "did not call expected handler")
|
||
end;
|
||
on_handler = function(err, _, ctx)
|
||
eq(table.remove(expected_handlers), {err, {}, ctx}, "expected handler")
|
||
if ctx.method == 'slow_request' then
|
||
local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=])
|
||
eq(NIL, request)
|
||
client.notify("finish")
|
||
end
|
||
if ctx.method == 'finish' then client.stop() end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should track cancel requests to the language server', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "check_cancel_request_tracked";
|
||
on_init = function(_client)
|
||
client = _client
|
||
client.request("slow_request")
|
||
client.cancel_request(2)
|
||
local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=])
|
||
eq("slow_request", request.method)
|
||
eq("cancel", request.type)
|
||
client.notify("release")
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
eq(0, #expected_handlers, "did not call expected handler")
|
||
end;
|
||
on_handler = function(err, _, ctx)
|
||
eq(table.remove(expected_handlers), {err, {}, ctx}, "expected handler")
|
||
local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=])
|
||
eq(NIL, request)
|
||
if ctx.method == 'finish' then client.stop() end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should clear pending and cancel requests on reply', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="slow_request", bufnr=1, client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "check_tracked_requests_cleared";
|
||
on_init = function(_client)
|
||
client = _client
|
||
client.request("slow_request")
|
||
local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=])
|
||
eq("slow_request", request.method)
|
||
eq("pending", request.type)
|
||
client.cancel_request(2)
|
||
request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=])
|
||
eq("slow_request", request.method)
|
||
eq("cancel", request.type)
|
||
client.notify("release")
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
eq(0, #expected_handlers, "did not call expected handler")
|
||
end;
|
||
on_handler = function(err, _, ctx)
|
||
eq(table.remove(expected_handlers), {err, {}, ctx}, "expected handler")
|
||
if ctx.method == 'slow_request' then
|
||
local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=])
|
||
eq(NIL, request)
|
||
client.notify("finish")
|
||
end
|
||
if ctx.method == 'finish' then client.stop() end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should trigger LspRequest autocmd when requests table changes', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="slow_request", bufnr=1, client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "check_tracked_requests_cleared";
|
||
on_init = function(_client)
|
||
command('let g:requests = 0')
|
||
command('autocmd User LspRequest let g:requests+=1')
|
||
client = _client
|
||
client.request("slow_request")
|
||
eq(1, eval('g:requests'))
|
||
client.cancel_request(2)
|
||
eq(2, eval('g:requests'))
|
||
client.notify("release")
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
eq(0, #expected_handlers, "did not call expected handler")
|
||
eq(3, eval('g:requests'))
|
||
end;
|
||
on_handler = function(err, _, ctx)
|
||
eq(table.remove(expected_handlers), {err, {}, ctx}, "expected handler")
|
||
if ctx.method == 'slow_request' then
|
||
client.notify("finish")
|
||
end
|
||
if ctx.method == 'finish' then client.stop() end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should not send didOpen if the buffer closes before init', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "basic_finish";
|
||
on_setup = function()
|
||
exec_lua [[
|
||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||
"testing";
|
||
"123";
|
||
})
|
||
]]
|
||
eq(1, exec_lua("return TEST_RPC_CLIENT_ID"))
|
||
eq(true, exec_lua("return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)"))
|
||
eq(true, exec_lua("return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)"))
|
||
exec_lua [[
|
||
vim.api.nvim_command(BUFFER.."bwipeout")
|
||
]]
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
|
||
eq(full_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().text_document_open_close)
|
||
client.notify('finish')
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should check the body sent attaching before init', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "basic_check_buffer_open";
|
||
on_setup = function()
|
||
exec_lua [[
|
||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||
"testing";
|
||
"123";
|
||
})
|
||
]]
|
||
exec_lua [[
|
||
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
|
||
]]
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
|
||
eq(full_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().text_document_open_close)
|
||
exec_lua [[
|
||
assert(not lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Shouldn't attach twice")
|
||
]]
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
if ctx.method == 'start' then
|
||
client.notify('finish')
|
||
end
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should check the body sent attaching after init', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "basic_check_buffer_open";
|
||
on_setup = function()
|
||
exec_lua [[
|
||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||
"testing";
|
||
"123";
|
||
})
|
||
]]
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
|
||
eq(full_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().text_document_open_close)
|
||
exec_lua [[
|
||
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
|
||
]]
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
if ctx.method == 'start' then
|
||
client.notify('finish')
|
||
end
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should check the body and didChange full', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "basic_check_buffer_open_and_change";
|
||
on_setup = function()
|
||
exec_lua [[
|
||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||
"testing";
|
||
"123";
|
||
})
|
||
]]
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
|
||
eq(full_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().text_document_open_close)
|
||
exec_lua [[
|
||
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
|
||
]]
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
if ctx.method == 'start' then
|
||
exec_lua [[
|
||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||
"boop";
|
||
})
|
||
]]
|
||
client.notify('finish')
|
||
end
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should check the body and didChange full with noeol', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "basic_check_buffer_open_and_change_noeol";
|
||
on_setup = function()
|
||
exec_lua [[
|
||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||
"testing";
|
||
"123";
|
||
})
|
||
vim.api.nvim_buf_set_option(BUFFER, 'eol', false)
|
||
]]
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
|
||
eq(full_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().text_document_open_close)
|
||
exec_lua [[
|
||
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
|
||
]]
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
if ctx.method == 'start' then
|
||
exec_lua [[
|
||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||
"boop";
|
||
})
|
||
]]
|
||
client.notify('finish')
|
||
end
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should check the body and didChange incremental', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "basic_check_buffer_open_and_change_incremental";
|
||
options = { allow_incremental_sync = true };
|
||
on_setup = function()
|
||
exec_lua [[
|
||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||
"testing";
|
||
"123";
|
||
})
|
||
]]
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental")
|
||
eq(sync_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().text_document_open_close)
|
||
exec_lua [[
|
||
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
|
||
]]
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
if ctx.method == 'start' then
|
||
exec_lua [[
|
||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||
"123boop";
|
||
})
|
||
]]
|
||
client.notify('finish')
|
||
end
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
-- TODO(askhan) we don't support full for now, so we can disable these tests.
|
||
pending('should check the body and didChange incremental normal mode editing', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", bufnr=1, client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "basic_check_buffer_open_and_change_incremental_editing";
|
||
on_setup = function()
|
||
exec_lua [[
|
||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||
"testing";
|
||
"123";
|
||
})
|
||
]]
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental")
|
||
eq(sync_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().text_document_open_close)
|
||
exec_lua [[
|
||
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
|
||
]]
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
if ctx.method == 'start' then
|
||
helpers.command("normal! 1Go")
|
||
client.notify('finish')
|
||
end
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should check the body and didChange full with 2 changes', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "basic_check_buffer_open_and_change_multi";
|
||
on_setup = function()
|
||
exec_lua [[
|
||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||
"testing";
|
||
"123";
|
||
})
|
||
]]
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
|
||
eq(sync_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().text_document_open_close)
|
||
exec_lua [[
|
||
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
|
||
]]
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
if ctx.method == 'start' then
|
||
exec_lua [[
|
||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||
"321";
|
||
})
|
||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||
"boop";
|
||
})
|
||
]]
|
||
client.notify('finish')
|
||
end
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should check the body and didChange full lifecycle', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "basic_check_buffer_open_and_change_multi_and_close";
|
||
on_setup = function()
|
||
exec_lua [[
|
||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||
"testing";
|
||
"123";
|
||
})
|
||
]]
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full")
|
||
eq(sync_kind, client.resolved_capabilities().text_document_did_change)
|
||
eq(true, client.resolved_capabilities().text_document_open_close)
|
||
exec_lua [[
|
||
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
|
||
]]
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result,ctx)
|
||
if ctx.method == 'start' then
|
||
exec_lua [[
|
||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||
"321";
|
||
})
|
||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||
"boop";
|
||
})
|
||
vim.api.nvim_command(BUFFER.."bwipeout")
|
||
]]
|
||
client.notify('finish')
|
||
end
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
end)
|
||
|
||
describe("parsing tests", function()
|
||
it('should handle invalid content-length correctly', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "invalid_header";
|
||
on_setup = function()
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
client.stop(true)
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
end;
|
||
}
|
||
end)
|
||
|
||
it('should not trim vim.NIL from the end of a list', function()
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="finish", client_id=1}};
|
||
{NIL,{
|
||
arguments = { "EXTRACT_METHOD", {metadata = {}}, 3, 0, 6123, NIL },
|
||
command = "refactor.perform",
|
||
title = "EXTRACT_METHOD"
|
||
}, {method="workspace/executeCommand", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
local client
|
||
test_rpc_server {
|
||
test_name = "decode_nil";
|
||
on_setup = function()
|
||
exec_lua [[
|
||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
|
||
"testing";
|
||
"123";
|
||
})
|
||
]]
|
||
end;
|
||
on_init = function(_client)
|
||
client = _client
|
||
exec_lua [[
|
||
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
|
||
]]
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'finish' then
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
end)
|
||
describe('lsp._cmd_parts test', function()
|
||
local function _cmd_parts(input)
|
||
return exec_lua([[
|
||
lsp = require('vim.lsp')
|
||
return lsp._cmd_parts(...)
|
||
]], input)
|
||
end
|
||
it('should valid cmd argument', function()
|
||
eq(true, pcall(_cmd_parts, {"nvim"}))
|
||
eq(true, pcall(_cmd_parts, {"nvim", "--head"}))
|
||
end)
|
||
|
||
it('should invalid cmd argument', function()
|
||
eq('Error executing lua: .../lsp.lua:0: cmd: expected list, got nvim',
|
||
pcall_err(_cmd_parts, 'nvim'))
|
||
eq('Error executing lua: .../lsp.lua:0: cmd argument: expected string, got number',
|
||
pcall_err(_cmd_parts, {'nvim', 1}))
|
||
end)
|
||
end)
|
||
end)
|
||
|
||
describe('LSP', function()
|
||
before_each(function()
|
||
clear_notrace()
|
||
end)
|
||
|
||
local function make_edit(y_0, x_0, y_1, x_1, text)
|
||
return {
|
||
range = {
|
||
start = { line = y_0, character = x_0 };
|
||
["end"] = { line = y_1, character = x_1 };
|
||
};
|
||
newText = type(text) == 'table' and table.concat(text, '\n') or (text or "");
|
||
}
|
||
end
|
||
|
||
describe('apply_text_edits', function()
|
||
before_each(function()
|
||
insert(dedent([[
|
||
First line of text
|
||
Second line of text
|
||
Third line of text
|
||
Fourth line of text
|
||
å å ɧ 汉语 ↥ 🤦 🦄]]))
|
||
end)
|
||
it('applies simple edits', function()
|
||
local edits = {
|
||
make_edit(0, 0, 0, 0, {"123"});
|
||
make_edit(1, 0, 1, 1, {"2"});
|
||
make_edit(2, 0, 2, 2, {"3"});
|
||
make_edit(3, 2, 3, 4, {""});
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'123First line of text';
|
||
'2econd line of text';
|
||
'3ird line of text';
|
||
'Foth line of text';
|
||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||
}, buf_lines(1))
|
||
end)
|
||
it('applies complex edits', function()
|
||
local edits = {
|
||
make_edit(0, 0, 0, 0, {"", "12"});
|
||
make_edit(0, 0, 0, 0, {"3", "foo"});
|
||
make_edit(0, 1, 0, 1, {"bar", "123"});
|
||
make_edit(0, #"First ", 0, #"First line of text", {"guy"});
|
||
make_edit(1, 0, 1, #'Second', {"baz"});
|
||
make_edit(2, #'Th', 2, #"Third", {"e next"});
|
||
make_edit(3, #'', 3, #"Fourth", {"another line of text", "before this"});
|
||
make_edit(3, #'Fourth', 3, #"Fourth line of text", {"!"});
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'';
|
||
'123';
|
||
'fooFbar';
|
||
'123irst guy';
|
||
'baz line of text';
|
||
'The next line of text';
|
||
'another line of text';
|
||
'before this!';
|
||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||
}, buf_lines(1))
|
||
end)
|
||
it('applies complex edits (reversed range)', function()
|
||
local edits = {
|
||
make_edit(0, 0, 0, 0, {"", "12"});
|
||
make_edit(0, 0, 0, 0, {"3", "foo"});
|
||
make_edit(0, 1, 0, 1, {"bar", "123"});
|
||
make_edit(0, #"First line of text", 0, #"First ", {"guy"});
|
||
make_edit(1, #'Second', 1, 0, {"baz"});
|
||
make_edit(2, #"Third", 2, #'Th', {"e next"});
|
||
make_edit(3, #"Fourth", 3, #'', {"another line of text", "before this"});
|
||
make_edit(3, #"Fourth line of text", 3, #'Fourth', {"!"});
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'';
|
||
'123';
|
||
'fooFbar';
|
||
'123irst guy';
|
||
'baz line of text';
|
||
'The next line of text';
|
||
'another line of text';
|
||
'before this!';
|
||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||
}, buf_lines(1))
|
||
end)
|
||
it('applies non-ASCII characters edits', function()
|
||
local edits = {
|
||
make_edit(4, 3, 4, 4, {"ä"});
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'First line of text';
|
||
'Second line of text';
|
||
'Third line of text';
|
||
'Fourth line of text';
|
||
'å ä ɧ 汉语 ↥ 🤦 🦄';
|
||
}, buf_lines(1))
|
||
end)
|
||
it('applies text edits at the end of the document', function()
|
||
local edits = {
|
||
make_edit(5, 0, 5, 0, "foobar");
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'First line of text';
|
||
'Second line of text';
|
||
'Third line of text';
|
||
'Fourth line of text';
|
||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||
'foobar';
|
||
}, buf_lines(1))
|
||
end)
|
||
|
||
describe('cursor position', function()
|
||
it('don\'t fix the cursor if the range contains the cursor', function()
|
||
funcs.nvim_win_set_cursor(0, { 2, 6 })
|
||
local edits = {
|
||
make_edit(1, 0, 1, 19, 'Second line of text')
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'First line of text';
|
||
'Second line of text';
|
||
'Third line of text';
|
||
'Fourth line of text';
|
||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||
}, buf_lines(1))
|
||
eq({ 2, 6 }, funcs.nvim_win_get_cursor(0))
|
||
end)
|
||
|
||
it('fix the cursor to the valid col if the content was removed', function()
|
||
funcs.nvim_win_set_cursor(0, { 2, 6 })
|
||
local edits = {
|
||
make_edit(1, 0, 1, 6, ''),
|
||
make_edit(1, 6, 1, 19, '')
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'First line of text';
|
||
'';
|
||
'Third line of text';
|
||
'Fourth line of text';
|
||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||
}, buf_lines(1))
|
||
eq({ 2, 0 }, funcs.nvim_win_get_cursor(0))
|
||
end)
|
||
|
||
it('fix the cursor to the valid row if the content was removed', function()
|
||
funcs.nvim_win_set_cursor(0, { 2, 6 })
|
||
local edits = {
|
||
make_edit(1, 0, 1, 6, ''),
|
||
make_edit(0, 18, 5, 0, '')
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'First line of text';
|
||
}, buf_lines(1))
|
||
eq({ 1, 6 }, funcs.nvim_win_get_cursor(0))
|
||
end)
|
||
|
||
it('fix the cursor row', function()
|
||
funcs.nvim_win_set_cursor(0, { 3, 0 })
|
||
local edits = {
|
||
make_edit(1, 0, 2, 0, '')
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'First line of text';
|
||
'Third line of text';
|
||
'Fourth line of text';
|
||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||
}, buf_lines(1))
|
||
eq({ 2, 0 }, funcs.nvim_win_get_cursor(0))
|
||
end)
|
||
|
||
it('fix the cursor col', function()
|
||
funcs.nvim_win_set_cursor(0, { 2, 11 })
|
||
local edits = {
|
||
make_edit(1, 7, 1, 11, '')
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'First line of text';
|
||
'Second of text';
|
||
'Third line of text';
|
||
'Fourth line of text';
|
||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||
}, buf_lines(1))
|
||
eq({ 2, 7 }, funcs.nvim_win_get_cursor(0))
|
||
end)
|
||
|
||
it('fix the cursor row and col', function()
|
||
funcs.nvim_win_set_cursor(0, { 2, 12 })
|
||
local edits = {
|
||
make_edit(0, 11, 1, 12, '')
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({
|
||
'First line of text';
|
||
'Third line of text';
|
||
'Fourth line of text';
|
||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||
}, buf_lines(1))
|
||
eq({ 1, 11 }, funcs.nvim_win_get_cursor(0))
|
||
end)
|
||
end)
|
||
|
||
describe('with LSP end line after what Vim considers to be the end line', function()
|
||
it('applies edits when the last linebreak is considered a new line', function()
|
||
local edits = {
|
||
make_edit(0, 0, 5, 0, {"All replaced"});
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({'All replaced'}, buf_lines(1))
|
||
end)
|
||
it('applies edits when the end line is 2 larger than vim\'s', function()
|
||
local edits = {
|
||
make_edit(0, 0, 6, 0, {"All replaced"});
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({'All replaced'}, buf_lines(1))
|
||
end)
|
||
it('applies edits with a column offset', function()
|
||
local edits = {
|
||
make_edit(0, 0, 5, 2, {"All replaced"});
|
||
}
|
||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||
eq({'All replaced'}, buf_lines(1))
|
||
end)
|
||
end)
|
||
end)
|
||
|
||
describe('apply_text_document_edit', function()
|
||
local target_bufnr
|
||
local text_document_edit = function(editVersion)
|
||
return {
|
||
edits = {
|
||
make_edit(0, 0, 0, 3, "First ↥ 🤦 🦄")
|
||
},
|
||
textDocument = {
|
||
uri = "file:///fake/uri";
|
||
version = editVersion
|
||
}
|
||
}
|
||
end
|
||
before_each(function()
|
||
target_bufnr = exec_lua [[
|
||
local bufnr = vim.uri_to_bufnr("file:///fake/uri")
|
||
local lines = {"1st line of text", "2nd line of 语text"}
|
||
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
|
||
return bufnr
|
||
]]
|
||
end)
|
||
it('correctly goes ahead with the edit if all is normal', function()
|
||
exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(5))
|
||
eq({
|
||
'First ↥ 🤦 🦄 line of text';
|
||
'2nd line of 语text';
|
||
}, buf_lines(target_bufnr))
|
||
end)
|
||
it('always accepts edit with version = 0', function()
|
||
exec_lua([[
|
||
local args = {...}
|
||
local bufnr = select(1, ...)
|
||
local text_edit = select(2, ...)
|
||
vim.lsp.util.buf_versions[bufnr] = 10
|
||
vim.lsp.util.apply_text_document_edit(text_edit)
|
||
]], target_bufnr, text_document_edit(0))
|
||
eq({
|
||
'First ↥ 🤦 🦄 line of text';
|
||
'2nd line of 语text';
|
||
}, buf_lines(target_bufnr))
|
||
end)
|
||
it('skips the edit if the version of the edit is behind the local buffer ', function()
|
||
local apply_edit_mocking_current_version = function(edit, versionedBuf)
|
||
exec_lua([[
|
||
local args = {...}
|
||
local versionedBuf = args[2]
|
||
vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion
|
||
vim.lsp.util.apply_text_document_edit(args[1])
|
||
]], edit, versionedBuf)
|
||
end
|
||
|
||
local baseText = {
|
||
'1st line of text';
|
||
'2nd line of 语text';
|
||
}
|
||
|
||
eq(baseText, buf_lines(target_bufnr))
|
||
|
||
-- Apply an edit for an old version, should skip
|
||
apply_edit_mocking_current_version(text_document_edit(2), {currentVersion=7; bufnr=target_bufnr})
|
||
eq(baseText, buf_lines(target_bufnr)) -- no change
|
||
|
||
-- Sanity check that next version to current does apply change
|
||
apply_edit_mocking_current_version(text_document_edit(8), {currentVersion=7; bufnr=target_bufnr})
|
||
eq({
|
||
'First ↥ 🤦 🦄 line of text';
|
||
'2nd line of 语text';
|
||
}, buf_lines(target_bufnr))
|
||
end)
|
||
end)
|
||
|
||
describe('workspace_apply_edit', function()
|
||
it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function()
|
||
local expected = {
|
||
applied = true;
|
||
failureReason = nil;
|
||
}
|
||
eq(expected, exec_lua [[
|
||
local apply_edit = {
|
||
label = nil;
|
||
edit = {};
|
||
}
|
||
return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit)
|
||
]])
|
||
end)
|
||
end)
|
||
|
||
describe('apply_workspace_edit', function()
|
||
local replace_line_edit = function(row, new_line, editVersion)
|
||
return {
|
||
edits = {
|
||
-- NOTE: This is a hack if you have a line longer than 1000 it won't replace it
|
||
make_edit(row, 0, row, 1000, new_line)
|
||
},
|
||
textDocument = {
|
||
uri = "file:///fake/uri";
|
||
version = editVersion
|
||
}
|
||
}
|
||
end
|
||
|
||
-- Some servers send all the edits separately, but with the same version.
|
||
-- We should not stop applying the edits
|
||
local make_workspace_edit = function(changes)
|
||
return {
|
||
documentChanges = changes
|
||
}
|
||
end
|
||
|
||
local target_bufnr, changedtick = nil, nil
|
||
|
||
before_each(function()
|
||
local ret = exec_lua [[
|
||
local bufnr = vim.uri_to_bufnr("file:///fake/uri")
|
||
local lines = {
|
||
"Original Line #1",
|
||
"Original Line #2"
|
||
}
|
||
|
||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||
|
||
local update_changed_tick = function()
|
||
vim.lsp.util.buf_versions[bufnr] = vim.api.nvim_buf_get_var(bufnr, 'changedtick')
|
||
end
|
||
|
||
update_changed_tick()
|
||
vim.api.nvim_buf_attach(bufnr, false, {
|
||
on_changedtick = function()
|
||
update_changed_tick()
|
||
end
|
||
})
|
||
|
||
return {bufnr, vim.api.nvim_buf_get_var(bufnr, 'changedtick')}
|
||
]]
|
||
|
||
target_bufnr = ret[1]
|
||
changedtick = ret[2]
|
||
end)
|
||
|
||
it('apply_workspace_edit applies a single edit', function()
|
||
local new_lines = {
|
||
"First Line",
|
||
}
|
||
|
||
local edits = {}
|
||
for row, line in ipairs(new_lines) do
|
||
table.insert(edits, replace_line_edit(row - 1, line, changedtick))
|
||
end
|
||
|
||
eq({
|
||
"First Line",
|
||
"Original Line #2",
|
||
}, exec_lua([[
|
||
local args = {...}
|
||
local workspace_edits = args[1]
|
||
local target_bufnr = args[2]
|
||
|
||
vim.lsp.util.apply_workspace_edit(workspace_edits)
|
||
|
||
return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false)
|
||
]], make_workspace_edit(edits), target_bufnr))
|
||
end)
|
||
|
||
it('apply_workspace_edit applies multiple edits', function()
|
||
local new_lines = {
|
||
"First Line",
|
||
"Second Line",
|
||
}
|
||
|
||
local edits = {}
|
||
for row, line in ipairs(new_lines) do
|
||
table.insert(edits, replace_line_edit(row - 1, line, changedtick))
|
||
end
|
||
|
||
eq(new_lines, exec_lua([[
|
||
local args = {...}
|
||
local workspace_edits = args[1]
|
||
local target_bufnr = args[2]
|
||
|
||
vim.lsp.util.apply_workspace_edit(workspace_edits)
|
||
|
||
return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false)
|
||
]], make_workspace_edit(edits), target_bufnr))
|
||
end)
|
||
it('Supports file creation with CreateFile payload', function()
|
||
local tmpfile = helpers.tmpname()
|
||
os.remove(tmpfile) -- Should not exist, only interested in a tmpname
|
||
local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile)
|
||
local edit = {
|
||
documentChanges = {
|
||
{
|
||
kind = 'create',
|
||
uri = uri,
|
||
},
|
||
}
|
||
}
|
||
exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit)
|
||
eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
|
||
end)
|
||
it('createFile does not touch file if it exists and ignoreIfExists is set', function()
|
||
local tmpfile = helpers.tmpname()
|
||
write_file(tmpfile, 'Dummy content')
|
||
local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile)
|
||
local edit = {
|
||
documentChanges = {
|
||
{
|
||
kind = 'create',
|
||
uri = uri,
|
||
options = {
|
||
ignoreIfExists = true,
|
||
},
|
||
},
|
||
}
|
||
}
|
||
exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit)
|
||
eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
|
||
eq('Dummy content', read_file(tmpfile))
|
||
end)
|
||
it('createFile overrides file if overwrite is set', function()
|
||
local tmpfile = helpers.tmpname()
|
||
write_file(tmpfile, 'Dummy content')
|
||
local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile)
|
||
local edit = {
|
||
documentChanges = {
|
||
{
|
||
kind = 'create',
|
||
uri = uri,
|
||
options = {
|
||
overwrite = true,
|
||
ignoreIfExists = true, -- overwrite must win over ignoreIfExists
|
||
},
|
||
},
|
||
}
|
||
}
|
||
exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit)
|
||
eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
|
||
eq('', read_file(tmpfile))
|
||
end)
|
||
it('DeleteFile delete file and buffer', function()
|
||
local tmpfile = helpers.tmpname()
|
||
write_file(tmpfile, 'Be gone')
|
||
local uri = exec_lua([[
|
||
local fname = select(1, ...)
|
||
local bufnr = vim.fn.bufadd(fname)
|
||
vim.fn.bufload(bufnr)
|
||
return vim.uri_from_fname(fname)
|
||
]], tmpfile)
|
||
local edit = {
|
||
documentChanges = {
|
||
{
|
||
kind = 'delete',
|
||
uri = uri,
|
||
}
|
||
}
|
||
}
|
||
eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit))
|
||
eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
|
||
eq(false, exec_lua('return vim.api.nvim_buf_is_loaded(vim.fn.bufadd(...))', tmpfile))
|
||
end)
|
||
it('DeleteFile fails if file does not exist and ignoreIfNotExists is false', function()
|
||
local tmpfile = helpers.tmpname()
|
||
os.remove(tmpfile)
|
||
local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile)
|
||
local edit = {
|
||
documentChanges = {
|
||
{
|
||
kind = 'delete',
|
||
uri = uri,
|
||
options = {
|
||
ignoreIfNotExists = false,
|
||
}
|
||
}
|
||
}
|
||
}
|
||
eq(false, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit))
|
||
eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
|
||
end)
|
||
end)
|
||
|
||
describe('completion_list_to_complete_items', function()
|
||
-- Completion option precedence:
|
||
-- textEdit.newText > insertText > label
|
||
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
||
it('should choose right completion option', function ()
|
||
local prefix = 'foo'
|
||
local completion_list = {
|
||
-- resolves into label
|
||
{ label='foobar', sortText="a" },
|
||
{ label='foobar', sortText="b", textEdit={} },
|
||
-- resolves into insertText
|
||
{ label='foocar', sortText="c", insertText='foobar' },
|
||
{ label='foocar', sortText="d", insertText='foobar', textEdit={} },
|
||
-- resolves into textEdit.newText
|
||
{ label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} },
|
||
{ label='foocar', sortText="f", textEdit={newText='foobar'} },
|
||
-- real-world snippet text
|
||
{ label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
|
||
{ label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} },
|
||
-- nested snippet tokens
|
||
{ label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} },
|
||
-- braced tabstop
|
||
{ label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} },
|
||
-- plain text
|
||
{ label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} },
|
||
}
|
||
local completion_list_items = {items=completion_list}
|
||
local expected = {
|
||
{ abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar', sortText="a" } } } } },
|
||
{ abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', sortText="b", textEdit={} } } } } },
|
||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="c", insertText='foobar' } } } } },
|
||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="d", insertText='foobar', textEdit={} } } } } },
|
||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} } } } } },
|
||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="f", textEdit={newText='foobar'} } } } } },
|
||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
|
||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
|
||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
|
||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar()', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} } } } } },
|
||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } },
|
||
}
|
||
|
||
eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix))
|
||
eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list_items, prefix))
|
||
eq({}, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], {}, prefix))
|
||
end)
|
||
end)
|
||
|
||
describe('lsp.util.rename', function()
|
||
it('Can rename an existing file', function()
|
||
local old = helpers.tmpname()
|
||
write_file(old, 'Test content')
|
||
local new = helpers.tmpname()
|
||
os.remove(new) -- only reserve the name, file must not exist for the test scenario
|
||
local lines = exec_lua([[
|
||
local old = select(1, ...)
|
||
local new = select(2, ...)
|
||
vim.lsp.util.rename(old, new)
|
||
|
||
-- after rename the target file must have the contents of the source file
|
||
local bufnr = vim.fn.bufadd(new)
|
||
vim.fn.bufload(new)
|
||
return vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
|
||
]], old, new)
|
||
eq({'Test content'}, lines)
|
||
local exists = exec_lua('return vim.loop.fs_stat(...) ~= nil', old)
|
||
eq(false, exists)
|
||
exists = exec_lua('return vim.loop.fs_stat(...) ~= nil', new)
|
||
eq(true, exists)
|
||
os.remove(new)
|
||
end)
|
||
it('Does not rename file if target exists and ignoreIfExists is set or overwrite is false', function()
|
||
local old = helpers.tmpname()
|
||
write_file(old, 'Old File')
|
||
local new = helpers.tmpname()
|
||
write_file(new, 'New file')
|
||
|
||
exec_lua([[
|
||
local old = select(1, ...)
|
||
local new = select(2, ...)
|
||
|
||
vim.lsp.util.rename(old, new, { ignoreIfExists = true })
|
||
]], old, new)
|
||
|
||
eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', old))
|
||
eq('New file', read_file(new))
|
||
|
||
exec_lua([[
|
||
local old = select(1, ...)
|
||
local new = select(2, ...)
|
||
|
||
vim.lsp.util.rename(old, new, { overwrite = false })
|
||
]], old, new)
|
||
|
||
eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', old))
|
||
eq('New file', read_file(new))
|
||
end)
|
||
it('Does override target if overwrite is true', function()
|
||
local old = helpers.tmpname()
|
||
write_file(old, 'Old file')
|
||
local new = helpers.tmpname()
|
||
write_file(new, 'New file')
|
||
exec_lua([[
|
||
local old = select(1, ...)
|
||
local new = select(2, ...)
|
||
|
||
vim.lsp.util.rename(old, new, { overwrite = true })
|
||
]], old, new)
|
||
|
||
eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', old))
|
||
eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new))
|
||
eq('Old file\n', read_file(new))
|
||
end)
|
||
end)
|
||
|
||
describe('lsp.util.locations_to_items', function()
|
||
it('Convert Location[] to items', function()
|
||
local expected = {
|
||
{
|
||
filename = '/fake/uri',
|
||
lnum = 1,
|
||
col = 3,
|
||
text = 'testing'
|
||
},
|
||
}
|
||
local actual = exec_lua [[
|
||
local bufnr = vim.uri_to_bufnr("file:///fake/uri")
|
||
local lines = {"testing", "123"}
|
||
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
|
||
local locations = {
|
||
{
|
||
uri = 'file:///fake/uri',
|
||
range = {
|
||
start = { line = 0, character = 2 },
|
||
['end'] = { line = 0, character = 3 },
|
||
}
|
||
},
|
||
}
|
||
return vim.lsp.util.locations_to_items(locations)
|
||
]]
|
||
eq(expected, actual)
|
||
end)
|
||
it('Convert LocationLink[] to items', function()
|
||
local expected = {
|
||
{
|
||
filename = '/fake/uri',
|
||
lnum = 1,
|
||
col = 3,
|
||
text = 'testing'
|
||
},
|
||
}
|
||
local actual = exec_lua [[
|
||
local bufnr = vim.uri_to_bufnr("file:///fake/uri")
|
||
local lines = {"testing", "123"}
|
||
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
|
||
local locations = {
|
||
{
|
||
targetUri = vim.uri_from_bufnr(bufnr),
|
||
targetRange = {
|
||
start = { line = 0, character = 2 },
|
||
['end'] = { line = 0, character = 3 },
|
||
},
|
||
targetSelectionRange = {
|
||
start = { line = 0, character = 2 },
|
||
['end'] = { line = 0, character = 3 },
|
||
}
|
||
},
|
||
}
|
||
return vim.lsp.util.locations_to_items(locations)
|
||
]]
|
||
eq(expected, actual)
|
||
end)
|
||
end)
|
||
describe('lsp.util.symbols_to_items', function()
|
||
describe('convert DocumentSymbol[] to items', function()
|
||
it('DocumentSymbol has children', function()
|
||
local expected = {
|
||
{
|
||
col = 1,
|
||
filename = '',
|
||
kind = 'File',
|
||
lnum = 2,
|
||
text = '[File] TestA'
|
||
},
|
||
{
|
||
col = 1,
|
||
filename = '',
|
||
kind = 'Module',
|
||
lnum = 4,
|
||
text = '[Module] TestB'
|
||
},
|
||
{
|
||
col = 1,
|
||
filename = '',
|
||
kind = 'Namespace',
|
||
lnum = 6,
|
||
text = '[Namespace] TestC'
|
||
}
|
||
}
|
||
eq(expected, exec_lua [[
|
||
local doc_syms = {
|
||
{
|
||
deprecated = false,
|
||
detail = "A",
|
||
kind = 1,
|
||
name = "TestA",
|
||
range = {
|
||
start = {
|
||
character = 0,
|
||
line = 1
|
||
},
|
||
["end"] = {
|
||
character = 0,
|
||
line = 2
|
||
}
|
||
},
|
||
selectionRange = {
|
||
start = {
|
||
character = 0,
|
||
line = 1
|
||
},
|
||
["end"] = {
|
||
character = 4,
|
||
line = 1
|
||
}
|
||
},
|
||
children = {
|
||
{
|
||
children = {},
|
||
deprecated = false,
|
||
detail = "B",
|
||
kind = 2,
|
||
name = "TestB",
|
||
range = {
|
||
start = {
|
||
character = 0,
|
||
line = 3
|
||
},
|
||
["end"] = {
|
||
character = 0,
|
||
line = 4
|
||
}
|
||
},
|
||
selectionRange = {
|
||
start = {
|
||
character = 0,
|
||
line = 3
|
||
},
|
||
["end"] = {
|
||
character = 4,
|
||
line = 3
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
deprecated = false,
|
||
detail = "C",
|
||
kind = 3,
|
||
name = "TestC",
|
||
range = {
|
||
start = {
|
||
character = 0,
|
||
line = 5
|
||
},
|
||
["end"] = {
|
||
character = 0,
|
||
line = 6
|
||
}
|
||
},
|
||
selectionRange = {
|
||
start = {
|
||
character = 0,
|
||
line = 5
|
||
},
|
||
["end"] = {
|
||
character = 4,
|
||
line = 5
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return vim.lsp.util.symbols_to_items(doc_syms, nil)
|
||
]])
|
||
end)
|
||
it('DocumentSymbol has no children', function()
|
||
local expected = {
|
||
{
|
||
col = 1,
|
||
filename = '',
|
||
kind = 'File',
|
||
lnum = 2,
|
||
text = '[File] TestA'
|
||
},
|
||
{
|
||
col = 1,
|
||
filename = '',
|
||
kind = 'Namespace',
|
||
lnum = 6,
|
||
text = '[Namespace] TestC'
|
||
}
|
||
}
|
||
eq(expected, exec_lua [[
|
||
local doc_syms = {
|
||
{
|
||
deprecated = false,
|
||
detail = "A",
|
||
kind = 1,
|
||
name = "TestA",
|
||
range = {
|
||
start = {
|
||
character = 0,
|
||
line = 1
|
||
},
|
||
["end"] = {
|
||
character = 0,
|
||
line = 2
|
||
}
|
||
},
|
||
selectionRange = {
|
||
start = {
|
||
character = 0,
|
||
line = 1
|
||
},
|
||
["end"] = {
|
||
character = 4,
|
||
line = 1
|
||
}
|
||
},
|
||
},
|
||
{
|
||
deprecated = false,
|
||
detail = "C",
|
||
kind = 3,
|
||
name = "TestC",
|
||
range = {
|
||
start = {
|
||
character = 0,
|
||
line = 5
|
||
},
|
||
["end"] = {
|
||
character = 0,
|
||
line = 6
|
||
}
|
||
},
|
||
selectionRange = {
|
||
start = {
|
||
character = 0,
|
||
line = 5
|
||
},
|
||
["end"] = {
|
||
character = 4,
|
||
line = 5
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return vim.lsp.util.symbols_to_items(doc_syms, nil)
|
||
]])
|
||
end)
|
||
end)
|
||
it('convert SymbolInformation[] to items', function()
|
||
local expected = {
|
||
{
|
||
col = 1,
|
||
filename = '/test_a',
|
||
kind = 'File',
|
||
lnum = 2,
|
||
text = '[File] TestA'
|
||
},
|
||
{
|
||
col = 1,
|
||
filename = '/test_b',
|
||
kind = 'Module',
|
||
lnum = 4,
|
||
text = '[Module] TestB'
|
||
}
|
||
}
|
||
eq(expected, exec_lua [[
|
||
local sym_info = {
|
||
{
|
||
deprecated = false,
|
||
kind = 1,
|
||
name = "TestA",
|
||
location = {
|
||
range = {
|
||
start = {
|
||
character = 0,
|
||
line = 1
|
||
},
|
||
["end"] = {
|
||
character = 0,
|
||
line = 2
|
||
}
|
||
},
|
||
uri = "file:///test_a"
|
||
},
|
||
contanerName = "TestAContainer"
|
||
},
|
||
{
|
||
deprecated = false,
|
||
kind = 2,
|
||
name = "TestB",
|
||
location = {
|
||
range = {
|
||
start = {
|
||
character = 0,
|
||
line = 3
|
||
},
|
||
["end"] = {
|
||
character = 0,
|
||
line = 4
|
||
}
|
||
},
|
||
uri = "file:///test_b"
|
||
},
|
||
contanerName = "TestBContainer"
|
||
}
|
||
}
|
||
return vim.lsp.util.symbols_to_items(sym_info, nil)
|
||
]])
|
||
end)
|
||
end)
|
||
|
||
describe('lsp.util._get_completion_item_kind_name', function()
|
||
it('returns the name specified by protocol', function()
|
||
eq("Text", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1)"))
|
||
eq("TypeParameter", exec_lua("return vim.lsp.util._get_completion_item_kind_name(25)"))
|
||
end)
|
||
it('returns the name not specified by protocol', function()
|
||
eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(nil)"))
|
||
eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(vim.NIL)"))
|
||
eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1000)"))
|
||
end)
|
||
end)
|
||
|
||
describe('lsp.util._get_symbol_kind_name', function()
|
||
it('returns the name specified by protocol', function()
|
||
eq("File", exec_lua("return vim.lsp.util._get_symbol_kind_name(1)"))
|
||
eq("TypeParameter", exec_lua("return vim.lsp.util._get_symbol_kind_name(26)"))
|
||
end)
|
||
it('returns the name not specified by protocol', function()
|
||
eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(nil)"))
|
||
eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(vim.NIL)"))
|
||
eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(1000)"))
|
||
end)
|
||
end)
|
||
|
||
describe('lsp.util.jump_to_location', function()
|
||
local target_bufnr
|
||
|
||
before_each(function()
|
||
target_bufnr = exec_lua [[
|
||
local bufnr = vim.uri_to_bufnr("file:///fake/uri")
|
||
local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"}
|
||
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
|
||
return bufnr
|
||
]]
|
||
end)
|
||
|
||
local location = function(start_line, start_char, end_line, end_char)
|
||
return {
|
||
uri = "file:///fake/uri",
|
||
range = {
|
||
start = { line = start_line, character = start_char },
|
||
["end"] = { line = end_line, character = end_char },
|
||
},
|
||
}
|
||
end
|
||
|
||
local jump = function(msg)
|
||
eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg))
|
||
eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]])
|
||
return {
|
||
line = exec_lua[[return vim.fn.line('.')]],
|
||
col = exec_lua[[return vim.fn.col('.')]],
|
||
}
|
||
end
|
||
|
||
it('jumps to a Location', function()
|
||
local pos = jump(location(0, 9, 0, 9))
|
||
eq(1, pos.line)
|
||
eq(10, pos.col)
|
||
end)
|
||
|
||
it('jumps to a LocationLink', function()
|
||
local pos = jump({
|
||
targetUri = "file:///fake/uri",
|
||
targetSelectionRange = {
|
||
start = { line = 0, character = 4 },
|
||
["end"] = { line = 0, character = 4 },
|
||
},
|
||
targetRange = {
|
||
start = { line = 1, character = 5 },
|
||
["end"] = { line = 1, character = 5 },
|
||
},
|
||
})
|
||
eq(1, pos.line)
|
||
eq(5, pos.col)
|
||
end)
|
||
|
||
it('jumps to the correct multibyte column', function()
|
||
local pos = jump(location(1, 2, 1, 2))
|
||
eq(2, pos.line)
|
||
eq(4, pos.col)
|
||
eq('å', exec_lua[[return vim.fn.expand('<cword>')]])
|
||
end)
|
||
|
||
it('adds current position to jumplist before jumping', function()
|
||
funcs.nvim_win_set_buf(0, target_bufnr)
|
||
local mark = funcs.nvim_buf_get_mark(target_bufnr, "'")
|
||
eq({ 1, 0 }, mark)
|
||
|
||
funcs.nvim_win_set_cursor(0, {2, 3})
|
||
jump(location(0, 9, 0, 9))
|
||
|
||
mark = funcs.nvim_buf_get_mark(target_bufnr, "'")
|
||
eq({ 2, 3 }, mark)
|
||
end)
|
||
end)
|
||
|
||
describe('lsp.util._make_floating_popup_size', function()
|
||
before_each(function()
|
||
exec_lua [[ contents =
|
||
{"text tαxt txtα tex",
|
||
"text tααt tααt text",
|
||
"text tαxt tαxt"}
|
||
]]
|
||
end)
|
||
|
||
it('calculates size correctly', function()
|
||
eq({19,3}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents)} ]])
|
||
end)
|
||
|
||
it('calculates size correctly with wrapping', function()
|
||
eq({15,5}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents,{width = 15, wrap_at = 14})} ]])
|
||
end)
|
||
end)
|
||
|
||
describe('lsp.util.trim.trim_empty_lines', function()
|
||
it('properly trims empty lines', function()
|
||
eq({{"foo", "bar"}}, exec_lua[[ return vim.lsp.util.trim_empty_lines({{ "foo", "bar" }, nil}) ]])
|
||
end)
|
||
end)
|
||
|
||
describe('lsp.util.get_effective_tabstop', function()
|
||
local function test_tabstop(tabsize, softtabstop)
|
||
exec_lua(string.format([[
|
||
vim.api.nvim_buf_set_option(0, 'softtabstop', %d)
|
||
vim.api.nvim_buf_set_option(0, 'tabstop', 2)
|
||
vim.api.nvim_buf_set_option(0, 'shiftwidth', 3)
|
||
]], softtabstop))
|
||
eq(tabsize, exec_lua('return vim.lsp.util.get_effective_tabstop()'))
|
||
end
|
||
|
||
it('with softtabstop = 1', function() test_tabstop(1, 1) end)
|
||
it('with softtabstop = 0', function() test_tabstop(2, 0) end)
|
||
it('with softtabstop = -1', function() test_tabstop(3, -1) end)
|
||
end)
|
||
|
||
describe('vim.lsp.buf.outgoing_calls', function()
|
||
it('does nothing for an empty response', function()
|
||
local qflist_count = exec_lua([=[
|
||
require'vim.lsp.handlers'['callHierarchy/outgoingCalls'](nil, nil, {}, nil)
|
||
return #vim.fn.getqflist()
|
||
]=])
|
||
eq(0, qflist_count)
|
||
end)
|
||
|
||
it('opens the quickfix list with the right caller', function()
|
||
local qflist = exec_lua([=[
|
||
local rust_analyzer_response = { {
|
||
fromRanges = { {
|
||
['end'] = {
|
||
character = 7,
|
||
line = 3
|
||
},
|
||
start = {
|
||
character = 4,
|
||
line = 3
|
||
}
|
||
} },
|
||
to = {
|
||
detail = "fn foo()",
|
||
kind = 12,
|
||
name = "foo",
|
||
range = {
|
||
['end'] = {
|
||
character = 11,
|
||
line = 0
|
||
},
|
||
start = {
|
||
character = 0,
|
||
line = 0
|
||
}
|
||
},
|
||
selectionRange = {
|
||
['end'] = {
|
||
character = 6,
|
||
line = 0
|
||
},
|
||
start = {
|
||
character = 3,
|
||
line = 0
|
||
}
|
||
},
|
||
uri = "file:///src/main.rs"
|
||
}
|
||
} }
|
||
local handler = require'vim.lsp.handlers'['callHierarchy/outgoingCalls']
|
||
handler(nil, rust_analyzer_response, {})
|
||
return vim.fn.getqflist()
|
||
]=])
|
||
|
||
local expected = { {
|
||
bufnr = 2,
|
||
col = 5,
|
||
end_col = 0,
|
||
lnum = 4,
|
||
end_lnum = 0,
|
||
module = "",
|
||
nr = 0,
|
||
pattern = "",
|
||
text = "foo",
|
||
type = "",
|
||
valid = 1,
|
||
vcol = 0
|
||
} }
|
||
|
||
eq(expected, qflist)
|
||
end)
|
||
end)
|
||
|
||
describe('vim.lsp.buf.incoming_calls', function()
|
||
it('does nothing for an empty response', function()
|
||
local qflist_count = exec_lua([=[
|
||
require'vim.lsp.handlers'['callHierarchy/incomingCalls'](nil, nil, {})
|
||
return #vim.fn.getqflist()
|
||
]=])
|
||
eq(0, qflist_count)
|
||
end)
|
||
|
||
it('opens the quickfix list with the right callee', function()
|
||
local qflist = exec_lua([=[
|
||
local rust_analyzer_response = { {
|
||
from = {
|
||
detail = "fn main()",
|
||
kind = 12,
|
||
name = "main",
|
||
range = {
|
||
['end'] = {
|
||
character = 1,
|
||
line = 4
|
||
},
|
||
start = {
|
||
character = 0,
|
||
line = 2
|
||
}
|
||
},
|
||
selectionRange = {
|
||
['end'] = {
|
||
character = 7,
|
||
line = 2
|
||
},
|
||
start = {
|
||
character = 3,
|
||
line = 2
|
||
}
|
||
},
|
||
uri = "file:///src/main.rs"
|
||
},
|
||
fromRanges = { {
|
||
['end'] = {
|
||
character = 7,
|
||
line = 3
|
||
},
|
||
start = {
|
||
character = 4,
|
||
line = 3
|
||
}
|
||
} }
|
||
} }
|
||
|
||
local handler = require'vim.lsp.handlers'['callHierarchy/incomingCalls']
|
||
handler(nil, rust_analyzer_response, {})
|
||
return vim.fn.getqflist()
|
||
]=])
|
||
|
||
local expected = { {
|
||
bufnr = 2,
|
||
col = 5,
|
||
end_col = 0,
|
||
lnum = 4,
|
||
end_lnum = 0,
|
||
module = "",
|
||
nr = 0,
|
||
pattern = "",
|
||
text = "main",
|
||
type = "",
|
||
valid = 1,
|
||
vcol = 0
|
||
} }
|
||
|
||
eq(expected, qflist)
|
||
end)
|
||
end)
|
||
|
||
describe('vim.lsp.buf.rename', function()
|
||
for _, test in ipairs({
|
||
{
|
||
it = "does not attempt to rename on nil response",
|
||
name = "prepare_rename_nil",
|
||
expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
},
|
||
},
|
||
{
|
||
it = "handles prepareRename placeholder response",
|
||
name = "prepare_rename_placeholder",
|
||
expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
},
|
||
expected_text = "placeholder", -- see fake lsp response
|
||
},
|
||
{
|
||
it = "handles range response",
|
||
name = "prepare_rename_range",
|
||
expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
},
|
||
expected_text = "line", -- see test case and fake lsp response
|
||
},
|
||
{
|
||
it = "handles error",
|
||
name = "prepare_rename_error",
|
||
expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
},
|
||
expected_text = "two", -- see test case
|
||
},
|
||
}) do
|
||
it(test.it, function()
|
||
local client
|
||
test_rpc_server {
|
||
test_name = test.name;
|
||
on_init = function(_client)
|
||
client = _client
|
||
eq(true, client.resolved_capabilities().rename)
|
||
end;
|
||
on_setup = function()
|
||
exec_lua([=[
|
||
local bufnr = vim.api.nvim_get_current_buf()
|
||
lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
|
||
vim.lsp._stubs = {}
|
||
vim.fn.input = function(opts, on_confirm)
|
||
vim.lsp._stubs.input_prompt = opts.prompt
|
||
vim.lsp._stubs.input_text = opts.default
|
||
return 'renameto' -- expect this value in fake lsp
|
||
end
|
||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'', 'this is line two'})
|
||
vim.fn.cursor(2, 13) -- the space between "line" and "two"
|
||
]=])
|
||
end;
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end;
|
||
on_handler = function(err, result, ctx)
|
||
-- Don't compare & assert params, they're not relevant for the testcase
|
||
-- This allows us to be lazy and avoid declaring them
|
||
ctx.params = nil
|
||
|
||
eq(table.remove(test.expected_handlers), {err, result, ctx}, "expected handler")
|
||
if ctx.method == 'start' then
|
||
exec_lua("vim.lsp.buf.rename()")
|
||
end
|
||
if ctx.method == 'shutdown' then
|
||
if test.expected_text then
|
||
eq("New Name: ", exec_lua("return vim.lsp._stubs.input_prompt"))
|
||
eq(test.expected_text, exec_lua("return vim.lsp._stubs.input_text"))
|
||
end
|
||
client.stop()
|
||
end
|
||
end;
|
||
}
|
||
end)
|
||
end
|
||
end)
|
||
|
||
describe('vim.lsp.buf.code_action', function()
|
||
it('Calls client side command if available', function()
|
||
local client
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
test_rpc_server {
|
||
test_name = 'code_action_with_resolve',
|
||
on_init = function(client_)
|
||
client = client_
|
||
end,
|
||
on_setup = function()
|
||
end,
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end,
|
||
on_handler = function(err, result, ctx)
|
||
eq(table.remove(expected_handlers), {err, result, ctx})
|
||
if ctx.method == 'start' then
|
||
exec_lua([[
|
||
vim.lsp.commands['dummy1'] = function(cmd)
|
||
vim.lsp.commands['dummy2'] = function()
|
||
end
|
||
end
|
||
local bufnr = vim.api.nvim_get_current_buf()
|
||
vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
|
||
vim.fn.inputlist = function()
|
||
return 1
|
||
end
|
||
vim.lsp.buf.code_action()
|
||
]])
|
||
elseif ctx.method == 'shutdown' then
|
||
eq('function', exec_lua[[return type(vim.lsp.commands['dummy2'])]])
|
||
client.stop()
|
||
end
|
||
end
|
||
}
|
||
end)
|
||
end)
|
||
describe('vim.lsp.commands', function()
|
||
it('Accepts only string keys', function()
|
||
matches(
|
||
'.*The key for commands in `vim.lsp.commands` must be a string',
|
||
pcall_err(exec_lua, 'vim.lsp.commands[1] = function() end')
|
||
)
|
||
end)
|
||
it('Accepts only function values', function()
|
||
matches(
|
||
'.*Command added to `vim.lsp.commands` must be a function',
|
||
pcall_err(exec_lua, 'vim.lsp.commands.dummy = 10')
|
||
)
|
||
end)
|
||
end)
|
||
describe('vim.lsp.codelens', function()
|
||
it('uses client commands', function()
|
||
local client
|
||
local expected_handlers = {
|
||
{NIL, {}, {method="shutdown", client_id=1}};
|
||
{NIL, {}, {method="start", client_id=1}};
|
||
}
|
||
test_rpc_server {
|
||
test_name = 'clientside_commands',
|
||
on_init = function(client_)
|
||
client = client_
|
||
end,
|
||
on_setup = function()
|
||
end,
|
||
on_exit = function(code, signal)
|
||
eq(0, code, "exit code", fake_lsp_logfile)
|
||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||
end,
|
||
on_handler = function(err, result, ctx)
|
||
eq(table.remove(expected_handlers), {err, result, ctx})
|
||
if ctx.method == 'start' then
|
||
local fake_uri = "file:///fake/uri"
|
||
local cmd = exec_lua([[
|
||
fake_uri = ...
|
||
local bufnr = vim.uri_to_bufnr(fake_uri)
|
||
vim.fn.bufload(bufnr)
|
||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'One line'})
|
||
local lenses = {
|
||
{
|
||
range = {
|
||
start = { line = 0, character = 0, },
|
||
['end'] = { line = 0, character = 8 }
|
||
},
|
||
command = { title = 'Lens1', command = 'Dummy' }
|
||
},
|
||
}
|
||
vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr})
|
||
local cmd_called = nil
|
||
vim.lsp.commands['Dummy'] = function(command)
|
||
cmd_called = command
|
||
end
|
||
vim.api.nvim_set_current_buf(bufnr)
|
||
vim.lsp.codelens.run()
|
||
return cmd_called
|
||
]], fake_uri)
|
||
eq({ command = 'Dummy', title = 'Lens1' }, cmd)
|
||
elseif ctx.method == 'shutdown' then
|
||
client.stop()
|
||
end
|
||
end
|
||
}
|
||
end)
|
||
end)
|
||
end)
|