2024-04-20 17:44:13 +02:00
local t = require ( 'test.testutil' )
local n = require ( 'test.functional.testnvim' )()
2024-04-08 11:03:20 +02:00
local t_lsp = require ( 'test.functional.plugin.lsp.testutil' )
local assert_log = t.assert_log
2024-04-20 17:44:13 +02:00
local buf_lines = n.buf_lines
local clear = n.clear
local command = n.command
2024-04-08 11:03:20 +02:00
local dedent = t.dedent
2024-04-20 17:44:13 +02:00
local exec_lua = n.exec_lua
2024-04-08 11:03:20 +02:00
local eq = t.eq
2024-04-20 17:44:13 +02:00
local eval = n.eval
2024-04-08 11:03:20 +02:00
local matches = t.matches
local pcall_err = t.pcall_err
2024-01-12 11:28:20 +00:00
local pesc = vim.pesc
2024-04-20 17:44:13 +02:00
local insert = n.insert
local fn = n.fn
2024-04-08 11:03:20 +02:00
local retry = t.retry
2024-04-20 17:44:13 +02:00
local stop = n.stop
2024-01-12 11:28:20 +00:00
local NIL = vim.NIL
2024-04-08 11:03:20 +02:00
local read_file = t.read_file
local write_file = t.write_file
local is_ci = t.is_ci
2024-04-20 17:44:13 +02:00
local api = n.api
2024-04-08 11:03:20 +02:00
local is_os = t.is_os
local skip = t.skip
local mkdir = t.mkdir
local tmpname = t.tmpname
local clear_notrace = t_lsp.clear_notrace
local create_server_definition = t_lsp.create_server_definition
local fake_lsp_code = t_lsp.fake_lsp_code
local fake_lsp_logfile = t_lsp.fake_lsp_logfile
local test_rpc_server = t_lsp.test_rpc_server
2019-11-13 12:55:26 -08:00
2023-03-09 15:12:56 +01:00
local function get_buf_option ( name , bufnr )
bufnr = bufnr or 'BUFFER'
2022-12-19 16:37:45 +00:00
return exec_lua (
string.format ( "return vim.api.nvim_get_option_value('%s', { buf = %s })" , name , bufnr )
)
2023-03-09 15:12:56 +01:00
end
2020-02-08 18:08:02 -08:00
-- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837
2022-11-22 01:13:30 +01:00
if skip ( is_os ( 'win' )) then
return
end
2019-11-13 12:55:26 -08:00
2020-02-08 18:08:02 -08:00
teardown ( function ()
os.remove ( fake_lsp_logfile )
end )
2019-11-13 12:55:26 -08:00
2019-12-31 07:51:54 -08:00
describe ( 'LSP' , function ()
2021-09-26 14:21:40 -07:00
before_each ( function ()
clear_notrace ()
2019-11-13 12:55:26 -08:00
2021-09-26 14:21:40 -07:00
-- 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')
2023-01-31 13:08:46 +01:00
local test_name, fake_lsp_code, fake_lsp_logfile = ...
2021-09-26 14:21:40 -07:00
function test__start_client()
return lsp.start_client {
cmd_env = {
2023-01-31 13:08:46 +01:00
NVIM_LOG_FILE = fake_lsp_logfile;
2023-03-10 17:30:40 +01:00
NVIM_APPNAME = "nvim_lsp_test";
2021-09-26 14:21:40 -07:00
};
cmd = {
2023-01-31 13:08:46 +01:00
vim.v.progpath, '-l', fake_lsp_code, test_name;
2021-09-26 14:21:40 -07:00
};
2021-11-21 11:39:30 -05:00
workspace_folders = {{
2023-06-03 11:06:00 +01:00
uri = 'file://' .. vim.uv.cwd(),
2021-11-21 11:39:30 -05:00
name = 'test_folder',
}};
2021-09-26 14:21:40 -07:00
}
end
TEST_CLIENT1 = test__start_client()
]=] ,
test_name ,
fake_lsp_code ,
fake_lsp_logfile
)
end )
2019-11-13 12:55:26 -08:00
2021-09-26 14:21:40 -07:00
after_each ( function ()
2024-04-02 15:45:19 +02:00
api.nvim_exec_autocmds ( 'VimLeavePre' , { modeline = false })
2021-09-26 14:21:40 -07:00
-- exec_lua("lsp.stop_all_clients(true)")
end )
describe ( 'server_name specified' , function ()
2019-12-31 07:51:54 -08:00
it ( 'start_client(), stop_client()' , function ()
retry ( nil , 4000 , function ()
2023-07-17 18:27:16 +02:00
eq ( 1 , exec_lua ( 'return #lsp.get_clients()' ))
2019-12-31 07:51:54 -08:00
end )
eq (
2 ,
exec_lua ( [[
TEST_CLIENT2 = test__start_client()
return TEST_CLIENT2
]] )
2024-01-03 02:09:18 +01:00
)
2019-12-31 07:51:54 -08:00
eq (
3 ,
exec_lua ( [[
TEST_CLIENT3 = test__start_client()
return TEST_CLIENT3
]] )
2024-01-03 02:09:18 +01:00
)
2019-12-31 07:51:54 -08:00
retry ( nil , 4000 , function ()
2023-07-17 18:27:16 +02:00
eq ( 3 , exec_lua ( 'return #lsp.get_clients()' ))
2019-12-31 07:51:54 -08:00
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 ()
2023-07-17 18:27:16 +02:00
eq ( 2 , exec_lua ( 'return #lsp.get_clients()' ))
2019-12-31 07:51:54 -08:00
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 ()
2023-07-17 18:27:16 +02:00
eq ( 0 , exec_lua ( 'return #lsp.get_clients()' ))
2019-12-31 07:51:54 -08:00
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 ()
2023-07-17 18:27:16 +02:00
eq ( 3 , exec_lua ( 'return #lsp.get_clients()' ))
2019-12-31 07:51:54 -08:00
end )
-- Stop all clients.
2023-07-17 18:27:16 +02:00
exec_lua ( 'lsp.stop_client(lsp.get_clients())' )
2019-12-31 07:51:54 -08:00
retry ( nil , 4000 , function ()
2023-07-17 18:27:16 +02:00
eq ( 0 , exec_lua ( 'return #lsp.get_clients()' ))
2019-11-13 12:55:26 -08:00
end )
end )
end )
2022-05-22 14:06:21 +02:00
end )
describe ( 'LSP' , function ()
2019-11-13 12:55:26 -08:00
describe ( 'basic_init test' , function ()
2021-01-27 15:32:07 +01:00
after_each ( function ()
stop ()
2023-07-17 18:27:16 +02:00
exec_lua ( 'lsp.stop_client(lsp.get_clients(), true)' )
2024-04-02 15:45:19 +02:00
api.nvim_exec_autocmds ( 'VimLeavePre' , { modeline = false })
2021-01-27 15:32:07 +01:00
end )
2019-11-13 12:55:26 -08:00
it ( 'should run correctly' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'test' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
test_rpc_server {
test_name = 'basic_init' ,
2021-02-23 00:02:51 +01:00
on_init = function ( client , _ )
2019-11-13 12:55:26 -08:00
-- 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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
-- Note that NIL must be used here.
2021-06-22 10:35:36 +02:00
-- on_handler(err, method, result, client_id)
on_handler = function (...)
eq ( table.remove ( expected_handlers ), { ... })
2019-11-13 12:55:26 -08:00
end ,
}
end )
it ( 'should fail' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'test' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
test_rpc_server {
test_name = 'basic_init' ,
on_init = function ( client )
client.notify ( 'test' )
client.stop ()
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 101 , code , 'exit code' ) -- See fake-lsp-server.lua
eq ( 0 , signal , 'exit signal' )
2020-02-16 19:02:09 -08:00
assert_log (
pesc ( [[assert_eq failed: left == "\"shutdown\"", right == "\"test\""]] ),
fake_lsp_logfile
)
2019-11-13 12:55:26 -08:00
end ,
2021-06-22 10:35:36 +02:00
on_handler = function (...)
2021-06-28 23:02:17 +02:00
eq ( table.remove ( expected_handlers ), { ... }, 'expected handler' )
2019-11-13 12:55:26 -08:00
end ,
}
end )
2022-06-03 18:16:11 +02:00
it ( 'should send didChangeConfiguration after initialize if there are settings' , function ()
test_rpc_server ({
test_name = 'basic_init_did_change_configuration' ,
on_init = function ( client , _ )
client.stop ()
end ,
on_exit = function ( code , signal )
2024-02-07 11:24:44 +00:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2022-06-03 18:16:11 +02:00
end ,
settings = {
dummy = 1 ,
},
})
end )
2023-06-01 11:15:33 -05:00
it (
"should set the client's offset_encoding when positionEncoding capability is supported" ,
function ()
clear ()
exec_lua ( create_server_definition )
local result = exec_lua ( [[
local server = _create_server({
capabilities = {
positionEncoding = "utf-8"
},
})
local client_id = vim.lsp.start({
name = 'dummy',
cmd = server.cmd,
})
if not client_id then
return 'vim.lsp.start did not return client_id'
end
local client = vim.lsp.get_client_by_id(client_id)
if not client then
return 'No client found with id ' .. client_id
end
return client.offset_encoding
]] )
eq ( 'utf-8' , result )
end
)
2019-11-13 12:55:26 -08:00
it ( 'should succeed with manual shutdown' , function ()
2022-11-22 01:13:30 +01:00
if is_ci () then
2021-07-23 18:33:07 +02:00
pending ( 'hangs the build on CI #14028, re-enable with freeze timeout #14204' )
2021-04-03 10:04:14 +02:00
return
2024-04-08 11:03:20 +02:00
elseif t.skip_fragile ( pending ) then
2021-08-13 23:53:55 -04:00
return
2021-04-03 10:04:14 +02:00
end
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2024-06-14 05:03:58 -04:00
{ NIL , {}, { method = 'shutdown' , bufnr = 1 , client_id = 1 , version = 0 } },
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'test' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
test_rpc_server {
test_name = 'basic_init' ,
on_init = function ( client )
2022-04-30 02:22:30 -07:00
eq ( 0 , client.server_capabilities (). textDocumentSync.change )
2019-11-13 12:55:26 -08:00
client.request ( 'shutdown' )
client.notify ( 'exit' )
2021-01-04 19:04:21 +01:00
client.stop ()
2019-11-13 12:55:26 -08:00
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-06-22 10:35:36 +02:00
on_handler = function (...)
2021-06-28 23:02:17 +02:00
eq ( table.remove ( expected_handlers ), { ... }, 'expected handler' )
2019-11-13 12:55:26 -08:00
end ,
}
end )
2021-12-21 10:53:34 -08:00
it ( 'should detach buffer in response to nvim_buf_detach' , function ()
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-12-21 10:53:34 -08:00
test_rpc_server {
test_name = 'basic_finish' ,
2024-03-21 15:15:20 +00:00
on_setup = function ()
2021-12-21 10:53:34 -08:00
exec_lua [[
BUFFER = vim.api.nvim_create_buf(false, true)
]]
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")
]]
2024-03-21 15:15:20 +00:00
end ,
on_init = function ( _client )
client = _client
2021-12-21 10:53:34 -08:00
client.notify ( 'finish' )
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-12-21 10:53:34 -08:00
end ,
on_handler = function ( err , result , ctx )
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
exec_lua ( 'return lsp.buf_detach_client(BUFFER, TEST_RPC_CLIENT_ID)' )
eq ( false , exec_lua ( 'return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)' ))
client.stop ()
end
end ,
}
end )
2022-05-09 12:00:27 -06:00
it ( 'should fire autocommands on attach and detach' , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-05-09 12:00:27 -06:00
test_rpc_server {
test_name = 'basic_init' ,
on_setup = function ()
exec_lua [[
BUFFER = vim.api.nvim_create_buf(false, true)
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
vim.g.lsp_attached = client.name
end,
})
vim.api.nvim_create_autocmd('LspDetach', {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
vim.g.lsp_detached = client.name
end,
})
]]
end ,
on_init = function ( _client )
client = _client
eq ( true , exec_lua ( 'return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)' ))
client.notify ( 'finish' )
end ,
on_handler = function ( _ , _ , ctx )
if ctx.method == 'finish' then
2024-01-12 17:59:57 +00:00
eq ( 'basic_init' , api.nvim_get_var ( 'lsp_attached' ))
2022-05-09 12:00:27 -06:00
exec_lua ( 'return lsp.buf_detach_client(BUFFER, TEST_RPC_CLIENT_ID)' )
2024-01-12 17:59:57 +00:00
eq ( 'basic_init' , api.nvim_get_var ( 'lsp_detached' ))
2022-05-09 12:00:27 -06:00
client.stop ()
end
end ,
}
end )
2023-03-09 15:12:56 +01:00
it ( 'should set default options on attach' , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2023-03-09 15:12:56 +01:00
test_rpc_server {
test_name = 'set_defaults_all_capabilities' ,
2023-04-11 18:37:27 +02:00
on_init = function ( _client )
client = _client
2023-03-09 15:12:56 +01:00
exec_lua [[
BUFFER = vim.api.nvim_create_buf(false, true)
2023-04-11 18:37:27 +02:00
lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
2023-03-09 15:12:56 +01:00
]]
end ,
on_handler = function ( _ , _ , ctx )
if ctx.method == 'test' then
eq ( 'v:lua.vim.lsp.tagfunc' , get_buf_option ( 'tagfunc' ))
eq ( 'v:lua.vim.lsp.omnifunc' , get_buf_option ( 'omnifunc' ))
eq ( 'v:lua.vim.lsp.formatexpr()' , get_buf_option ( 'formatexpr' ))
2023-07-14 18:47:18 +02:00
eq ( '' , get_buf_option ( 'keywordprg' ))
eq (
true ,
exec_lua [[
local keymap
vim.api.nvim_buf_call(BUFFER, function()
keymap = vim.fn.maparg("K", "n", false, true)
end)
return keymap.callback == vim.lsp.buf.hover
]]
)
2023-03-09 15:12:56 +01:00
client.stop ()
end
end ,
on_exit = function ( _ , _ )
eq ( '' , get_buf_option ( 'tagfunc' ))
eq ( '' , get_buf_option ( 'omnifunc' ))
eq ( '' , get_buf_option ( 'formatexpr' ))
2023-07-14 18:47:18 +02:00
eq (
'' ,
exec_lua [[
local keymap
vim.api.nvim_buf_call(BUFFER, function()
keymap = vim.fn.maparg("K", "n", false, false)
end)
return keymap
]]
)
2023-03-09 15:12:56 +01:00
end ,
}
end )
it ( 'should overwrite options set by ftplugins' , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2023-03-09 15:12:56 +01:00
test_rpc_server {
test_name = 'set_defaults_all_capabilities' ,
2023-04-11 18:37:27 +02:00
on_init = function ( _client )
client = _client
2023-03-09 15:12:56 +01:00
exec_lua [[
vim.api.nvim_command('filetype plugin on')
BUFFER_1 = vim.api.nvim_create_buf(false, true)
BUFFER_2 = vim.api.nvim_create_buf(false, true)
2022-12-19 16:37:45 +00:00
vim.api.nvim_set_option_value('filetype', 'man', { buf = BUFFER_1 })
vim.api.nvim_set_option_value('filetype', 'xml', { buf = BUFFER_2 })
2023-03-09 15:12:56 +01:00
]]
2023-04-11 18:37:27 +02:00
-- Sanity check to ensure that some values are set after setting filetype.
2023-03-09 15:12:56 +01:00
eq ( "v:lua.require'man'.goto_tag" , get_buf_option ( 'tagfunc' , 'BUFFER_1' ))
eq ( 'xmlcomplete#CompleteTags' , get_buf_option ( 'omnifunc' , 'BUFFER_2' ))
eq ( 'xmlformat#Format()' , get_buf_option ( 'formatexpr' , 'BUFFER_2' ))
2023-04-11 18:37:27 +02:00
exec_lua [[
lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID)
lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID)
]]
2023-03-09 15:12:56 +01:00
end ,
on_handler = function ( _ , _ , ctx )
if ctx.method == 'test' then
eq ( 'v:lua.vim.lsp.tagfunc' , get_buf_option ( 'tagfunc' , 'BUFFER_1' ))
eq ( 'v:lua.vim.lsp.omnifunc' , get_buf_option ( 'omnifunc' , 'BUFFER_2' ))
eq ( 'v:lua.vim.lsp.formatexpr()' , get_buf_option ( 'formatexpr' , 'BUFFER_2' ))
client.stop ()
end
end ,
on_exit = function ( _ , _ )
eq ( '' , get_buf_option ( 'tagfunc' , 'BUFFER_1' ))
eq ( '' , get_buf_option ( 'omnifunc' , 'BUFFER_2' ))
eq ( '' , get_buf_option ( 'formatexpr' , 'BUFFER_2' ))
end ,
}
end )
it ( 'should not overwrite user-defined options' , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2023-03-09 15:12:56 +01:00
test_rpc_server {
test_name = 'set_defaults_all_capabilities' ,
2023-04-11 18:37:27 +02:00
on_init = function ( _client )
client = _client
2023-03-09 15:12:56 +01:00
exec_lua [[
BUFFER = vim.api.nvim_create_buf(false, true)
2022-12-19 16:37:45 +00:00
vim.api.nvim_set_option_value('tagfunc', 'tfu', { buf = BUFFER })
vim.api.nvim_set_option_value('omnifunc', 'ofu', { buf = BUFFER })
vim.api.nvim_set_option_value('formatexpr', 'fex', { buf = BUFFER })
2023-04-11 18:37:27 +02:00
lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
2023-03-09 15:12:56 +01:00
]]
end ,
on_handler = function ( _ , _ , ctx )
if ctx.method == 'test' then
eq ( 'tfu' , get_buf_option ( 'tagfunc' ))
eq ( 'ofu' , get_buf_option ( 'omnifunc' ))
eq ( 'fex' , get_buf_option ( 'formatexpr' ))
client.stop ()
end
end ,
on_exit = function ( _ , _ )
eq ( 'tfu' , get_buf_option ( 'tagfunc' ))
eq ( 'ofu' , get_buf_option ( 'omnifunc' ))
eq ( 'fex' , get_buf_option ( 'formatexpr' ))
end ,
}
end )
2022-09-08 23:09:32 +08:00
it ( 'should detach buffer on bufwipe' , function ()
2022-09-13 06:01:35 +08:00
clear ()
2022-12-04 15:39:22 +01:00
exec_lua ( create_server_definition )
2022-09-08 23:09:32 +08:00
local result = exec_lua ( [[
2022-12-04 15:39:22 +01:00
local server = _create_server()
2022-09-08 23:09:32 +08:00
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_set_current_buf(bufnr)
2024-05-23 12:03:47 +02:00
local detach_called = false
vim.api.nvim_create_autocmd("LspDetach", {
callback = function()
detach_called = true
end
})
2022-12-04 15:39:22 +01:00
local client_id = vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd })
2022-09-08 23:09:32 +08:00
assert(client_id, "lsp.start must return client_id")
local client = vim.lsp.get_client_by_id(client_id)
local num_attached_before = vim.tbl_count(client.attached_buffers)
vim.api.nvim_buf_delete(bufnr, { force = true })
local num_attached_after = vim.tbl_count(client.attached_buffers)
return {
bufnr = bufnr,
client_id = client_id,
num_attached_before = num_attached_before,
num_attached_after = num_attached_after,
2024-05-23 12:03:47 +02:00
detach_called = detach_called,
2022-09-08 23:09:32 +08:00
}
]] )
eq ( true , result ~= nil , 'exec_lua must return result' )
eq ( 1 , result.num_attached_before )
eq ( 0 , result.num_attached_after )
2024-05-23 12:03:47 +02:00
eq ( true , result.detach_called )
2022-09-08 23:09:32 +08:00
end )
2024-05-23 09:17:53 -04:00
it ( 'should not re-attach buffer if it was deleted in on_init #28575' , function ()
clear ()
exec_lua ( create_server_definition )
exec_lua ( [[
local server = _create_server({
handlers = {
initialize = function(method, params, callback)
vim.schedule(function()
callback(nil, { capabilities = {} })
end)
end
}
})
local bufnr = vim.api.nvim_create_buf(false, true)
local on_init_called = false
local client_id = vim.lsp.start({
name = 'detach-dummy',
cmd = server.cmd,
on_init = function()
vim.api.nvim_buf_delete(bufnr, {})
on_init_called = true
end
})
vim.lsp.buf_attach_client(bufnr, client_id)
local ok = vim.wait(1000, function() return on_init_called end)
assert(ok, "on_init was not called")
]] )
end )
2024-05-27 11:06:03 -04:00
it ( 'should allow on_lines + nvim_buf_delete during LSP initialization #28575' , function ()
clear ()
exec_lua ( create_server_definition )
exec_lua ( [[
local initialized = false
local server = _create_server({
handlers = {
initialize = function(method, params, callback)
vim.schedule(function()
callback(nil, { capabilities = {} })
initialized = true
end)
end
}
})
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_set_current_buf(bufnr)
local client_id = vim.lsp.start({
name = 'detach-dummy',
cmd = server.cmd,
})
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {"hello"})
vim.api.nvim_buf_delete(bufnr, {})
local ok = vim.wait(1000, function() return initialized end)
assert(ok, "lsp did not initialize")
]] )
end )
2020-12-31 05:41:43 -08:00
it ( 'client should return settings via workspace/configuration handler' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{
NIL ,
{
items = {
2020-12-31 05:41:43 -08:00
{ section = 'testSetting1' },
{ section = 'testSetting2' },
2022-04-15 11:12:41 +02:00
{ section = 'test.Setting3' },
{ section = 'test.Setting4' },
2024-01-03 02:09:18 +01:00
},
},
2021-08-28 00:12:30 -04:00
{ method = 'workspace/configuration' , client_id = 1 },
2024-01-03 02:09:18 +01:00
},
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'start' , client_id = 1 } },
2020-12-31 05:41:43 -08:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2020-12-31 05:41:43 -08:00
test_rpc_server {
test_name = 'check_workspace_configuration' ,
on_init = function ( _client )
client = _client
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2020-12-31 05:41:43 -08:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'start' then
2020-12-31 05:41:43 -08:00
exec_lua ( [=[
local client = vim.lsp.get_client_by_id(TEST_RPC_CLIENT_ID)
2024-02-12 13:46:32 +00:00
client.settings = {
2020-12-31 05:41:43 -08:00
testSetting1 = true;
testSetting2 = false;
2022-04-15 11:12:41 +02:00
test = {Setting3 = 'nested' };
2020-12-31 05:41:43 -08:00
}]=] )
end
2021-08-28 00:12:30 -04:00
if ctx.method == 'workspace/configuration' then
local server_result = exec_lua (
[=[
2020-12-31 05:41:43 -08:00
local method, params = ...
2021-08-28 00:12:30 -04:00
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 )
2020-12-31 05:41:43 -08:00
end
2021-08-28 00:12:30 -04:00
if ctx.method == 'shutdown' then
2020-12-31 05:41:43 -08:00
client.stop ()
end
end ,
}
end )
2021-01-18 19:33:10 +01:00
it (
'workspace/configuration returns NIL per section if client was started without config.settings' ,
function ()
2022-05-22 14:06:21 +02:00
local result = nil
test_rpc_server {
test_name = 'basic_init' ,
on_init = function ( c )
c.stop ()
end ,
on_setup = function ()
result = exec_lua [[
local result = {
items = {
{section = 'foo'},
{section = 'bar'},
}
}
return vim.lsp.handlers['workspace/configuration'](nil, result, {client_id=TEST_RPC_CLIENT_ID})
]]
end ,
}
eq ({ NIL , NIL }, result )
2021-01-18 19:33:10 +01:00
end
)
2020-12-31 05:41:43 -08:00
2019-11-13 12:55:26 -08:00
it ( 'should verify capabilities sent' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
test_rpc_server {
test_name = 'basic_check_capabilities' ,
on_init = function ( client )
client.stop ()
2020-10-25 00:28:15 -04:00
local full_kind = exec_lua ( "return require'vim.lsp.protocol'.TextDocumentSyncKind.Full" )
2022-04-30 02:22:30 -07:00
eq ( full_kind , client.server_capabilities (). textDocumentSync.change )
eq ({ includeText = false }, client.server_capabilities (). textDocumentSync.save )
eq ( false , client.server_capabilities (). codeLensProvider )
2020-10-25 00:28:15 -04:00
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2020-10-25 00:28:15 -04:00
end ,
2021-06-22 10:35:36 +02:00
on_handler = function (...)
2021-06-28 23:02:17 +02:00
eq ( table.remove ( expected_handlers ), { ... }, 'expected handler' )
2020-10-25 00:28:15 -04:00
end ,
}
end )
2022-07-17 19:13:33 +02:00
it ( 'BufWritePost sends didSave with bool textDocumentSync.save' , function ()
2022-04-30 22:13:26 +02:00
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-04-30 22:13:26 +02:00
test_rpc_server {
test_name = 'text_document_sync_save_bool' ,
on_init = function ( c )
client = c
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2022-04-30 22:13:26 +02:00
end ,
on_handler = function ( err , result , ctx )
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'start' then
exec_lua ( [=[
BUFFER = vim.api.nvim_get_current_buf()
lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
2022-07-17 19:13:33 +02:00
vim.api.nvim_exec_autocmds('BufWritePost', { buffer = BUFFER, modeline = false })
2022-04-30 22:13:26 +02:00
]=] )
else
client.stop ()
end
end ,
}
end )
2022-12-08 10:55:01 +01:00
it ( 'BufWritePre does not send notifications if server lacks willSave capabilities' , function ()
2022-12-08 19:22:40 +08:00
clear ()
2022-12-08 10:55:01 +01:00
exec_lua ( create_server_definition )
local messages = exec_lua ( [[
local server = _create_server({
capabilities = {
textDocumentSync = {
willSave = false,
willSaveWaitUntil = false,
}
},
})
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
local buf = vim.api.nvim_get_current_buf()
vim.api.nvim_exec_autocmds('BufWritePre', { buffer = buf, modeline = false })
vim.lsp.stop_client(client_id)
return server.messages
]] )
2024-03-11 22:23:14 +08:00
eq ( 4 , # messages )
eq ( 'initialize' , messages [ 1 ]. method )
eq ( 'initialized' , messages [ 2 ]. method )
eq ( 'shutdown' , messages [ 3 ]. method )
eq ( 'exit' , messages [ 4 ]. method )
2022-12-08 10:55:01 +01:00
end )
2022-12-08 19:22:40 +08:00
2022-12-08 10:55:01 +01:00
it ( 'BufWritePre sends willSave / willSaveWaitUntil, applies textEdits' , function ()
2022-12-08 19:22:40 +08:00
clear ()
2022-12-08 10:55:01 +01:00
exec_lua ( create_server_definition )
local result = exec_lua ( [[
local server = _create_server({
capabilities = {
textDocumentSync = {
willSave = true,
willSaveWaitUntil = true,
}
},
handlers = {
2024-05-23 09:17:53 -04:00
['textDocument/willSaveWaitUntil'] = function(_, _, callback)
2022-12-08 10:55:01 +01:00
local text_edit = {
range = {
start = { line = 0, character = 0 },
['end'] = { line = 0, character = 0 },
},
newText = 'Hello'
}
2024-05-23 09:17:53 -04:00
callback(nil, { text_edit, })
2022-12-08 10:55:01 +01:00
end
},
})
local buf = vim.api.nvim_get_current_buf()
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
vim.api.nvim_exec_autocmds('BufWritePre', { buffer = buf, modeline = false })
vim.lsp.stop_client(client_id)
return {
messages = server.messages,
lines = vim.api.nvim_buf_get_lines(buf, 0, -1, true)
}
]] )
local messages = result.messages
eq ( 'textDocument/willSave' , messages [ 3 ]. method )
eq ( 'textDocument/willSaveWaitUntil' , messages [ 4 ]. method )
eq ({ 'Hello' }, result.lines )
end )
2022-08-01 22:32:53 +02:00
it ( 'saveas sends didOpen if filename changed' , function ()
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-08-01 22:32:53 +02:00
test_rpc_server ({
test_name = 'text_document_save_did_open' ,
on_init = function ( c )
client = c
end ,
on_exit = function ( code , signal )
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
end ,
on_handler = function ( err , result , ctx )
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'start' then
2024-01-12 11:28:20 +00:00
local tmpfile_old = tmpname ()
local tmpfile_new = tmpname ()
2022-08-01 22:32:53 +02:00
os.remove ( tmpfile_new )
exec_lua (
[=[
local oldname, newname = ...
BUFFER = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_name(BUFFER, oldname)
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, true, {"help me"})
lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
vim.api.nvim_buf_call(BUFFER, function() vim.cmd('saveas ' .. newname) end)
]=] ,
tmpfile_old ,
tmpfile_new
)
else
client.stop ()
end
end ,
})
end )
2022-07-17 19:13:33 +02:00
it ( 'BufWritePost sends didSave including text if server capability is set' , function ()
2022-04-30 22:13:26 +02:00
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-04-30 22:13:26 +02:00
test_rpc_server {
test_name = 'text_document_sync_save_includeText' ,
on_init = function ( c )
client = c
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2022-04-30 22:13:26 +02:00
end ,
on_handler = function ( err , result , ctx )
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'start' then
exec_lua ( [=[
BUFFER = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, true, {"help me"})
lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
2022-07-17 19:13:33 +02:00
vim.api.nvim_exec_autocmds('BufWritePost', { buffer = BUFFER, modeline = false })
2022-04-30 22:13:26 +02:00
]=] )
else
client.stop ()
end
end ,
}
end )
2020-10-25 00:28:15 -04:00
it ( 'client.supports_methods() should validate capabilities' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
2020-10-25 00:28:15 -04:00
}
test_rpc_server {
test_name = 'capabilities_for_client_supports_method' ,
on_init = function ( client )
client.stop ()
2022-04-30 02:22:30 -07:00
local expected_sync_capabilities = {
change = 1 ,
openClose = true ,
save = { includeText = false },
willSave = false ,
willSaveWaitUntil = false ,
}
eq ( expected_sync_capabilities , client.server_capabilities (). textDocumentSync )
eq ( true , client.server_capabilities (). completionProvider )
eq ( true , client.server_capabilities (). hoverProvider )
eq ( false , client.server_capabilities (). definitionProvider )
eq ( false , client.server_capabilities (). renameProvider )
eq ( true , client.server_capabilities (). codeLensProvider.resolveProvider )
2020-10-25 00:28:15 -04:00
-- 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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2020-10-25 00:28:15 -04:00
end ,
2021-06-22 10:35:36 +02:00
on_handler = function (...)
2021-06-28 23:02:17 +02:00
eq ( table.remove ( expected_handlers ), { ... }, 'expected handler' )
2020-10-25 00:28:15 -04:00
end ,
}
end )
2021-10-10 22:32:50 -07:00
it ( 'should not call unsupported_method when trying to call an unsupported method' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
2020-10-25 00:28:15 -04:00
}
test_rpc_server {
test_name = 'capabilities_for_client_supports_method' ,
on_setup = function ()
exec_lua ( [=[
2020-12-03 14:00:54 +08:00
BUFFER = vim.api.nvim_get_current_buf()
lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
2021-10-10 22:32:50 -07:00
vim.lsp.handlers['textDocument/typeDefinition'] = function() end
2024-03-21 15:15:20 +00:00
vim.cmd(BUFFER.."bwipeout")
2020-10-25 00:28:15 -04:00
]=] )
end ,
on_init = function ( client )
client.stop ()
2021-10-10 22:32:50 -07:00
exec_lua ( 'vim.lsp.buf.type_definition()' )
2020-12-03 14:00:54 +08:00
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2020-12-03 14:00:54 +08:00
end ,
2021-06-22 10:35:36 +02:00
on_handler = function (...)
2021-06-28 23:02:17 +02:00
eq ( table.remove ( expected_handlers ), { ... }, 'expected handler' )
2020-12-03 14:00:54 +08:00
end ,
}
end )
2021-10-10 22:32:50 -07:00
it (
'should not call unsupported_method when no client and trying to call an unsupported method' ,
function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
2020-12-03 14:00:54 +08:00
}
test_rpc_server {
test_name = 'capabilities_for_client_supports_method' ,
on_setup = function ()
exec_lua ( [=[
2021-10-10 22:32:50 -07:00
vim.lsp.handlers['textDocument/typeDefinition'] = function() end
2020-12-03 14:00:54 +08:00
]=] )
end ,
on_init = function ( client )
client.stop ()
2021-10-10 22:32:50 -07:00
exec_lua ( 'vim.lsp.buf.type_definition()' )
2019-11-13 12:55:26 -08:00
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-06-22 10:35:36 +02:00
on_handler = function (...)
2021-06-28 23:02:17 +02:00
eq ( table.remove ( expected_handlers ), { ... }, 'expected handler' )
2019-11-13 12:55:26 -08:00
end ,
}
end
)
2021-10-08 11:19:33 -07:00
it ( 'should not forward RequestCancelled to callback' , function ()
local expected_handlers = {
2021-10-08 14:01:55 -07:00
{ NIL , {}, { method = 'finish' , client_id = 1 } },
2021-10-08 11:19:33 -07:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-10-08 11:19:33 -07:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-10-08 11:19:33 -07:00
eq ( 0 , # expected_handlers , 'did not call expected handler' )
end ,
2021-10-08 14:01:55 -07:00
on_handler = function ( err , _ , ctx )
eq ( table.remove ( expected_handlers ), { err , {}, ctx }, 'expected handler' )
if ctx.method == 'finish' then
client.stop ()
end
2021-10-08 11:19:33 -07:00
end ,
}
end )
it ( 'should forward ContentModified to callback' , function ()
local expected_handlers = {
2021-10-08 14:01:55 -07:00
{ NIL , {}, { method = 'finish' , client_id = 1 } },
2024-06-14 05:03:58 -04:00
{
{ code = - 32801 },
NIL ,
{ method = 'error_code_test' , bufnr = 1 , client_id = 1 , version = 0 },
},
2021-10-08 11:19:33 -07:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-10-08 11:19:33 -07:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-10-08 11:19:33 -07:00
eq ( 0 , # expected_handlers , 'did not call expected handler' )
end ,
2021-10-08 14:01:55 -07:00
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 ()
2021-10-08 11:19:33 -07:00
end
2024-01-03 02:09:18 +01:00
end ,
2021-10-08 11:19:33 -07:00
}
end )
2021-10-29 07:45:01 -05:00
it ( 'should track pending requests to the language server' , function ()
local expected_handlers = {
{ NIL , {}, { method = 'finish' , client_id = 1 } },
2024-06-14 05:03:58 -04:00
{ NIL , {}, { method = 'slow_request' , bufnr = 1 , client_id = 1 , version = 0 } },
2021-10-29 07:45:01 -05:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-10-29 07:45:01 -05:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-10-29 07:45:01 -05:00
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 } },
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-10-29 07:45:01 -05:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-10-29 07:45:01 -05:00
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 } },
2024-06-14 05:03:58 -04:00
{ NIL , {}, { method = 'slow_request' , bufnr = 1 , client_id = 1 , version = 0 } },
2021-10-29 07:45:01 -05:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-10-29 07:45:01 -05:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-10-29 07:45:01 -05:00
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 } },
2024-06-14 05:03:58 -04:00
{ NIL , {}, { method = 'slow_request' , bufnr = 1 , client_id = 1 , version = 0 } },
2021-10-29 07:45:01 -05:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-10-29 07:45:01 -05:00
test_rpc_server {
test_name = 'check_tracked_requests_cleared' ,
on_init = function ( _client )
command ( 'let g:requests = 0' )
2023-05-30 13:56:29 -05:00
command ( 'autocmd LspRequest * let g:requests+=1' )
2021-10-29 07:45:01 -05:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-10-29 07:45:01 -05:00
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 )
2019-11-13 12:55:26 -08:00
it ( 'should not send didOpen if the buffer closes before init' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2019-11-13 12:55:26 -08:00
test_rpc_server {
test_name = 'basic_finish' ,
2024-03-21 15:15:20 +00:00
on_setup = function ()
2019-11-13 12:55:26 -08:00
exec_lua [[
BUFFER = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
"testing";
"123";
})
2024-03-21 15:15:20 +00:00
assert(TEST_RPC_CLIENT_ID == 1)
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
assert(lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID))
vim.cmd(BUFFER.."bwipeout")
2019-11-13 12:55:26 -08:00
]]
2024-03-21 15:15:20 +00:00
end ,
on_init = function ( _client )
client = _client
2019-11-13 12:55:26 -08:00
local full_kind = exec_lua ( "return require'vim.lsp.protocol'.TextDocumentSyncKind.Full" )
2022-04-30 02:22:30 -07:00
eq ( full_kind , client.server_capabilities (). textDocumentSync.change )
eq ( true , client.server_capabilities (). textDocumentSync.openClose )
2019-11-13 12:55:26 -08:00
client.notify ( 'finish' )
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
2019-11-13 12:55:26 -08:00
client.stop ()
end
end ,
}
end )
it ( 'should check the body sent attaching before init' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2019-11-13 12:55:26 -08:00
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" )
2022-04-30 02:22:30 -07:00
eq ( full_kind , client.server_capabilities (). textDocumentSync.change )
eq ( true , client.server_capabilities (). textDocumentSync.openClose )
2019-11-13 12:55:26 -08:00
exec_lua [[
2023-06-20 15:17:13 -04:00
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Already attached, returns true")
2019-11-13 12:55:26 -08:00
]]
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
if ctx.method == 'start' then
2019-11-13 12:55:26 -08:00
client.notify ( 'finish' )
end
2021-08-28 00:12:30 -04:00
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
2019-11-13 12:55:26 -08:00
client.stop ()
end
end ,
}
end )
it ( 'should check the body sent attaching after init' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2019-11-13 12:55:26 -08:00
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" )
2022-04-30 02:22:30 -07:00
eq ( full_kind , client.server_capabilities (). textDocumentSync.change )
eq ( true , client.server_capabilities (). textDocumentSync.openClose )
2019-11-13 12:55:26 -08:00
exec_lua [[
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
]]
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
if ctx.method == 'start' then
2019-11-13 12:55:26 -08:00
client.notify ( 'finish' )
end
2021-08-28 00:12:30 -04:00
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
2019-11-13 12:55:26 -08:00
client.stop ()
end
end ,
}
end )
it ( 'should check the body and didChange full' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2019-11-13 12:55:26 -08:00
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" )
2022-04-30 02:22:30 -07:00
eq ( full_kind , client.server_capabilities (). textDocumentSync.change )
eq ( true , client.server_capabilities (). textDocumentSync.openClose )
2019-11-13 12:55:26 -08:00
exec_lua [[
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
]]
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
if ctx.method == 'start' then
2019-11-21 10:29:54 +01:00
exec_lua [[
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
"boop";
})
]]
client.notify ( 'finish' )
end
2021-08-28 00:12:30 -04:00
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
2019-11-21 10:29:54 +01:00
client.stop ()
end
end ,
}
end )
it ( 'should check the body and didChange full with noeol' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
2019-11-21 10:29:54 +01:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2019-11-21 10:29:54 +01:00
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";
})
2022-12-19 16:37:45 +00:00
vim.bo[BUFFER].eol = false
2019-11-21 10:29:54 +01:00
]]
end ,
on_init = function ( _client )
client = _client
local full_kind = exec_lua ( "return require'vim.lsp.protocol'.TextDocumentSyncKind.Full" )
2022-04-30 02:22:30 -07:00
eq ( full_kind , client.server_capabilities (). textDocumentSync.change )
eq ( true , client.server_capabilities (). textDocumentSync.openClose )
2019-11-21 10:29:54 +01:00
exec_lua [[
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
]]
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-21 10:29:54 +01:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
if ctx.method == 'start' then
2019-11-13 12:55:26 -08:00
exec_lua [[
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
"boop";
})
]]
client.notify ( 'finish' )
end
2021-08-28 00:12:30 -04:00
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
2019-11-13 12:55:26 -08:00
client.stop ()
end
end ,
}
end )
2023-09-21 14:06:40 +04:00
it ( 'should send correct range for inlay hints with noeol' , function ()
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{
NIL ,
{},
{
method = 'textDocument/inlayHint' ,
params = {
textDocument = {
uri = 'file://' ,
2024-01-03 02:09:18 +01:00
},
2023-09-21 14:06:40 +04:00
range = {
start = { line = 0 , character = 0 },
[ 'end' ] = { line = 1 , character = 3 },
2024-01-03 02:09:18 +01:00
},
2023-09-21 14:06:40 +04:00
},
bufnr = 2 ,
client_id = 1 ,
2024-06-14 05:03:58 -04:00
version = 0 ,
2023-09-21 14:06:40 +04:00
},
},
{ NIL , {}, { method = 'start' , client_id = 1 } },
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2023-09-21 14:06:40 +04:00
test_rpc_server {
test_name = 'inlay_hint' ,
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.bo[BUFFER].eol = false
]]
end ,
on_init = function ( _client )
client = _client
eq ( true , client.supports_method ( 'textDocument/inlayHint' ))
exec_lua [[
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
]]
end ,
on_exit = function ( code , signal )
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
end ,
on_handler = function ( err , result , ctx )
if ctx.method == 'start' then
exec_lua [[
2024-04-18 07:57:58 -07:00
vim.lsp.inlay_hint.enable(true, { bufnr = BUFFER })
2023-09-21 14:06:40 +04:00
]]
end
if ctx.method == 'textDocument/inlayHint' 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 )
2021-03-07 17:18:32 -08:00
it ( 'should check the body and didChange incremental' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2019-11-13 12:55:26 -08:00
test_rpc_server {
test_name = 'basic_check_buffer_open_and_change_incremental' ,
2022-01-11 18:10:29 +01:00
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" )
2022-04-30 02:22:30 -07:00
eq ( sync_kind , client.server_capabilities (). textDocumentSync.change )
eq ( true , client.server_capabilities (). textDocumentSync.openClose )
2022-01-11 18:10:29 +01:00
exec_lua [[
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
]]
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2022-01-11 18:10:29 +01:00
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 )
it ( 'should check the body and didChange incremental with debounce' , function ()
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-01-11 18:10:29 +01:00
test_rpc_server {
test_name = 'basic_check_buffer_open_and_change_incremental' ,
options = {
allow_incremental_sync = true ,
debounce_text_changes = 5 ,
},
2019-11-13 12:55:26 -08:00
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" )
2022-04-30 02:22:30 -07:00
eq ( sync_kind , client.server_capabilities (). textDocumentSync.change )
eq ( true , client.server_capabilities (). textDocumentSync.openClose )
2019-11-13 12:55:26 -08:00
exec_lua [[
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
]]
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
if ctx.method == 'start' then
2019-11-13 12:55:26 -08:00
exec_lua [[
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
2021-03-07 17:18:32 -08:00
"123boop";
2019-11-13 12:55:26 -08:00
})
]]
client.notify ( 'finish' )
end
2021-08-28 00:12:30 -04:00
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
2019-11-13 12:55:26 -08:00
client.stop ()
end
end ,
}
end )
-- TODO(askhan) we don't support full for now, so we can disable these tests.
2020-02-08 15:35:28 -08:00
pending ( 'should check the body and didChange incremental normal mode editing' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-10-29 07:45:01 -05:00
{ NIL , {}, { method = 'shutdown' , bufnr = 1 , client_id = 1 } },
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2019-11-13 12:55:26 -08:00
test_rpc_server {
2020-02-08 15:35:28 -08:00
test_name = 'basic_check_buffer_open_and_change_incremental_editing' ,
2019-11-13 12:55:26 -08:00
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" )
2022-04-30 02:22:30 -07:00
eq ( sync_kind , client.server_capabilities (). textDocumentSync.change )
eq ( true , client.server_capabilities (). textDocumentSync.openClose )
2019-11-13 12:55:26 -08:00
exec_lua [[
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
]]
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
if ctx.method == 'start' then
2024-04-20 17:44:13 +02:00
n.command ( 'normal! 1Go' )
2019-11-13 12:55:26 -08:00
client.notify ( 'finish' )
end
2021-08-28 00:12:30 -04:00
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
2019-11-13 12:55:26 -08:00
client.stop ()
end
end ,
}
end )
it ( 'should check the body and didChange full with 2 changes' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2019-11-13 12:55:26 -08:00
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" )
2022-04-30 02:22:30 -07:00
eq ( sync_kind , client.server_capabilities (). textDocumentSync.change )
eq ( true , client.server_capabilities (). textDocumentSync.openClose )
2019-11-13 12:55:26 -08:00
exec_lua [[
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
]]
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
if ctx.method == 'start' then
2019-11-13 12:55:26 -08:00
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
2021-08-28 00:12:30 -04:00
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
2019-11-13 12:55:26 -08:00
client.stop ()
end
end ,
}
end )
it ( 'should check the body and didChange full lifecycle' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2019-11-13 12:55:26 -08:00
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" )
2022-04-30 02:22:30 -07:00
eq ( sync_kind , client.server_capabilities (). textDocumentSync.change )
eq ( true , client.server_capabilities (). textDocumentSync.openClose )
2019-11-13 12:55:26 -08:00
exec_lua [[
assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
]]
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
if ctx.method == 'start' then
2019-11-13 12:55:26 -08:00
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
2021-08-28 00:12:30 -04:00
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
2019-11-13 12:55:26 -08:00
client.stop ()
end
end ,
}
end )
end )
describe ( 'parsing tests' , function ()
it ( 'should handle invalid content-length correctly' , function ()
2021-06-22 10:35:36 +02:00
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
2019-11-13 12:55:26 -08:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2019-11-13 12:55:26 -08:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2019-11-13 12:55:26 -08:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
2019-11-13 12:55:26 -08:00
end ,
}
end )
2021-05-18 23:28:09 +01:00
it ( 'should not trim vim.NIL from the end of a list' , function ()
local expected_handlers = {
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'finish' , client_id = 1 } },
{
NIL ,
2024-01-03 02:09:18 +01:00
{
2021-05-18 23:28:09 +01:00
arguments = { 'EXTRACT_METHOD' , { metadata = {} }, 3 , 0 , 6123 , NIL },
command = 'refactor.perform' ,
title = 'EXTRACT_METHOD' ,
2024-01-03 02:09:18 +01:00
},
2021-08-28 00:12:30 -04:00
{ method = 'workspace/executeCommand' , client_id = 1 },
2024-01-03 02:09:18 +01:00
},
2021-08-28 00:12:30 -04:00
{ NIL , {}, { method = 'start' , client_id = 1 } },
2021-05-18 23:28:09 +01:00
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-05-18 23:28:09 +01:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-05-18 23:28:09 +01:00
end ,
2021-08-28 00:12:30 -04:00
on_handler = function ( err , result , ctx )
eq ( table.remove ( expected_handlers ), { err , result , ctx }, 'expected handler' )
if ctx.method == 'finish' then
2021-05-18 23:28:09 +01:00
client.stop ()
end
end ,
}
end )
2020-05-26 21:55:45 +09:00
end )
2019-11-13 12:55:26 -08:00
end )
2020-01-08 09:20:53 -08:00
2020-01-08 09:46:25 -08:00
describe ( 'LSP' , function ()
2020-01-08 09:20:53 -08:00
before_each ( function ()
2021-04-03 10:04:14 +02:00
clear_notrace ()
2020-01-08 09:20:53 -08:00
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
2020-05-04 09:05:16 +09:00
describe ( 'apply_text_edits' , function ()
2020-01-08 09:46:25 -08:00
before_each ( function ()
insert ( dedent ( [[
First line of text
Second line of text
Third line of text
2020-05-04 09:05:16 +09:00
Fourth line of text
å å ɧ 汉语 ↥ 🤦 🦄]] ))
2020-01-08 09:46:25 -08:00
end )
2020-05-08 16:04:41 +02:00
it ( 'applies simple edits' , function ()
2020-01-08 09:20:53 -08:00
local edits = {
make_edit ( 0 , 0 , 0 , 0 , { '123' }),
make_edit ( 1 , 0 , 1 , 1 , { '2' }),
make_edit ( 2 , 0 , 2 , 2 , { '3' }),
2020-06-04 21:48:48 +09:00
make_edit ( 3 , 2 , 3 , 4 , { '' }),
2020-01-08 09:20:53 -08:00
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2020-01-08 09:20:53 -08:00
eq ({
'123First line of text' ,
'2econd line of text' ,
'3ird line of text' ,
2020-06-04 21:48:48 +09:00
'Foth line of text' ,
2020-05-04 09:05:16 +09:00
'å å ɧ 汉语 ↥ 🤦 🦄' ,
2020-01-08 09:20:53 -08:00
}, buf_lines ( 1 ))
end )
2020-01-08 09:46:25 -08:00
it ( 'applies complex edits' , function ()
2020-01-08 09:20:53 -08:00
local edits = {
2020-06-14 21:23:16 +02:00
make_edit ( 0 , 0 , 0 , 0 , { '' , '12' }),
2020-07-30 19:37:19 +02:00
make_edit ( 0 , 0 , 0 , 0 , { '3' , 'foo' }),
2020-01-08 09:20:53 -08:00
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' , { '!' }),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2020-01-08 09:20:53 -08:00
eq ({
2024-06-07 02:54:43 -07:00
'3' ,
'foo' ,
'12Fbar' ,
2020-01-08 09:20:53 -08:00
'123irst guy' ,
'baz line of text' ,
'The next line of text' ,
'another line of text' ,
'before this!' ,
2020-05-04 09:05:16 +09:00
'å å ɧ 汉语 ↥ 🤦 🦄' ,
2020-01-08 09:20:53 -08:00
}, buf_lines ( 1 ))
end )
2021-09-19 05:19:21 +09:00
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' , { '!' }),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2021-09-19 05:19:21 +09:00
eq ({
2024-06-07 02:54:43 -07:00
'3' ,
'foo' ,
'12Fbar' ,
2021-09-19 05:19:21 +09:00
'123irst guy' ,
'baz line of text' ,
'The next line of text' ,
'another line of text' ,
'before this!' ,
'å å ɧ 汉语 ↥ 🤦 🦄' ,
}, buf_lines ( 1 ))
end )
2020-05-08 16:04:41 +02:00
it ( 'applies non-ASCII characters edits' , function ()
2020-05-04 09:05:16 +09:00
local edits = {
2020-05-08 16:04:41 +02:00
make_edit ( 4 , 3 , 4 , 4 , { 'ä' }),
2020-05-04 09:05:16 +09:00
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2020-05-04 09:05:16 +09:00
eq ({
'First line of text' ,
'Second line of text' ,
'Third line of text' ,
'Fourth line of text' ,
2020-05-08 16:04:41 +02:00
'å ä ɧ 汉语 ↥ 🤦 🦄' ,
2020-01-08 09:20:53 -08:00
}, buf_lines ( 1 ))
end )
2021-02-20 04:20:42 +01:00
it ( 'applies text edits at the end of the document' , function ()
local edits = {
make_edit ( 5 , 0 , 5 , 0 , 'foobar' ),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2021-02-20 04:20:42 +01:00
eq ({
'First line of text' ,
'Second line of text' ,
'Third line of text' ,
'Fourth line of text' ,
'å å ɧ 汉语 ↥ 🤦 🦄' ,
'foobar' ,
}, buf_lines ( 1 ))
end )
2022-01-13 18:28:13 +09:00
it ( 'applies multiple text edits at the end of the document' , function ()
local edits = {
make_edit ( 4 , 0 , 5 , 0 , '' ),
make_edit ( 5 , 0 , 5 , 0 , 'foobar' ),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2022-01-13 18:28:13 +09:00
eq ({
'First line of text' ,
'Second line of text' ,
'Third line of text' ,
'Fourth line of text' ,
'foobar' ,
}, buf_lines ( 1 ))
end )
2023-06-05 01:45:01 +02:00
it ( 'it restores marks' , function ()
local edits = {
make_edit ( 1 , 0 , 2 , 5 , 'foobar' ),
make_edit ( 4 , 0 , 5 , 0 , 'barfoo' ),
}
2024-04-02 15:45:19 +02:00
eq ( true , api.nvim_buf_set_mark ( 1 , 'a' , 2 , 1 , {}))
2023-06-05 01:45:01 +02:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
eq ({
'First line of text' ,
'foobar line of text' ,
'Fourth line of text' ,
'barfoo' ,
}, buf_lines ( 1 ))
2024-04-02 15:45:19 +02:00
local mark = api.nvim_buf_get_mark ( 1 , 'a' )
2023-06-05 01:45:01 +02:00
eq ({ 2 , 1 }, mark )
end )
it ( 'it restores marks to last valid col' , function ()
local edits = {
make_edit ( 1 , 0 , 2 , 15 , 'foobar' ),
make_edit ( 4 , 0 , 5 , 0 , 'barfoo' ),
}
2024-04-02 15:45:19 +02:00
eq ( true , api.nvim_buf_set_mark ( 1 , 'a' , 2 , 10 , {}))
2023-06-05 01:45:01 +02:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
eq ({
'First line of text' ,
'foobarext' ,
'Fourth line of text' ,
'barfoo' ,
}, buf_lines ( 1 ))
2024-04-02 15:45:19 +02:00
local mark = api.nvim_buf_get_mark ( 1 , 'a' )
2023-06-05 01:45:01 +02:00
eq ({ 2 , 9 }, mark )
end )
it ( 'it restores marks to last valid line' , function ()
local edits = {
make_edit ( 1 , 0 , 4 , 5 , 'foobar' ),
make_edit ( 4 , 0 , 5 , 0 , 'barfoo' ),
}
2024-04-02 15:45:19 +02:00
eq ( true , api.nvim_buf_set_mark ( 1 , 'a' , 4 , 1 , {}))
2023-06-05 01:45:01 +02:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
eq ({
'First line of text' ,
'foobaro' ,
}, buf_lines ( 1 ))
2024-04-02 15:45:19 +02:00
local mark = api.nvim_buf_get_mark ( 1 , 'a' )
2023-06-05 01:45:01 +02:00
eq ({ 2 , 1 }, mark )
end )
2020-05-17 19:47:14 +02:00
2021-09-19 05:19:21 +09:00
describe ( 'cursor position' , function ()
it ( "don't fix the cursor if the range contains the cursor" , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_cursor ( 0 , { 2 , 6 })
2021-09-19 05:19:21 +09:00
local edits = {
make_edit ( 1 , 0 , 1 , 19 , 'Second line of text' ),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2021-09-19 05:19:21 +09:00
eq ({
'First line of text' ,
'Second line of text' ,
'Third line of text' ,
'Fourth line of text' ,
'å å ɧ 汉语 ↥ 🤦 🦄' ,
}, buf_lines ( 1 ))
2024-03-25 19:06:28 +00:00
eq ({ 2 , 6 }, api.nvim_win_get_cursor ( 0 ))
2021-09-19 05:19:21 +09:00
end )
2021-10-19 03:49:33 +09:00
it ( 'fix the cursor to the valid col if the content was removed' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_cursor ( 0 , { 2 , 6 })
2021-09-19 05:19:21 +09:00
local edits = {
2021-10-19 03:49:33 +09:00
make_edit ( 1 , 0 , 1 , 6 , '' ),
make_edit ( 1 , 6 , 1 , 19 , '' ),
2021-09-19 05:19:21 +09:00
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2021-09-19 05:19:21 +09:00
eq ({
'First line of text' ,
'' ,
'Third line of text' ,
'Fourth line of text' ,
'å å ɧ 汉语 ↥ 🤦 🦄' ,
}, buf_lines ( 1 ))
2024-03-25 19:06:28 +00:00
eq ({ 2 , 0 }, api.nvim_win_get_cursor ( 0 ))
2021-09-19 05:19:21 +09:00
end )
2021-10-19 03:49:33 +09:00
it ( 'fix the cursor to the valid row if the content was removed' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_cursor ( 0 , { 2 , 6 })
2021-10-19 03:49:33 +09:00
local edits = {
make_edit ( 1 , 0 , 1 , 6 , '' ),
make_edit ( 0 , 18 , 5 , 0 , '' ),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2021-10-19 03:49:33 +09:00
eq ({
'First line of text' ,
}, buf_lines ( 1 ))
2024-03-25 19:06:28 +00:00
eq ({ 1 , 17 }, api.nvim_win_get_cursor ( 0 ))
2021-10-19 03:49:33 +09:00
end )
2021-09-19 05:19:21 +09:00
it ( 'fix the cursor row' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_cursor ( 0 , { 3 , 0 })
2021-09-19 05:19:21 +09:00
local edits = {
make_edit ( 1 , 0 , 2 , 0 , '' ),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2021-09-19 05:19:21 +09:00
eq ({
'First line of text' ,
'Third line of text' ,
'Fourth line of text' ,
'å å ɧ 汉语 ↥ 🤦 🦄' ,
}, buf_lines ( 1 ))
2024-03-25 19:06:28 +00:00
eq ({ 2 , 0 }, api.nvim_win_get_cursor ( 0 ))
2021-09-19 05:19:21 +09:00
end )
it ( 'fix the cursor col' , function ()
2023-03-14 20:59:43 +09:00
-- append empty last line. See #22636
2024-04-02 15:45:19 +02:00
api.nvim_buf_set_lines ( 1 , - 1 , - 1 , true , { '' })
2023-03-14 20:59:43 +09:00
2024-03-25 19:06:28 +00:00
api.nvim_win_set_cursor ( 0 , { 2 , 11 })
2021-09-19 05:19:21 +09:00
local edits = {
make_edit ( 1 , 7 , 1 , 11 , '' ),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2021-09-19 05:19:21 +09:00
eq ({
'First line of text' ,
'Second of text' ,
'Third line of text' ,
'Fourth line of text' ,
'å å ɧ 汉语 ↥ 🤦 🦄' ,
2023-03-14 20:59:43 +09:00
'' ,
2021-09-19 05:19:21 +09:00
}, buf_lines ( 1 ))
2024-03-25 19:06:28 +00:00
eq ({ 2 , 7 }, api.nvim_win_get_cursor ( 0 ))
2021-09-19 05:19:21 +09:00
end )
it ( 'fix the cursor row and col' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_cursor ( 0 , { 2 , 12 })
2021-09-19 05:19:21 +09:00
local edits = {
make_edit ( 0 , 11 , 1 , 12 , '' ),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2021-09-19 05:19:21 +09:00
eq ({
'First line of text' ,
'Third line of text' ,
'Fourth line of text' ,
'å å ɧ 汉语 ↥ 🤦 🦄' ,
}, buf_lines ( 1 ))
2024-03-25 19:06:28 +00:00
eq ({ 1 , 11 }, api.nvim_win_get_cursor ( 0 ))
2021-09-19 05:19:21 +09:00
end )
end )
2020-05-17 19:47:14 +02:00
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' }),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2020-05-17 19:47:14 +02:00
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' }),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2020-05-17 19:47:14 +02:00
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' }),
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-16' )
2020-05-17 19:47:14 +02:00
eq ({ 'All replaced' }, buf_lines ( 1 ))
end )
end )
2020-01-08 09:20:53 -08:00
end )
2020-04-27 10:55:06 +02:00
2022-09-20 22:14:58 +02:00
describe ( 'apply_text_edits regression tests for #20116' , function ()
before_each ( function ()
insert ( dedent ( [[
Test line one
Test line two 21 char]] ))
end )
describe ( 'with LSP end column out of bounds and start column at 0' , function ()
it ( 'applies edits at the end of the buffer' , function ()
local edits = {
make_edit ( 0 , 0 , 1 , 22 , { '#include "whatever.h" \r\n #include <algorithm> \r ' }),
}
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-8' )
eq ({ '#include "whatever.h"' , '#include <algorithm>' }, buf_lines ( 1 ))
end )
it ( 'applies edits in the middle of the buffer' , function ()
local edits = {
make_edit ( 0 , 0 , 0 , 22 , { '#include "whatever.h" \r\n #include <algorithm> \r ' }),
}
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-8' )
eq (
{ '#include "whatever.h"' , '#include <algorithm>' , 'Test line two 21 char' },
buf_lines ( 1 )
2024-01-03 02:09:18 +01:00
)
2022-09-20 22:14:58 +02:00
end )
end )
describe ( 'with LSP end column out of bounds and start column NOT at 0' , function ()
it ( 'applies edits at the end of the buffer' , function ()
local edits = {
make_edit ( 0 , 2 , 1 , 22 , { '#include "whatever.h" \r\n #include <algorithm> \r ' }),
}
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-8' )
eq ({ 'Te#include "whatever.h"' , '#include <algorithm>' }, buf_lines ( 1 ))
end )
it ( 'applies edits in the middle of the buffer' , function ()
local edits = {
make_edit ( 0 , 2 , 0 , 22 , { '#include "whatever.h" \r\n #include <algorithm> \r ' }),
}
exec_lua ( 'vim.lsp.util.apply_text_edits(...)' , edits , 1 , 'utf-8' )
eq (
{ 'Te#include "whatever.h"' , '#include <algorithm>' , 'Test line two 21 char' },
buf_lines ( 1 )
2024-01-03 02:09:18 +01:00
)
2022-09-20 22:14:58 +02:00
end )
end )
end )
2020-04-27 10:55:06 +02:00
describe ( 'apply_text_document_edit' , function ()
2024-03-25 19:06:28 +00:00
local target_bufnr --- @type integer
2020-05-14 04:14:52 +01:00
local text_document_edit = function ( editVersion )
return {
edits = {
make_edit ( 0 , 0 , 0 , 3 , 'First ↥ 🤦 🦄' ),
2021-01-11 11:39:11 -05:00
},
2020-05-14 04:14:52 +01:00
textDocument = {
2021-07-08 00:36:27 +01:00
uri = 'file:///fake/uri' ,
2020-05-14 04:14:52 +01:00
version = editVersion ,
},
}
end
2020-04-27 10:55:06 +02:00
before_each ( function ()
2020-05-03 13:01:00 +09:00
target_bufnr = exec_lua [[
2021-07-08 00:36:27 +01:00
local bufnr = vim.uri_to_bufnr("file:///fake/uri")
2020-05-14 04:14:52 +01:00
local lines = {"1st line of text", "2nd line of 语text"}
2020-05-03 13:01:00 +09:00
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
return bufnr
]]
2020-04-27 10:55:06 +02:00
end )
2020-05-03 13:01:00 +09:00
it ( 'correctly goes ahead with the edit if all is normal' , function ()
2022-01-13 02:34:04 -08:00
exec_lua ( "vim.lsp.util.apply_text_document_edit(..., nil, 'utf-16')" , text_document_edit ( 5 ))
2020-04-27 10:55:06 +02:00
eq ({
2020-05-14 04:14:52 +01:00
'First ↥ 🤦 🦄 line of text' ,
'2nd line of 语text' ,
2020-05-03 13:01:00 +09:00
}, buf_lines ( target_bufnr ))
2020-04-27 10:55:06 +02:00
end )
2021-04-14 21:29:32 +02:00
it ( 'always accepts edit with version = 0' , function ()
exec_lua (
[[
local args = {...}
local bufnr = select(1, ...)
local text_edit = select(2, ...)
2024-06-07 11:36:46 +02:00
vim.lsp.util.buf_versions[bufnr] = 10
2022-01-13 02:34:04 -08:00
vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16')
2021-04-14 21:29:32 +02:00
]] ,
target_bufnr ,
text_document_edit ( 0 )
2024-01-03 02:09:18 +01:00
)
2021-04-14 21:29:32 +02:00
eq ({
'First ↥ 🤦 🦄 line of text' ,
'2nd line of 语text' ,
}, buf_lines ( target_bufnr ))
end )
2020-04-27 10:55:06 +02:00
it ( 'skips the edit if the version of the edit is behind the local buffer ' , function ()
2024-06-07 11:36:46 +02:00
local apply_edit_mocking_current_version = function ( edit , versionedBuf )
2020-05-14 04:14:52 +01:00
exec_lua (
[[
local args = {...}
2024-06-07 11:36:46 +02:00
local versionedBuf = args[2]
vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion
2022-01-13 02:34:04 -08:00
vim.lsp.util.apply_text_document_edit(args[1], nil, 'utf-16')
2020-05-14 04:14:52 +01:00
]] ,
2024-06-07 11:36:46 +02:00
edit ,
versionedBuf
2020-05-14 04:14:52 +01:00
)
end
local baseText = {
'1st line of text' ,
'2nd line of 语text' ,
2020-04-27 10:55:06 +02:00
}
2020-05-14 04:14:52 +01:00
eq ( baseText , buf_lines ( target_bufnr ))
-- Apply an edit for an old version, should skip
2024-06-07 11:36:46 +02:00
apply_edit_mocking_current_version (
text_document_edit ( 2 ),
{ currentVersion = 7 , bufnr = target_bufnr }
)
2020-05-14 04:14:52 +01:00
eq ( baseText , buf_lines ( target_bufnr )) -- no change
-- Sanity check that next version to current does apply change
2024-06-07 11:36:46 +02:00
apply_edit_mocking_current_version (
text_document_edit ( 8 ),
{ currentVersion = 7 , bufnr = target_bufnr }
)
2020-04-27 10:55:06 +02:00
eq ({
2020-05-14 04:14:52 +01:00
'First ↥ 🤦 🦄 line of text' ,
'2nd line of 语text' ,
2020-05-03 13:01:00 +09:00
}, buf_lines ( target_bufnr ))
2020-04-27 10:55:06 +02:00
end )
2020-01-08 09:20:53 -08:00
end )
2021-01-11 11:39:11 -05:00
2020-05-11 16:56:35 +02:00
describe ( 'workspace_apply_edit' , function ()
it ( 'workspace/applyEdit returns ApplyWorkspaceEditResponse' , function ()
2022-01-13 02:34:04 -08:00
local expected_handlers = {
{ NIL , {}, { method = 'test' , client_id = 1 } },
}
test_rpc_server {
test_name = 'basic_init' ,
on_init = function ( client , _ )
client.stop ()
end ,
-- If the program timed out, then code will be nil.
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2022-01-13 02:34:04 -08:00
end ,
-- Note that NIL must be used here.
-- on_handler(err, method, result, client_id)
on_handler = 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, {client_id = TEST_RPC_CLIENT_ID})
]]
)
eq ( table.remove ( expected_handlers ), { ... })
end ,
2020-05-11 16:56:35 +02:00
}
end )
end )
2021-01-11 11:39:11 -05:00
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 = {
2021-07-08 00:36:27 +01:00
uri = 'file:///fake/uri' ,
2021-01-11 11:39:11 -05:00
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 [[
2021-07-08 00:36:27 +01:00
local bufnr = vim.uri_to_bufnr("file:///fake/uri")
2021-01-11 11:39:11 -05:00
local lines = {
"Original Line #1",
"Original Line #2"
}
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
2024-06-07 11:36:46 +02:00
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
})
2021-01-11 11:39:11 -05:00
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]
2022-01-13 02:34:04 -08:00
vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16')
2021-01-11 11:39:11 -05:00
return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false)
]] ,
make_workspace_edit ( edits ),
target_bufnr
)
2024-01-03 02:09:18 +01:00
)
2021-01-11 11:39:11 -05:00
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]
2022-01-13 02:34:04 -08:00
vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16')
2021-01-11 11:39:11 -05:00
return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false)
]] ,
make_workspace_edit ( edits ),
target_bufnr
)
2024-01-03 02:09:18 +01:00
)
2021-01-11 11:39:11 -05:00
end )
2021-03-18 11:07:06 +01:00
it ( 'Supports file creation with CreateFile payload' , function ()
2024-01-12 11:28:20 +00:00
local tmpfile = tmpname ()
2021-03-18 11:07:06 +01:00
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 ,
},
},
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_workspace_edit(...)' , edit , 'utf-16' )
2024-04-02 15:45:19 +02:00
eq ( true , vim.uv . fs_stat ( tmpfile ) ~= nil )
2021-03-18 11:07:06 +01:00
end )
2022-09-24 06:46:21 -04:00
it (
'Supports file creation in folder that needs to be created with CreateFile payload' ,
function ()
2024-01-12 11:28:20 +00:00
local tmpfile = tmpname ()
2022-09-24 06:46:21 -04:00
os.remove ( tmpfile ) -- Should not exist, only interested in a tmpname
tmpfile = tmpfile .. '/dummy/x/'
local uri = exec_lua ( 'return vim.uri_from_fname(...)' , tmpfile )
local edit = {
documentChanges = {
{
kind = 'create' ,
uri = uri ,
2024-01-03 02:09:18 +01:00
},
2022-09-24 06:46:21 -04:00
},
}
exec_lua ( 'vim.lsp.util.apply_workspace_edit(...)' , edit , 'utf-16' )
2024-04-02 15:45:19 +02:00
eq ( true , vim.uv . fs_stat ( tmpfile ) ~= nil )
2022-09-24 06:46:21 -04:00
end
)
2021-03-18 11:07:06 +01:00
it ( 'createFile does not touch file if it exists and ignoreIfExists is set' , function ()
2024-01-12 11:28:20 +00:00
local tmpfile = tmpname ()
2021-03-18 11:07:06 +01:00
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 ,
},
},
},
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_workspace_edit(...)' , edit , 'utf-16' )
2024-04-02 15:45:19 +02:00
eq ( true , vim.uv . fs_stat ( tmpfile ) ~= nil )
2021-03-18 11:07:06 +01:00
eq ( 'Dummy content' , read_file ( tmpfile ))
end )
it ( 'createFile overrides file if overwrite is set' , function ()
2024-01-12 11:28:20 +00:00
local tmpfile = tmpname ()
2021-03-18 11:07:06 +01:00
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
},
},
},
}
2022-01-13 02:34:04 -08:00
exec_lua ( 'vim.lsp.util.apply_workspace_edit(...)' , edit , 'utf-16' )
2024-04-02 15:45:19 +02:00
eq ( true , vim.uv . fs_stat ( tmpfile ) ~= nil )
2021-03-18 11:07:06 +01:00
eq ( '' , read_file ( tmpfile ))
end )
2021-03-18 11:32:33 +01:00
it ( 'DeleteFile delete file and buffer' , function ()
2024-01-12 11:28:20 +00:00
local tmpfile = tmpname ()
2021-03-18 11:32:33 +01:00
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 ,
},
},
}
2022-01-13 02:34:04 -08:00
eq ( true , pcall ( exec_lua , 'vim.lsp.util.apply_workspace_edit(...)' , edit , 'utf-16' ))
2024-04-02 15:45:19 +02:00
eq ( false , vim.uv . fs_stat ( tmpfile ) ~= nil )
eq ( false , api.nvim_buf_is_loaded ( fn.bufadd ( tmpfile )))
2021-03-18 11:32:33 +01:00
end )
it ( 'DeleteFile fails if file does not exist and ignoreIfNotExists is false' , function ()
2024-01-12 11:28:20 +00:00
local tmpfile = tmpname ()
2021-03-18 11:32:33 +01:00
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 ))
2024-04-02 15:45:19 +02:00
eq ( false , vim.uv . fs_stat ( tmpfile ) ~= nil )
2021-03-18 11:32:33 +01:00
end )
2021-01-11 11:39:11 -05:00
end )
2021-03-15 22:34:22 +01:00
describe ( 'lsp.util.rename' , function ()
2024-04-20 17:44:13 +02:00
local pathsep = n.get_pathsep ()
2023-02-18 09:43:59 +03:00
2021-03-15 22:34:22 +01:00
it ( 'Can rename an existing file' , function ()
2024-01-12 11:28:20 +00:00
local old = tmpname ()
2021-03-15 22:34:22 +01:00
write_file ( old , 'Test content' )
2024-01-12 11:28:20 +00:00
local new = tmpname ()
2021-03-15 22:34:22 +01:00
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, ...)
2024-03-02 23:21:53 +09:00
local old_bufnr = vim.fn.bufadd(old)
vim.fn.bufload(old_bufnr)
2021-03-15 22:34:22 +01:00
vim.lsp.util.rename(old, new)
2024-03-02 23:21:53 +09:00
-- the existing buffer is renamed in-place and its contents is kept
local new_bufnr = vim.fn.bufadd(new)
vim.fn.bufload(new_bufnr)
return (old_bufnr == new_bufnr) and vim.api.nvim_buf_get_lines(new_bufnr, 0, -1, true)
2021-03-15 22:34:22 +01:00
]] ,
old ,
new
)
eq ({ 'Test content' }, lines )
2024-04-02 15:45:19 +02:00
local exists = vim.uv . fs_stat ( old ) ~= nil
2021-03-15 22:34:22 +01:00
eq ( false , exists )
2024-04-02 15:45:19 +02:00
exists = vim.uv . fs_stat ( new ) ~= nil
2021-03-15 22:34:22 +01:00
eq ( true , exists )
os.remove ( new )
end )
2023-03-14 13:08:37 +01:00
it ( 'Can rename a directory' , function ()
2023-02-18 09:43:59 +03:00
-- only reserve the name, file must not exist for the test scenario
2024-01-12 11:28:20 +00:00
local old_dir = tmpname ()
local new_dir = tmpname ()
2023-02-18 09:43:59 +03:00
os.remove ( old_dir )
os.remove ( new_dir )
2024-04-20 17:44:13 +02:00
n.mkdir_p ( old_dir )
2023-02-18 09:43:59 +03:00
2023-03-14 13:08:37 +01:00
local file = 'file.txt'
2023-02-18 09:43:59 +03:00
write_file ( old_dir .. pathsep .. file , 'Test content' )
2023-03-14 13:08:37 +01:00
local lines = exec_lua (
[[
2023-02-18 09:43:59 +03:00
local old_dir = select(1, ...)
local new_dir = select(2, ...)
2024-03-02 23:21:53 +09:00
local pathsep = select(3, ...)
local file = select(4, ...)
local old_bufnr = vim.fn.bufadd(old_dir .. pathsep .. file)
vim.fn.bufload(old_bufnr)
2023-02-18 09:43:59 +03:00
vim.lsp.util.rename(old_dir, new_dir)
2024-03-02 23:21:53 +09:00
-- the existing buffer is renamed in-place and its contents is kept
local new_bufnr = vim.fn.bufadd(new_dir .. pathsep .. file)
vim.fn.bufload(new_bufnr)
return (old_bufnr == new_bufnr) and vim.api.nvim_buf_get_lines(new_bufnr, 0, -1, true)
2023-03-14 13:08:37 +01:00
]] ,
old_dir ,
new_dir ,
2024-03-02 23:21:53 +09:00
pathsep ,
file
2023-03-14 13:08:37 +01:00
)
2024-03-02 23:21:53 +09:00
eq ({ 'Test content' }, lines )
2024-04-02 15:45:19 +02:00
eq ( false , vim.uv . fs_stat ( old_dir ) ~= nil )
eq ( true , vim.uv . fs_stat ( new_dir ) ~= nil )
eq ( true , vim.uv . fs_stat ( new_dir .. pathsep .. file ) ~= nil )
2023-02-18 09:43:59 +03:00
os.remove ( new_dir )
end )
2024-02-25 00:47:34 +09:00
it ( 'Does not touch buffers that do not match path prefix' , function ()
local old = tmpname ()
local new = tmpname ()
os.remove ( old )
os.remove ( new )
2024-04-20 17:44:13 +02:00
n.mkdir_p ( old )
2024-02-25 00:47:34 +09:00
local result = exec_lua (
[[
local old = select(1, ...)
local new = select(2, ...)
local old_prefixed = 'explorer://' .. old
local old_suffixed = old .. '.bak'
local new_prefixed = 'explorer://' .. new
local new_suffixed = new .. '.bak'
local old_prefixed_buf = vim.fn.bufadd(old_prefixed)
local old_suffixed_buf = vim.fn.bufadd(old_suffixed)
local new_prefixed_buf = vim.fn.bufadd(new_prefixed)
local new_suffixed_buf = vim.fn.bufadd(new_suffixed)
vim.lsp.util.rename(old, new)
return
vim.api.nvim_buf_is_valid(old_prefixed_buf) and
vim.api.nvim_buf_is_valid(old_suffixed_buf) and
vim.api.nvim_buf_is_valid(new_prefixed_buf) and
vim.api.nvim_buf_is_valid(new_suffixed_buf) and
vim.api.nvim_buf_get_name(old_prefixed_buf) == old_prefixed and
vim.api.nvim_buf_get_name(old_suffixed_buf) == old_suffixed and
vim.api.nvim_buf_get_name(new_prefixed_buf) == new_prefixed and
vim.api.nvim_buf_get_name(new_suffixed_buf) == new_suffixed
]] ,
old ,
new
)
eq ( true , result )
os.remove ( new )
end )
2021-03-15 22:34:22 +01:00
it (
'Does not rename file if target exists and ignoreIfExists is set or overwrite is false' ,
function ()
2024-01-12 11:28:20 +00:00
local old = tmpname ()
2021-03-15 22:34:22 +01:00
write_file ( old , 'Old File' )
2024-01-12 11:28:20 +00:00
local new = tmpname ()
2021-03-15 22:34:22 +01:00
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
)
2024-04-02 15:45:19 +02:00
eq ( true , vim.uv . fs_stat ( old ) ~= nil )
2021-03-15 22:34:22 +01:00
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
)
2024-04-02 15:45:19 +02:00
eq ( true , vim.uv . fs_stat ( old ) ~= nil )
2021-03-15 22:34:22 +01:00
eq ( 'New file' , read_file ( new ))
end
)
2024-03-02 02:31:54 +09:00
it ( 'Maintains undo information for loaded buffer' , function ()
local old = tmpname ()
write_file ( old , 'line' )
local new = tmpname ()
os.remove ( new )
local undo_kept = exec_lua (
[[
local old = select(1, ...)
local new = select(2, ...)
vim.opt.undofile = true
vim.cmd.edit(old)
vim.cmd.normal('dd')
vim.cmd.write()
local undotree = vim.fn.undotree()
vim.lsp.util.rename(old, new)
2024-03-02 23:21:53 +09:00
-- Renaming uses :saveas, which updates the "last write" information.
-- Other than that, the undotree should remain the same.
undotree.save_cur = undotree.save_cur + 1
undotree.save_last = undotree.save_last + 1
undotree.entries[1].save = undotree.entries[1].save + 1
2024-03-02 02:31:54 +09:00
return vim.deep_equal(undotree, vim.fn.undotree())
]] ,
old ,
new
)
2024-04-02 15:45:19 +02:00
eq ( false , vim.uv . fs_stat ( old ) ~= nil )
eq ( true , vim.uv . fs_stat ( new ) ~= nil )
2024-03-02 02:31:54 +09:00
eq ( true , undo_kept )
end )
it ( 'Maintains undo information for unloaded buffer' , function ()
local old = tmpname ()
write_file ( old , 'line' )
local new = tmpname ()
os.remove ( new )
local undo_kept = exec_lua (
[[
local old = select(1, ...)
local new = select(2, ...)
vim.opt.undofile = true
vim.cmd.split(old)
vim.cmd.normal('dd')
vim.cmd.write()
local undotree = vim.fn.undotree()
vim.cmd.bdelete()
vim.lsp.util.rename(old, new)
vim.cmd.edit(new)
return vim.deep_equal(undotree, vim.fn.undotree())
]] ,
old ,
new
)
2024-04-02 15:45:19 +02:00
eq ( false , vim.uv . fs_stat ( old ) ~= nil )
eq ( true , vim.uv . fs_stat ( new ) ~= nil )
2024-03-02 02:31:54 +09:00
eq ( true , undo_kept )
end )
2024-03-02 23:21:53 +09:00
it ( 'Does not rename file when it conflicts with a buffer without file' , function ()
local old = tmpname ()
write_file ( old , 'Old File' )
local new = tmpname ()
os.remove ( new )
local lines = exec_lua (
[[
local old = select(1, ...)
local new = select(2, ...)
local old_buf = vim.fn.bufadd(old)
vim.fn.bufload(old_buf)
local conflict_buf = vim.api.nvim_create_buf(true, false)
vim.api.nvim_buf_set_name(conflict_buf, new)
vim.api.nvim_buf_set_lines(conflict_buf, 0, -1, true, {'conflict'})
vim.api.nvim_win_set_buf(0, conflict_buf)
vim.lsp.util.rename(old, new)
return vim.api.nvim_buf_get_lines(conflict_buf, 0, -1, true)
]] ,
old ,
new
)
eq ({ 'conflict' }, lines )
eq ( 'Old File' , read_file ( old ))
end )
2021-03-15 22:34:22 +01:00
it ( 'Does override target if overwrite is true' , function ()
2024-01-12 11:28:20 +00:00
local old = tmpname ()
2021-03-15 22:34:22 +01:00
write_file ( old , 'Old file' )
2024-01-12 11:28:20 +00:00
local new = tmpname ()
2021-03-15 22:34:22 +01:00
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
)
2024-04-02 15:45:19 +02:00
eq ( false , vim.uv . fs_stat ( old ) ~= nil )
eq ( true , vim.uv . fs_stat ( new ) ~= nil )
2024-02-29 01:32:25 +09:00
eq ( 'Old file' , read_file ( new ))
2021-03-15 22:34:22 +01:00
end )
end )
2020-06-30 17:48:04 +02:00
describe ( 'lsp.util.locations_to_items' , function ()
it ( 'Convert Location[] to items' , function ()
local expected = {
{
2021-07-08 00:36:27 +01:00
filename = '/fake/uri' ,
2020-06-30 17:48:04 +02:00
lnum = 1 ,
2024-06-10 03:14:55 +02:00
end_lnum = 2 ,
2020-06-30 17:48:04 +02:00
col = 3 ,
2024-06-10 03:14:55 +02:00
end_col = 4 ,
2023-09-07 10:12:02 +02:00
text = 'testing' ,
user_data = {
uri = 'file:///fake/uri' ,
range = {
start = { line = 0 , character = 2 },
2024-06-10 03:14:55 +02:00
[ 'end' ] = { line = 1 , character = 3 },
2023-09-07 10:12:02 +02:00
},
},
2020-06-30 17:48:04 +02:00
},
}
local actual = exec_lua [[
2021-07-08 00:36:27 +01:00
local bufnr = vim.uri_to_bufnr("file:///fake/uri")
2020-06-30 17:48:04 +02:00
local lines = {"testing", "123"}
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
local locations = {
{
2021-07-08 00:36:27 +01:00
uri = 'file:///fake/uri',
2020-06-30 17:48:04 +02:00
range = {
start = { line = 0, character = 2 },
2024-06-10 03:14:55 +02:00
['end'] = { line = 1, character = 3 },
2020-06-30 17:48:04 +02:00
}
},
}
2022-01-13 02:34:04 -08:00
return vim.lsp.util.locations_to_items(locations, 'utf-16')
2020-06-30 17:48:04 +02:00
]]
eq ( expected , actual )
end )
it ( 'Convert LocationLink[] to items' , function ()
local expected = {
{
2021-07-08 00:36:27 +01:00
filename = '/fake/uri' ,
2020-06-30 17:48:04 +02:00
lnum = 1 ,
2024-06-10 03:14:55 +02:00
end_lnum = 1 ,
2020-06-30 17:48:04 +02:00
col = 3 ,
2024-06-10 03:14:55 +02:00
end_col = 4 ,
2023-09-07 10:12:02 +02:00
text = 'testing' ,
user_data = {
targetUri = 'file:///fake/uri' ,
targetRange = {
start = { line = 0 , character = 2 },
[ 'end' ] = { line = 0 , character = 3 },
},
targetSelectionRange = {
start = { line = 0 , character = 2 },
[ 'end' ] = { line = 0 , character = 3 },
},
},
2020-06-30 17:48:04 +02:00
},
}
local actual = exec_lua [[
2021-07-08 00:36:27 +01:00
local bufnr = vim.uri_to_bufnr("file:///fake/uri")
2020-06-30 17:48:04 +02:00
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 },
}
},
}
2022-01-13 02:34:04 -08:00
return vim.lsp.util.locations_to_items(locations, 'utf-16')
2020-06-30 17:48:04 +02:00
]]
eq ( expected , actual )
end )
end )
2024-03-25 19:06:28 +00:00
2020-05-02 15:09:49 +09:00
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 )
2020-06-12 02:34:34 +02:00
it ( 'convert SymbolInformation[] to items' , function ()
2020-05-02 15:09:49 +09:00
local expected = {
{
col = 1 ,
2021-07-08 00:36:27 +01:00
filename = '/test_a' ,
2020-05-02 15:09:49 +09:00
kind = 'File' ,
lnum = 2 ,
text = '[File] TestA' ,
},
{
col = 1 ,
2021-07-08 00:36:27 +01:00
filename = '/test_b' ,
2020-05-02 15:09:49 +09:00
kind = 'Module' ,
lnum = 4 ,
text = '[Module] TestB' ,
2024-01-03 02:09:18 +01:00
},
2020-05-02 15:09:49 +09:00
}
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
}
},
2021-07-08 00:36:27 +01:00
uri = "file:///test_a"
2020-05-02 15:09:49 +09:00
},
2022-10-23 10:17:45 +02:00
containerName = "TestAContainer"
2020-05-02 15:09:49 +09:00
},
{
deprecated = false,
kind = 2,
name = "TestB",
location = {
range = {
start = {
character = 0,
line = 3
},
["end"] = {
character = 0,
line = 4
}
},
2021-07-08 00:36:27 +01:00
uri = "file:///test_b"
2020-05-02 15:09:49 +09:00
},
2022-10-23 10:17:45 +02:00
containerName = "TestBContainer"
2020-05-02 15:09:49 +09:00
}
}
return vim.lsp.util.symbols_to_items(sym_info, nil)
]]
)
end )
end )
2020-05-08 05:23:25 +09:00
describe ( 'lsp.util._get_symbol_kind_name' , function ()
2020-06-12 02:34:34 +02:00
it ( 'returns the name specified by protocol' , function ()
2020-05-08 05:23:25 +09:00
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 )
2020-06-12 02:34:34 +02:00
it ( 'returns the name not specified by protocol' , function ()
2020-05-08 05:23:25 +09:00
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 )
2020-05-19 08:51:10 +02:00
describe ( 'lsp.util.jump_to_location' , function ()
2024-03-25 19:06:28 +00:00
local target_bufnr --- @type integer
2021-04-23 18:50:35 -03:00
2021-05-19 15:33:32 +02:00
before_each ( function ()
target_bufnr = exec_lua [[
2021-07-08 00:36:27 +01:00
local bufnr = vim.uri_to_bufnr("file:///fake/uri")
2021-05-19 15:33:32 +02:00
local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"}
2020-05-19 08:51:10 +02:00
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
return bufnr
2021-05-19 15:33:32 +02:00
]]
2020-05-19 08:51:10 +02:00
end )
2021-05-19 15:33:32 +02:00
local location = function ( start_line , start_char , end_line , end_char )
2020-05-19 08:51:10 +02:00
return {
2021-07-08 00:36:27 +01:00
uri = 'file:///fake/uri' ,
2020-05-19 08:51:10 +02:00
range = {
start = { line = start_line , character = start_char },
[ 'end' ] = { line = end_line , character = end_char },
},
}
end
2021-05-19 15:33:32 +02:00
local jump = function ( msg )
2022-01-13 02:34:04 -08:00
eq ( true , exec_lua ( 'return vim.lsp.util.jump_to_location(...)' , msg , 'utf-16' ))
2024-04-02 15:45:19 +02:00
eq ( target_bufnr , fn.bufnr ( '%' ))
2020-05-19 08:51:10 +02:00
return {
2024-04-02 15:45:19 +02:00
line = fn.line ( '.' ),
col = fn.col ( '.' ),
2020-05-19 08:51:10 +02:00
}
end
it ( 'jumps to a Location' , function ()
2021-05-19 15:33:32 +02:00
local pos = jump ( location ( 0 , 9 , 0 , 9 ))
2020-05-19 08:51:10 +02:00
eq ( 1 , pos.line )
eq ( 10 , pos.col )
end )
it ( 'jumps to a LocationLink' , function ()
2021-05-19 15:33:32 +02:00
local pos = jump ({
2021-07-08 00:36:27 +01:00
targetUri = 'file:///fake/uri' ,
2020-05-19 08:51:10 +02:00
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 ()
2021-05-19 15:33:32 +02:00
local pos = jump ( location ( 1 , 2 , 1 , 2 ))
2020-05-19 08:51:10 +02:00
eq ( 2 , pos.line )
eq ( 4 , pos.col )
2024-04-02 15:45:19 +02:00
eq ( 'å' , fn.expand ( '<cword>' ))
2020-05-19 08:51:10 +02:00
end )
2021-04-23 18:50:35 -03:00
2021-05-06 17:51:25 -04:00
it ( 'adds current position to jumplist before jumping' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_buf ( 0 , target_bufnr )
local mark = api.nvim_buf_get_mark ( target_bufnr , "'" )
2021-05-19 15:35:30 +02:00
eq ({ 1 , 0 }, mark )
2024-03-25 19:06:28 +00:00
api.nvim_win_set_cursor ( 0 , { 2 , 3 })
2021-05-19 15:35:30 +02:00
jump ( location ( 0 , 9 , 0 , 9 ))
2021-05-06 17:51:25 -04:00
2024-03-25 19:06:28 +00:00
mark = api.nvim_buf_get_mark ( target_bufnr , "'" )
2021-05-19 15:35:30 +02:00
eq ({ 2 , 3 }, mark )
2021-05-06 17:51:25 -04:00
end )
2020-05-19 08:51:10 +02:00
end )
2020-06-04 20:23:03 +02:00
2022-10-08 05:22:25 -03:00
describe ( 'lsp.util.show_document' , function ()
2024-03-25 19:06:28 +00:00
local target_bufnr --- @type integer
local target_bufnr2 --- @type integer
2022-10-08 05:22:25 -03:00
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
]] )
target_bufnr2 = exec_lua ( [[
local bufnr = vim.uri_to_bufnr("file:///fake/uri2")
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 , second_uri )
return {
uri = second_uri and 'file:///fake/uri2' or 'file:///fake/uri' ,
range = {
start = { line = start_line , character = start_char },
[ 'end' ] = { line = end_line , character = end_char },
},
}
end
local show_document = function ( msg , focus , reuse_win )
eq (
true ,
exec_lua (
'return vim.lsp.util.show_document(...)' ,
msg ,
'utf-16' ,
{ reuse_win = reuse_win , focus = focus }
)
)
if focus == true or focus == nil then
2024-04-02 15:45:19 +02:00
eq ( target_bufnr , fn.bufnr ( '%' ))
2022-10-08 05:22:25 -03:00
end
return {
2024-04-02 15:45:19 +02:00
line = fn.line ( '.' ),
col = fn.col ( '.' ),
2022-10-08 05:22:25 -03:00
}
end
it ( 'jumps to a Location if focus is true' , function ()
local pos = show_document ( location ( 0 , 9 , 0 , 9 ), true , true )
eq ( 1 , pos.line )
eq ( 10 , pos.col )
end )
2022-12-04 15:57:46 +01:00
it ( 'jumps to a Location if focus is true via handler' , function ()
exec_lua ( create_server_definition )
local result = exec_lua ( [[
local server = _create_server()
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
local result = {
uri = 'file:///fake/uri',
selection = {
start = { line = 0, character = 9 },
['end'] = { line = 0, character = 9 }
},
takeFocus = true,
}
local ctx = {
client_id = client_id,
method = 'window/showDocument',
}
vim.lsp.handlers['window/showDocument'](nil, result, ctx)
vim.lsp.stop_client(client_id)
return {
cursor = vim.api.nvim_win_get_cursor(0)
}
]] )
eq ( 1 , result.cursor [ 1 ])
eq ( 9 , result.cursor [ 2 ])
end )
2022-10-08 05:22:25 -03:00
it ( 'jumps to a Location if focus not set' , function ()
local pos = show_document ( location ( 0 , 9 , 0 , 9 ), nil , true )
eq ( 1 , pos.line )
eq ( 10 , pos.col )
end )
it ( 'does not add current position to jumplist if not focus' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_buf ( 0 , target_bufnr )
local mark = api.nvim_buf_get_mark ( target_bufnr , "'" )
2022-10-08 05:22:25 -03:00
eq ({ 1 , 0 }, mark )
2024-03-25 19:06:28 +00:00
api.nvim_win_set_cursor ( 0 , { 2 , 3 })
2022-10-08 05:22:25 -03:00
show_document ( location ( 0 , 9 , 0 , 9 ), false , true )
show_document ( location ( 0 , 9 , 0 , 9 , true ), false , true )
2024-03-25 19:06:28 +00:00
mark = api.nvim_buf_get_mark ( target_bufnr , "'" )
2022-10-08 05:22:25 -03:00
eq ({ 1 , 0 }, mark )
end )
it ( 'does not change cursor position if not focus and not reuse_win' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_buf ( 0 , target_bufnr )
local cursor = api.nvim_win_get_cursor ( 0 )
2022-10-08 05:22:25 -03:00
show_document ( location ( 0 , 9 , 0 , 9 ), false , false )
2024-03-25 19:06:28 +00:00
eq ( cursor , api.nvim_win_get_cursor ( 0 ))
2022-10-08 05:22:25 -03:00
end )
it ( 'does not change window if not focus' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_buf ( 0 , target_bufnr )
local win = api.nvim_get_current_win ()
2022-10-08 05:22:25 -03:00
-- same document/bufnr
show_document ( location ( 0 , 9 , 0 , 9 ), false , true )
2024-03-25 19:06:28 +00:00
eq ( win , api.nvim_get_current_win ())
2022-10-08 05:22:25 -03:00
-- different document/bufnr, new window/split
show_document ( location ( 0 , 9 , 0 , 9 , true ), false , true )
2024-03-25 19:06:28 +00:00
eq ( 2 , # api.nvim_list_wins ())
eq ( win , api.nvim_get_current_win ())
2022-10-08 05:22:25 -03:00
end )
it ( "respects 'reuse_win' parameter" , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_buf ( 0 , target_bufnr )
2022-10-08 05:22:25 -03:00
-- does not create a new window if the buffer is already open
show_document ( location ( 0 , 9 , 0 , 9 ), false , true )
2024-03-25 19:06:28 +00:00
eq ( 1 , # api.nvim_list_wins ())
2022-10-08 05:22:25 -03:00
-- creates a new window even if the buffer is already open
show_document ( location ( 0 , 9 , 0 , 9 ), false , false )
2024-03-25 19:06:28 +00:00
eq ( 2 , # api.nvim_list_wins ())
2022-10-08 05:22:25 -03:00
end )
it ( 'correctly sets the cursor of the split if range is given without focus' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_buf ( 0 , target_bufnr )
2022-10-08 05:22:25 -03:00
show_document ( location ( 0 , 9 , 0 , 9 , true ), false , true )
2024-03-25 19:06:28 +00:00
local wins = api.nvim_list_wins ()
2022-10-08 05:22:25 -03:00
eq ( 2 , # wins )
table.sort ( wins )
2024-03-25 19:06:28 +00:00
eq ({ 1 , 0 }, api.nvim_win_get_cursor ( wins [ 1 ]))
eq ({ 1 , 9 }, api.nvim_win_get_cursor ( wins [ 2 ]))
2022-10-08 05:22:25 -03:00
end )
it ( 'does not change cursor of the split if not range and not focus' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_buf ( 0 , target_bufnr )
api.nvim_win_set_cursor ( 0 , { 2 , 3 })
2022-10-08 05:22:25 -03:00
exec_lua ( [[vim.cmd.new()]] )
2024-03-25 19:06:28 +00:00
api.nvim_win_set_buf ( 0 , target_bufnr2 )
api.nvim_win_set_cursor ( 0 , { 2 , 3 })
2022-10-08 05:22:25 -03:00
show_document ({ uri = 'file:///fake/uri2' }, false , true )
2024-03-25 19:06:28 +00:00
local wins = api.nvim_list_wins ()
2022-10-08 05:22:25 -03:00
eq ( 2 , # wins )
2024-03-25 19:06:28 +00:00
eq ({ 2 , 3 }, api.nvim_win_get_cursor ( wins [ 1 ]))
eq ({ 2 , 3 }, api.nvim_win_get_cursor ( wins [ 2 ]))
2022-10-08 05:22:25 -03:00
end )
it ( 'respects existing buffers' , function ()
2024-03-25 19:06:28 +00:00
api.nvim_win_set_buf ( 0 , target_bufnr )
local win = api.nvim_get_current_win ()
2022-10-08 05:22:25 -03:00
exec_lua ( [[vim.cmd.new()]] )
2024-03-25 19:06:28 +00:00
api.nvim_win_set_buf ( 0 , target_bufnr2 )
api.nvim_win_set_cursor ( 0 , { 2 , 3 })
local split = api.nvim_get_current_win ()
2022-10-08 05:22:25 -03:00
-- reuse win for open document/bufnr if called from split
show_document ( location ( 0 , 9 , 0 , 9 , true ), false , true )
2024-03-25 19:06:28 +00:00
eq ({ 1 , 9 }, api.nvim_win_get_cursor ( split ))
eq ( 2 , # api.nvim_list_wins ())
2022-10-08 05:22:25 -03:00
2024-03-25 19:06:28 +00:00
api.nvim_set_current_win ( win )
2022-10-08 05:22:25 -03:00
-- reuse win for open document/bufnr if called outside the split
show_document ( location ( 0 , 9 , 0 , 9 , true ), false , true )
2024-03-25 19:06:28 +00:00
eq ({ 1 , 9 }, api.nvim_win_get_cursor ( split ))
eq ( 2 , # api.nvim_list_wins ())
2022-10-08 05:22:25 -03:00
end )
end )
2020-06-04 20:23:03 +02:00
describe ( 'lsp.util._make_floating_popup_size' , function ()
2020-06-12 02:34:34 +02:00
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 )
2023-10-12 15:39:39 +08:00
it ( 'handles NUL bytes in text' , function ()
exec_lua ( [[ contents = {
'\000\001\002\003\004\005\006\007\008\009',
'\010\011\012\013\014\015\016\017\018\019',
'\020\021\022\023\024\025\026\027\028\029',
} ]] )
command ( 'set list listchars=' )
eq ({ 20 , 3 }, exec_lua [[ return {vim.lsp.util._make_floating_popup_size(contents)} ]] )
command ( 'set display+=uhex' )
eq ({ 40 , 3 }, exec_lua [[ return {vim.lsp.util._make_floating_popup_size(contents)} ]] )
end )
2020-06-04 20:23:03 +02:00
end )
2020-05-05 17:23:45 +02:00
2021-08-10 20:52:30 +02:00
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 )
2022-01-13 10:47:36 +01:00
describe ( 'lsp.util.convert_signature_help_to_markdown_lines' , function ()
it ( 'can handle negative activeSignature' , function ()
local result = exec_lua [[
local signature_help = {
activeParameter = 0,
activeSignature = -1,
signatures = {
{
2023-07-25 08:40:13 -04:00
documentation = "some doc",
2022-01-13 10:47:36 +01:00
label = "TestEntity.TestEntity()",
parameters = {}
},
}
}
return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','})
]]
2023-09-12 20:51:21 -07:00
local expected = { '```cs' , 'TestEntity.TestEntity()' , '```' , 'some doc' }
2022-01-13 10:47:36 +01:00
eq ( expected , result )
end )
end )
2020-05-05 17:23:45 +02:00
describe ( 'lsp.util.get_effective_tabstop' , function ()
2022-03-20 13:41:46 -04:00
local function test_tabstop ( tabsize , shiftwidth )
2020-05-05 17:23:45 +02:00
exec_lua ( string.format (
[[
2022-12-19 16:37:45 +00:00
vim.bo.shiftwidth = %d
vim.bo.tabstop = 2
2022-03-20 13:41:46 -04:00
]] ,
shiftwidth
))
2020-05-05 17:23:45 +02:00
eq ( tabsize , exec_lua ( 'return vim.lsp.util.get_effective_tabstop()' ))
end
2022-03-20 13:41:46 -04:00
it ( 'with shiftwidth = 1' , function ()
test_tabstop ( 1 , 1 )
end )
it ( 'with shiftwidth = 0' , function ()
test_tabstop ( 2 , 0 )
end )
2020-05-05 17:23:45 +02:00
end )
2020-07-18 21:10:09 +02:00
describe ( 'vim.lsp.buf.outgoing_calls' , function ()
it ( 'does nothing for an empty response' , function ()
local qflist_count = exec_lua ( [=[
2021-08-28 00:12:30 -04:00
require'vim.lsp.handlers'['callHierarchy/outgoingCalls'](nil, nil, {}, nil)
2020-07-18 21:10:09 +02:00
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"
}
} }
2021-02-23 00:02:51 +01:00
local handler = require'vim.lsp.handlers'['callHierarchy/outgoingCalls']
2021-08-28 00:12:30 -04:00
handler(nil, rust_analyzer_response, {})
2020-07-18 21:10:09 +02:00
return vim.fn.getqflist()
]=] )
local expected = {
{
bufnr = 2 ,
col = 5 ,
2021-07-31 16:49:25 -04:00
end_col = 0 ,
2020-07-18 21:10:09 +02:00
lnum = 4 ,
2021-07-31 16:49:25 -04:00
end_lnum = 0 ,
2020-07-18 21:10:09 +02:00
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 ( [=[
2021-08-28 00:12:30 -04:00
require'vim.lsp.handlers'['callHierarchy/incomingCalls'](nil, nil, {})
2020-07-18 21:10:09 +02:00
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
}
} }
} }
2021-02-23 00:02:51 +01:00
local handler = require'vim.lsp.handlers'['callHierarchy/incomingCalls']
2021-08-28 00:12:30 -04:00
handler(nil, rust_analyzer_response, {})
2020-07-18 21:10:09 +02:00
return vim.fn.getqflist()
]=] )
local expected = {
{
bufnr = 2 ,
col = 5 ,
2021-07-31 16:49:25 -04:00
end_col = 0 ,
2020-07-18 21:10:09 +02:00
lnum = 4 ,
2021-07-31 16:49:25 -04:00
end_lnum = 0 ,
2020-07-18 21:10:09 +02:00
module = '' ,
nr = 0 ,
pattern = '' ,
text = 'main' ,
type = '' ,
valid = 1 ,
vcol = 0 ,
},
}
eq ( expected , qflist )
end )
end )
2021-09-08 23:00:15 +08:00
2024-04-23 19:05:01 +02:00
describe ( 'vim.lsp.buf.typehierarchy subtypes' , function ()
2024-04-20 21:40:01 +08:00
it ( 'does nothing for an empty response' , function ()
local qflist_count = exec_lua ( [=[
require'vim.lsp.handlers'['typeHierarchy/subtypes'](nil, nil, {})
return #vim.fn.getqflist()
]=] )
eq ( 0 , qflist_count )
end )
it ( 'opens the quickfix list with the right subtypes' , function ()
clear ()
exec_lua ( create_server_definition )
local qflist = exec_lua ( [=[
local clangd_response = { {
data = {
parents = { {
parents = { {
parents = { {
parents = {},
symbolID = "62B3D268A01B9978"
} },
symbolID = "DC9B0AD433B43BEC"
} },
symbolID = "06B5F6A19BA9F6A8"
} },
symbolID = "EDC336589C09ABB2"
},
kind = 5,
name = "D2",
range = {
["end"] = {
character = 8,
line = 9
},
start = {
character = 6,
line = 9
}
},
selectionRange = {
["end"] = {
character = 8,
line = 9
},
start = {
character = 6,
line = 9
}
},
uri = "file:///home/jiangyinzuo/hello.cpp"
}, {
data = {
parents = { {
parents = { {
parents = { {
parents = {},
symbolID = "62B3D268A01B9978"
} },
symbolID = "DC9B0AD433B43BEC"
} },
symbolID = "06B5F6A19BA9F6A8"
} },
symbolID = "AFFCAED15557EF08"
},
kind = 5,
name = "D1",
range = {
["end"] = {
character = 8,
line = 8
},
start = {
character = 6,
line = 8
}
},
selectionRange = {
["end"] = {
character = 8,
line = 8
},
start = {
character = 6,
line = 8
}
},
uri = "file:///home/jiangyinzuo/hello.cpp"
} }
local server = _create_server({
capabilities = {
positionEncoding = "utf-8"
},
})
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
local handler = require'vim.lsp.handlers'['typeHierarchy/subtypes']
handler(nil, clangd_response, { client_id = client_id, bufnr = 1 })
return vim.fn.getqflist()
]=] )
local expected = {
{
bufnr = 2 ,
col = 7 ,
end_col = 0 ,
end_lnum = 0 ,
lnum = 10 ,
module = '' ,
nr = 0 ,
pattern = '' ,
text = 'D2' ,
type = '' ,
valid = 1 ,
vcol = 0 ,
},
{
bufnr = 2 ,
col = 7 ,
end_col = 0 ,
end_lnum = 0 ,
lnum = 9 ,
module = '' ,
nr = 0 ,
pattern = '' ,
text = 'D1' ,
type = '' ,
valid = 1 ,
vcol = 0 ,
},
}
eq ( expected , qflist )
end )
it ( 'opens the quickfix list with the right subtypes and details' , function ()
clear ()
exec_lua ( create_server_definition )
local qflist = exec_lua ( [=[
local jdtls_response = {
{
data = { element = '=hello-java_ed323c3c/_<{Main.java[Main[A' },
detail = '',
kind = 5,
name = 'A',
range = {
['end'] = { character = 26, line = 3 },
start = { character = 1, line = 3 },
},
selectionRange = {
['end'] = { character = 8, line = 3 },
start = { character = 7, line = 3 },
},
tags = {},
uri = 'file:///home/jiangyinzuo/hello-java/Main.java',
},
{
data = { element = '=hello-java_ed323c3c/_<mylist{MyList.java[MyList[Inner' },
detail = 'mylist',
kind = 5,
name = 'MyList$Inner',
range = {
['end'] = { character = 37, line = 3 },
start = { character = 1, line = 3 },
},
selectionRange = {
['end'] = { character = 19, line = 3 },
start = { character = 14, line = 3 },
},
tags = {},
uri = 'file:///home/jiangyinzuo/hello-java/mylist/MyList.java',
},
}
local server = _create_server({
capabilities = {
positionEncoding = "utf-8"
},
})
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
local handler = require'vim.lsp.handlers'['typeHierarchy/subtypes']
handler(nil, jdtls_response, { client_id = client_id, bufnr = 1 })
return vim.fn.getqflist()
]=] )
local expected = {
{
bufnr = 2 ,
col = 2 ,
end_col = 0 ,
end_lnum = 0 ,
lnum = 4 ,
module = '' ,
nr = 0 ,
pattern = '' ,
text = 'A' ,
type = '' ,
valid = 1 ,
vcol = 0 ,
},
{
bufnr = 3 ,
col = 2 ,
end_col = 0 ,
end_lnum = 0 ,
lnum = 4 ,
module = '' ,
nr = 0 ,
pattern = '' ,
text = 'MyList$Inner mylist' ,
type = '' ,
valid = 1 ,
vcol = 0 ,
},
}
eq ( expected , qflist )
end )
end )
2024-04-23 19:05:01 +02:00
describe ( 'vim.lsp.buf.typehierarchy supertypes' , function ()
2024-04-20 21:40:01 +08:00
it ( 'does nothing for an empty response' , function ()
local qflist_count = exec_lua ( [=[
require'vim.lsp.handlers'['typeHierarchy/supertypes'](nil, nil, {})
return #vim.fn.getqflist()
]=] )
eq ( 0 , qflist_count )
end )
it ( 'opens the quickfix list with the right supertypes' , function ()
clear ()
exec_lua ( create_server_definition )
local qflist = exec_lua ( [=[
local clangd_response = { {
data = {
parents = { {
parents = { {
parents = { {
parents = {},
symbolID = "62B3D268A01B9978"
} },
symbolID = "DC9B0AD433B43BEC"
} },
symbolID = "06B5F6A19BA9F6A8"
} },
symbolID = "EDC336589C09ABB2"
},
kind = 5,
name = "D2",
range = {
["end"] = {
character = 8,
line = 9
},
start = {
character = 6,
line = 9
}
},
selectionRange = {
["end"] = {
character = 8,
line = 9
},
start = {
character = 6,
line = 9
}
},
uri = "file:///home/jiangyinzuo/hello.cpp"
}, {
data = {
parents = { {
parents = { {
parents = { {
parents = {},
symbolID = "62B3D268A01B9978"
} },
symbolID = "DC9B0AD433B43BEC"
} },
symbolID = "06B5F6A19BA9F6A8"
} },
symbolID = "AFFCAED15557EF08"
},
kind = 5,
name = "D1",
range = {
["end"] = {
character = 8,
line = 8
},
start = {
character = 6,
line = 8
}
},
selectionRange = {
["end"] = {
character = 8,
line = 8
},
start = {
character = 6,
line = 8
}
},
uri = "file:///home/jiangyinzuo/hello.cpp"
} }
local server = _create_server({
capabilities = {
positionEncoding = "utf-8"
},
})
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
local handler = require'vim.lsp.handlers'['typeHierarchy/supertypes']
handler(nil, clangd_response, { client_id = client_id, bufnr = 1 })
return vim.fn.getqflist()
]=] )
local expected = {
{
bufnr = 2 ,
col = 7 ,
end_col = 0 ,
end_lnum = 0 ,
lnum = 10 ,
module = '' ,
nr = 0 ,
pattern = '' ,
text = 'D2' ,
type = '' ,
valid = 1 ,
vcol = 0 ,
},
{
bufnr = 2 ,
col = 7 ,
end_col = 0 ,
end_lnum = 0 ,
lnum = 9 ,
module = '' ,
nr = 0 ,
pattern = '' ,
text = 'D1' ,
type = '' ,
valid = 1 ,
vcol = 0 ,
},
}
eq ( expected , qflist )
end )
it ( 'opens the quickfix list with the right supertypes and details' , function ()
clear ()
exec_lua ( create_server_definition )
local qflist = exec_lua ( [=[
local jdtls_response = {
{
data = { element = '=hello-java_ed323c3c/_<{Main.java[Main[A' },
detail = '',
kind = 5,
name = 'A',
range = {
['end'] = { character = 26, line = 3 },
start = { character = 1, line = 3 },
},
selectionRange = {
['end'] = { character = 8, line = 3 },
start = { character = 7, line = 3 },
},
tags = {},
uri = 'file:///home/jiangyinzuo/hello-java/Main.java',
},
{
data = { element = '=hello-java_ed323c3c/_<mylist{MyList.java[MyList[Inner' },
detail = 'mylist',
kind = 5,
name = 'MyList$Inner',
range = {
['end'] = { character = 37, line = 3 },
start = { character = 1, line = 3 },
},
selectionRange = {
['end'] = { character = 19, line = 3 },
start = { character = 14, line = 3 },
},
tags = {},
uri = 'file:///home/jiangyinzuo/hello-java/mylist/MyList.java',
},
}
local server = _create_server({
capabilities = {
positionEncoding = "utf-8"
},
})
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
local handler = require'vim.lsp.handlers'['typeHierarchy/supertypes']
handler(nil, jdtls_response, { client_id = client_id, bufnr = 1 })
return vim.fn.getqflist()
]=] )
local expected = {
{
bufnr = 2 ,
col = 2 ,
end_col = 0 ,
end_lnum = 0 ,
lnum = 4 ,
module = '' ,
nr = 0 ,
pattern = '' ,
text = 'A' ,
type = '' ,
valid = 1 ,
vcol = 0 ,
},
{
bufnr = 3 ,
col = 2 ,
end_col = 0 ,
end_lnum = 0 ,
lnum = 4 ,
module = '' ,
nr = 0 ,
pattern = '' ,
text = 'MyList$Inner mylist' ,
type = '' ,
valid = 1 ,
vcol = 0 ,
},
}
eq ( expected , qflist )
end )
end )
2021-09-08 23:00:15 +08:00
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 , {}, { method = 'start' , client_id = 1 } },
},
},
}) do
it ( test.it , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-09-08 23:00:15 +08:00
test_rpc_server {
test_name = test.name ,
on_init = function ( _client )
client = _client
2022-04-30 02:22:30 -07:00
eq ( true , client.server_capabilities (). renameProvider.prepareProvider )
2021-09-08 23:00:15 +08:00
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 = {}
2021-11-07 16:13:53 +01:00
vim.fn.input = function(opts, on_confirm)
vim.lsp._stubs.input_prompt = opts.prompt
vim.lsp._stubs.input_text = opts.default
2021-09-08 23:00:15 +08:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-09-08 23:00:15 +08:00
end ,
on_handler = function ( err , result , ctx )
2023-06-11 15:23:37 +05:30
-- Don't compare & assert params and version, they're not relevant for the testcase
2021-03-11 23:49:59 +01:00
-- This allows us to be lazy and avoid declaring them
ctx.params = nil
2023-06-11 15:23:37 +05:30
ctx.version = nil
2024-01-03 02:09:18 +01:00
2021-09-08 23:00:15 +08:00
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' ))
2024-01-03 02:09:18 +01:00
end
2021-09-08 23:00:15 +08:00
client.stop ()
end
end ,
}
end )
end
end )
2021-03-12 10:19:21 +01:00
describe ( 'vim.lsp.buf.code_action' , function ()
it ( 'Calls client side command if available' , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-09-28 23:04:01 +02:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-09-28 23:04:01 +02:00
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
2021-03-12 10:19:21 +01:00
end ,
2021-09-28 23:04:01 +02:00
}
2021-03-12 10:19:21 +01:00
end )
2022-06-05 16:43:32 +02:00
it ( 'Calls workspace/executeCommand if no client side command' , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-06-05 16:43:32 +02:00
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{
NIL ,
{ command = 'dummy1' , title = 'Command 1' },
{ bufnr = 1 , method = 'workspace/executeCommand' , client_id = 1 },
},
{ NIL , {}, { method = 'start' , client_id = 1 } },
}
test_rpc_server ({
test_name = 'code_action_server_side_command' ,
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 )
ctx.params = nil -- don't compare in assert
2023-06-11 15:23:37 +05:30
ctx.version = nil
2022-06-05 16:43:32 +02:00
eq ( table.remove ( expected_handlers ), { err , result , ctx })
if ctx.method == 'start' then
exec_lua ( [[
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
client.stop ()
end
end ,
})
end )
2022-04-30 10:14:31 +02:00
it ( 'Filters and automatically applies action if requested' , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-04-30 10:14:31 +02:00
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
}
test_rpc_server {
test_name = 'code_action_filter' ,
on_init = function ( client_ )
client = client_
end ,
on_setup = function () end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2022-04-30 10:14:31 +02:00
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['preferred_command'] = function(cmd)
vim.lsp.commands['executed_preferred'] = function()
end
end
2023-06-17 08:01:31 +02:00
vim.lsp.commands['type_annotate_command'] = function(cmd)
vim.lsp.commands['executed_type_annotate'] = function()
2022-05-12 18:48:02 +02:00
end
end
2022-04-30 10:14:31 +02:00
local bufnr = vim.api.nvim_get_current_buf()
vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
vim.lsp.buf.code_action({ filter = function(a) return a.isPreferred end, apply = true, })
2022-05-12 18:48:02 +02:00
vim.lsp.buf.code_action({
2023-06-17 08:01:31 +02:00
-- expect to be returned actions 'type-annotate' and 'type-annotate.foo'
context = { only = { 'type-annotate' }, },
2022-05-12 18:48:02 +02:00
apply = true,
filter = function(a)
2023-06-17 08:01:31 +02:00
if a.kind == 'type-annotate.foo' then
vim.lsp.commands['filtered_type_annotate_foo'] = function() end
2022-05-12 18:48:02 +02:00
return false
2023-06-17 08:01:31 +02:00
elseif a.kind == 'type-annotate' then
2022-05-12 18:48:02 +02:00
return true
else
assert(nil, 'unreachable')
end
end,
})
2022-04-30 10:14:31 +02:00
]] )
elseif ctx.method == 'shutdown' then
eq ( 'function' , exec_lua [[return type(vim.lsp.commands['executed_preferred'])]] )
2023-06-17 08:01:31 +02:00
eq ( 'function' , exec_lua [[return type(vim.lsp.commands['filtered_type_annotate_foo'])]] )
eq ( 'function' , exec_lua [[return type(vim.lsp.commands['executed_type_annotate'])]] )
2022-04-30 10:14:31 +02:00
client.stop ()
end
end ,
}
end )
2023-10-02 22:14:19 +02:00
it ( 'Fallback to command execution on resolve error' , function ()
clear ()
exec_lua ( create_server_definition )
local result = exec_lua ( [[
local server = _create_server({
capabilities = {
executeCommandProvider = {
commands = {"command:1"},
},
codeActionProvider = {
resolveProvider = true
}
},
handlers = {
2024-05-23 09:17:53 -04:00
["textDocument/codeAction"] = function(_, _, callback)
callback(nil, {
2023-10-02 22:14:19 +02:00
{
title = "Code Action 1",
command = {
title = "Command 1",
command = "command:1",
}
}
2024-05-23 09:17:53 -04:00
})
2023-10-02 22:14:19 +02:00
end,
2024-05-23 09:17:53 -04:00
["codeAction/resolve"] = function(_, _, callback)
callback("resolve failed", nil)
2023-10-02 22:14:19 +02:00
end,
}
})
local client_id = vim.lsp.start({
name = "dummy",
cmd = server.cmd,
})
vim.lsp.buf.code_action({ apply = true })
vim.lsp.stop_client(client_id)
return server.messages
]] )
eq ( 'codeAction/resolve' , result [ 4 ]. method )
eq ( 'workspace/executeCommand' , result [ 5 ]. method )
eq ( 'command:1' , result [ 5 ]. params.command )
end )
2021-03-12 10:19:21 +01:00
end )
2024-03-25 19:06:28 +00:00
2021-03-12 10:19:21 +01:00
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 )
2024-03-25 19:06:28 +00:00
2021-11-01 03:14:59 -07:00
describe ( 'vim.lsp.codelens' , function ()
it ( 'uses client commands' , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2021-11-01 03:14:59 -07:00
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 )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2021-11-01 03:14:59 -07:00
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 )
2022-05-05 18:50:12 +02:00
it ( 'releases buffer refresh lock' , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-05-05 18:50:12 +02:00
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
}
test_rpc_server {
test_name = 'codelens_refresh_lock' ,
on_init = function ( client_ )
client = client_
end ,
on_setup = function ()
exec_lua ( [=[
local bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'One line'})
vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
CALLED = false
RESPONSE = nil
local on_codelens = vim.lsp.codelens.on_codelens
vim.lsp.codelens.on_codelens = function (err, result, ...)
CALLED = true
RESPONSE = { err = err, result = result }
return on_codelens(err, result, ...)
end
]=] )
end ,
on_exit = function ( code , signal )
2022-05-30 02:40:17 -07:00
eq ( 0 , code , 'exit code' )
eq ( 0 , signal , 'exit signal' )
2022-05-05 18:50:12 +02:00
end ,
on_handler = function ( err , result , ctx )
eq ( table.remove ( expected_handlers ), { err , result , ctx })
if ctx.method == 'start' then
-- 1. first codelens request errors
local response = exec_lua ( [=[
CALLED = false
vim.lsp.codelens.refresh()
vim.wait(100, function () return CALLED end)
return RESPONSE
]=] )
eq ({ err = { code = - 32002 , message = 'ServerNotInitialized' } }, response )
-- 2. second codelens request runs
response = exec_lua ( [=[
CALLED = false
local cmd_called = nil
vim.lsp.commands["Dummy"] = function (command)
cmd_called = command
end
vim.lsp.codelens.refresh()
vim.wait(100, function () return CALLED end)
vim.lsp.codelens.run()
vim.wait(100, function () return cmd_called end)
return cmd_called
]=] )
eq ({ command = 'Dummy' , title = 'Lens1' }, response )
-- 3. third codelens request runs
response = exec_lua ( [=[
CALLED = false
local cmd_called = nil
vim.lsp.commands["Dummy"] = function (command)
cmd_called = command
end
vim.lsp.codelens.refresh()
vim.wait(100, function () return CALLED end)
vim.lsp.codelens.run()
vim.wait(100, function () return cmd_called end)
return cmd_called
]=] )
eq ({ command = 'Dummy' , title = 'Lens2' }, response )
elseif ctx.method == 'shutdown' then
client.stop ()
end
end ,
}
end )
2024-03-18 05:04:59 +09:00
it ( 'refresh multiple buffers' , function ()
local lens_title_per_fake_uri = {
[ 'file:///fake/uri1' ] = 'Lens1' ,
[ 'file:///fake/uri2' ] = 'Lens2' ,
}
clear ()
exec_lua ( create_server_definition )
-- setup lsp
exec_lua (
[[
local lens_title_per_fake_uri = ...
local server = _create_server({
capabilities = {
codeLensProvider = {
resolveProvider = true
},
},
handlers = {
2024-05-23 09:17:53 -04:00
["textDocument/codeLens"] = function(method, params, callback)
2024-03-18 05:04:59 +09:00
local lenses = {
{
range = {
start = { line = 0, character = 0 },
['end'] = { line = 0, character = 0 },
},
command = {
title = lens_title_per_fake_uri[params.textDocument.uri],
command = 'Dummy',
},
},
}
2024-05-23 09:17:53 -04:00
callback(nil, lenses)
2024-03-18 05:04:59 +09:00
end,
}
})
CLIENT_ID = vim.lsp.start({
name = "dummy",
cmd = server.cmd,
})
]] ,
lens_title_per_fake_uri
)
-- create buffers and setup handler
exec_lua (
[[
local lens_title_per_fake_uri = ...
local default_buf = vim.api.nvim_get_current_buf()
for fake_uri, _ in pairs(lens_title_per_fake_uri) do
local bufnr = vim.uri_to_bufnr(fake_uri)
vim.api.nvim_set_current_buf(bufnr)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'Some contents'})
vim.lsp.buf_attach_client(bufnr, CLIENT_ID)
end
vim.api.nvim_buf_delete(default_buf, {force = true})
REQUEST_COUNT = vim.tbl_count(lens_title_per_fake_uri)
RESPONSES = {}
local on_codelens = vim.lsp.codelens.on_codelens
vim.lsp.codelens.on_codelens = function (err, result, ctx, ...)
table.insert(RESPONSES, { err = err, result = result, ctx = ctx })
return on_codelens(err, result, ctx, ...)
end
]] ,
lens_title_per_fake_uri
)
-- call codelens refresh
local cmds = exec_lua ( [[
RESPONSES = {}
vim.lsp.codelens.refresh()
vim.wait(100, function () return #RESPONSES >= REQUEST_COUNT end)
local cmds = {}
for _, resp in ipairs(RESPONSES) do
local uri = resp.ctx.params.textDocument.uri
cmds[uri] = resp.result[1].command
end
return cmds
]] )
eq ({ command = 'Dummy' , title = 'Lens1' }, cmds [ 'file:///fake/uri1' ])
eq ({ command = 'Dummy' , title = 'Lens2' }, cmds [ 'file:///fake/uri2' ])
end )
2021-11-01 03:14:59 -07:00
end )
2022-04-30 06:36:40 -07:00
describe ( 'vim.lsp.buf.format' , function ()
it ( 'Aborts with notify if no client matches filter' , function ()
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-04-30 06:36:40 -07:00
test_rpc_server {
test_name = 'basic_init' ,
on_init = function ( c )
client = c
end ,
on_handler = function ()
local notify_msg = exec_lua ( [[
local bufnr = vim.api.nvim_get_current_buf()
vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
local notify_msg
local notify = vim.notify
vim.notify = function(msg, log_level)
notify_msg = msg
end
vim.lsp.buf.format({ name = 'does-not-exist' })
vim.notify = notify
return notify_msg
]] )
eq ( '[LSP] Format request failed, no matching language servers.' , notify_msg )
client.stop ()
end ,
}
end )
it ( 'Sends textDocument/formatting request to format buffer' , function ()
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-04-30 06:36:40 -07:00
test_rpc_server {
test_name = 'basic_formatting' ,
on_init = function ( c )
client = c
end ,
on_handler = function ( _ , _ , ctx )
table.remove ( expected_handlers )
if ctx.method == 'start' then
local notify_msg = exec_lua ( [[
local bufnr = vim.api.nvim_get_current_buf()
vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
local notify_msg
local notify = vim.notify
vim.notify = function(msg, log_level)
notify_msg = msg
end
vim.lsp.buf.format({ bufnr = bufnr })
vim.notify = notify
return notify_msg
]] )
eq ( NIL , notify_msg )
elseif ctx.method == 'shutdown' then
client.stop ()
end
end ,
}
end )
2022-04-30 17:23:50 +02:00
it ( 'Can format async' , function ()
local expected_handlers = {
{ NIL , {}, { method = 'shutdown' , client_id = 1 } },
{ NIL , {}, { method = 'start' , client_id = 1 } },
}
2024-03-25 19:06:28 +00:00
local client --- @type vim.lsp.Client
2022-04-30 17:23:50 +02:00
test_rpc_server {
test_name = 'basic_formatting' ,
on_init = function ( c )
client = c
end ,
on_handler = function ( _ , _ , ctx )
table.remove ( expected_handlers )
if ctx.method == 'start' then
local result = exec_lua ( [[
local bufnr = vim.api.nvim_get_current_buf()
vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
local notify_msg
local notify = vim.notify
vim.notify = function(msg, log_level)
notify_msg = msg
end
local handler = vim.lsp.handlers['textDocument/formatting']
local handler_called = false
vim.lsp.handlers['textDocument/formatting'] = function(...)
handler_called = true
end
vim.lsp.buf.format({ bufnr = bufnr, async = true })
vim.wait(1000, function() return handler_called end)
vim.notify = notify
vim.lsp.handlers['textDocument/formatting'] = handler
return {notify = notify_msg, handler_called = handler_called}
]] )
eq ({ handler_called = true }, result )
elseif ctx.method == 'shutdown' then
client.stop ()
end
end ,
}
end )
2022-09-08 11:33:04 +02:00
it ( 'format formats range in visual mode' , function ()
2022-12-04 15:39:22 +01:00
exec_lua ( create_server_definition )
2022-09-08 11:33:04 +02:00
local result = exec_lua ( [[
2022-12-04 15:39:22 +01:00
local server = _create_server({ capabilities = {
documentFormattingProvider = true,
documentRangeFormattingProvider = true,
}})
2022-09-08 11:33:04 +02:00
local bufnr = vim.api.nvim_get_current_buf()
2022-12-04 15:39:22 +01:00
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
2022-09-08 11:33:04 +02:00
vim.api.nvim_win_set_buf(0, bufnr)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar'})
vim.api.nvim_win_set_cursor(0, { 1, 0 })
vim.cmd.normal('v')
vim.api.nvim_win_set_cursor(0, { 2, 3 })
vim.lsp.buf.format({ bufnr = bufnr, false })
2023-03-12 09:45:28 +01:00
vim.lsp.stop_client(client_id)
2022-12-04 15:39:22 +01:00
return server.messages
2022-09-08 11:33:04 +02:00
]] )
2022-12-08 10:55:01 +01:00
eq ( 'textDocument/rangeFormatting' , result [ 3 ]. method )
2022-09-08 11:33:04 +02:00
local expected_range = {
start = { line = 0 , character = 0 },
[ 'end' ] = { line = 1 , character = 4 },
}
2022-12-08 10:55:01 +01:00
eq ( expected_range , result [ 3 ]. params.range )
2022-09-08 11:33:04 +02:00
end )
2023-03-12 09:45:28 +01:00
it ( 'format formats range in visual line mode' , function ()
exec_lua ( create_server_definition )
local result = exec_lua ( [[
local server = _create_server({ capabilities = {
documentFormattingProvider = true,
documentRangeFormattingProvider = true,
}})
local bufnr = vim.api.nvim_get_current_buf()
local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
vim.api.nvim_win_set_buf(0, bufnr)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar baz'})
vim.api.nvim_win_set_cursor(0, { 1, 2 })
vim.cmd.normal('V')
vim.api.nvim_win_set_cursor(0, { 2, 1 })
vim.lsp.buf.format({ bufnr = bufnr, false })
-- Format again with visual lines going from bottom to top
-- Must result in same formatting
vim.cmd.normal("<ESC>")
vim.api.nvim_win_set_cursor(0, { 2, 1 })
vim.cmd.normal('V')
vim.api.nvim_win_set_cursor(0, { 1, 2 })
vim.lsp.buf.format({ bufnr = bufnr, false })
vim.lsp.stop_client(client_id)
return server.messages
]] )
local expected_methods = {
'initialize' ,
'initialized' ,
'textDocument/rangeFormatting' ,
'$/cancelRequest' ,
'textDocument/rangeFormatting' ,
'$/cancelRequest' ,
'shutdown' ,
'exit' ,
}
eq (
expected_methods ,
vim.tbl_map ( function ( x )
return x.method
end , result )
2024-01-03 02:09:18 +01:00
)
2023-03-12 09:45:28 +01:00
-- uses first column of start line and last column of end line
local expected_range = {
start = { line = 0 , character = 0 },
[ 'end' ] = { line = 1 , character = 7 },
}
eq ( expected_range , result [ 3 ]. params.range )
eq ( expected_range , result [ 5 ]. params.range )
end )
2023-01-24 06:15:43 +00:00
it ( 'Aborts with notify if no clients support requested method' , function ()
exec_lua ( create_server_definition )
exec_lua ( [[
vim.notify = function(msg, _)
notify_msg = msg
end
]] )
local fail_msg = '[LSP] Format request failed, no matching language servers.'
2024-03-25 19:06:28 +00:00
--- @param name string
--- @param formatting boolean
--- @param range_formatting boolean
2023-01-24 06:15:43 +00:00
local function check_notify ( name , formatting , range_formatting )
local timeout_msg = '[LSP][' .. name .. '] timeout'
exec_lua (
[[
local formatting, range_formatting, name = ...
local server = _create_server({ capabilities = {
documentFormattingProvider = formatting,
documentRangeFormattingProvider = range_formatting,
}})
vim.lsp.start({ name = name, cmd = server.cmd })
notify_msg = nil
vim.lsp.buf.format({ name = name, timeout_ms = 1 })
]] ,
formatting ,
range_formatting ,
name
)
eq ( formatting and timeout_msg or fail_msg , exec_lua ( 'return notify_msg' ))
exec_lua ( [[
notify_msg = nil
vim.lsp.buf.format({ name = name, timeout_ms = 1, range = {start={1, 0}, ['end']={1, 0}}})
]] )
eq ( range_formatting and timeout_msg or fail_msg , exec_lua ( 'return notify_msg' ))
end
check_notify ( 'none' , false , false )
check_notify ( 'formatting' , true , false )
check_notify ( 'rangeFormatting' , false , true )
check_notify ( 'both' , true , true )
end )
2022-04-30 06:36:40 -07:00
end )
2024-02-03 17:49:28 -05:00
describe ( 'vim.lsp.tagfunc' , function ()
before_each ( function ()
clear ()
---@type lsp.Location[]
local mock_locations = {
{
range = {
[ 'start' ] = { line = 5 , character = 23 },
[ 'end' ] = { line = 10 , character = 0 },
},
uri = 'test://buf' ,
},
{
range = {
[ 'start' ] = { line = 42 , character = 10 },
[ 'end' ] = { line = 44 , character = 0 },
},
uri = 'test://another-file' ,
},
}
exec_lua ( create_server_definition )
exec_lua (
[[
_G.mock_locations = ...
_G.server = _create_server({
---@type lsp.ServerCapabilities
capabilities = {
definitionProvider = true,
workspaceSymbolProvider = true,
},
handlers = {
---@return lsp.Location[]
2024-05-23 09:17:53 -04:00
['textDocument/definition'] = function(_, _, callback)
callback(nil, { _G.mock_locations[1] })
2024-02-03 17:49:28 -05:00
end,
---@return lsp.WorkspaceSymbol[]
2024-05-23 09:17:53 -04:00
['workspace/symbol'] = function(_, request, callback)
2024-02-03 17:49:28 -05:00
assert(request.query == 'foobar')
2024-05-23 09:17:53 -04:00
callback(nil, {
2024-02-03 17:49:28 -05:00
{
name = 'foobar',
kind = 13, ---@type lsp.SymbolKind
location = _G.mock_locations[1],
},
{
name = 'vim.foobar',
kind = 12, ---@type lsp.SymbolKind
location = _G.mock_locations[2],
}
2024-05-23 09:17:53 -04:00
})
2024-02-03 17:49:28 -05:00
end,
},
})
_G.client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
]] ,
mock_locations
)
end )
after_each ( function ()
exec_lua [[
vim.lsp.stop_client(_G.client_id)
]]
end )
it ( 'with flags=c, returns matching tags using textDocument/definition' , function ()
local result = exec_lua [[
return vim.lsp.tagfunc('foobar', 'c')
]]
eq ({
{
cmd = '/ \\ %6l \\ %1c/' , -- for location (5, 23)
filename = 'test://buf' ,
name = 'foobar' ,
},
}, result )
end )
it ( 'without flags=c, returns all matching tags using workspace/symbol' , function ()
local result = exec_lua [[
return vim.lsp.tagfunc('foobar', '')
]]
eq ({
{
cmd = '/ \\ %6l \\ %1c/' , -- for location (5, 23)
filename = 'test://buf' ,
kind = 'Variable' ,
name = 'foobar' ,
},
{
cmd = '/ \\ %43l \\ %1c/' , -- for location (42, 10)
filename = 'test://another-file' ,
kind = 'Function' ,
name = 'vim.foobar' ,
},
}, result )
end )
end )
2022-08-24 20:25:34 +02:00
describe ( 'cmd' , function ()
it ( 'can connect to lsp server via rpc.connect' , function ()
local result = exec_lua [[
2023-06-03 11:06:00 +01:00
local uv = vim.uv
2022-08-24 20:25:34 +02:00
local server = uv.new_tcp()
local init = nil
server:bind('127.0.0.1', 0)
server:listen(127, function(err)
assert(not err, err)
local socket = uv.new_tcp()
server:accept(socket)
socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body)
init = body
socket:close()
end))
end)
local port = server:getsockname().port
vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('127.0.0.1', port) })
vim.wait(1000, function() return init ~= nil end)
assert(init, "server must receive `initialize` request")
server:close()
server:shutdown()
return vim.json.decode(init)
]]
2024-03-11 22:23:14 +08:00
eq ( 'initialize' , result.method )
2022-08-24 20:25:34 +02:00
end )
2024-04-18 15:34:10 +02:00
it ( 'can connect to lsp server via pipe or domain_socket' , function ()
2024-03-25 19:06:28 +00:00
local tmpfile --- @type string
2024-01-02 04:08:36 -05:00
if is_os ( 'win' ) then
tmpfile = ' \\\\ . \\\\ pipe \\ pipe.test'
else
2024-01-12 11:28:20 +00:00
tmpfile = tmpname ()
2024-01-02 04:08:36 -05:00
os.remove ( tmpfile )
end
local result = exec_lua (
[[
local SOCK = ...
local uv = vim.uv
local server = uv.new_pipe(false)
server:bind(SOCK)
local init = nil
server:listen(127, function(err)
assert(not err, err)
local client = uv.new_pipe()
server:accept(client)
client:read_start(require("vim.lsp.rpc").create_read_loop(function(body)
init = body
client:close()
end))
end)
2024-04-18 15:34:10 +02:00
vim.lsp.start({ name = "dummy", cmd = vim.lsp.rpc.connect(SOCK) })
2024-01-02 04:08:36 -05:00
vim.wait(1000, function() return init ~= nil end)
assert(init, "server must receive `initialize` request")
server:close()
server:shutdown()
return vim.json.decode(init)
]] ,
tmpfile
)
2024-03-11 22:23:14 +08:00
eq ( 'initialize' , result.method )
2024-01-02 04:08:36 -05:00
end )
2022-08-24 20:25:34 +02:00
end )
2022-09-20 09:28:23 +02:00
describe ( 'handlers' , function ()
it ( 'handler can return false as response' , function ()
local result = exec_lua [[
2023-06-03 11:06:00 +01:00
local uv = vim.uv
2022-09-20 09:28:23 +02:00
local server = uv.new_tcp()
local messages = {}
local responses = {}
server:bind('127.0.0.1', 0)
server:listen(127, function(err)
assert(not err, err)
local socket = uv.new_tcp()
server:accept(socket)
socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body)
local payload = vim.json.decode(body)
if payload.method then
table.insert(messages, payload.method)
if payload.method == 'initialize' then
local msg = vim.json.encode({
id = payload.id,
jsonrpc = '2.0',
result = {
capabilities = {}
},
})
socket:write(table.concat({'Content-Length: ', tostring(#msg), '\r\n\r\n', msg}))
elseif payload.method == 'initialized' then
local msg = vim.json.encode({
id = 10,
jsonrpc = '2.0',
method = 'dummy',
params = {},
})
socket:write(table.concat({'Content-Length: ', tostring(#msg), '\r\n\r\n', msg}))
end
else
table.insert(responses, payload)
socket:close()
end
end))
end)
local port = server:getsockname().port
local handler_called = false
vim.lsp.handlers['dummy'] = function(err, result)
handler_called = true
return false
end
local client_id = vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('127.0.0.1', port) })
local client = vim.lsp.get_client_by_id(client_id)
vim.wait(1000, function() return #messages == 2 and handler_called and #responses == 1 end)
server:close()
server:shutdown()
return {
messages = messages,
handler_called = handler_called,
responses = responses }
]]
local expected = {
messages = { 'initialize' , 'initialized' },
handler_called = true ,
responses = {
{
id = 10 ,
jsonrpc = '2.0' ,
result = false ,
},
},
}
eq ( expected , result )
end )
end )
2023-03-05 00:52:27 -06:00
2023-05-28 07:51:28 +02:00
describe ( '#dynamic vim.lsp._dynamic' , function ()
it ( 'supports dynamic registration' , function ()
2023-12-22 15:03:13 +01:00
---@type string
2024-01-12 11:28:20 +00:00
local root_dir = tmpname ()
2023-05-28 07:51:28 +02:00
os.remove ( root_dir )
mkdir ( root_dir )
local tmpfile = root_dir .. '/dynamic.foo'
local file = io.open ( tmpfile , 'w' )
2023-12-22 15:03:13 +01:00
if file then
file : close ()
end
2023-05-28 07:51:28 +02:00
exec_lua ( create_server_definition )
local result = exec_lua (
[[
local root_dir, tmpfile = ...
local server = _create_server()
local client_id = vim.lsp.start({
name = 'dynamic-test',
cmd = server.cmd,
root_dir = root_dir,
2023-12-22 15:03:13 +01:00
get_language_id = function()
return "dummy-lang"
end,
2023-05-28 07:51:28 +02:00
capabilities = {
textDocument = {
formatting = {
dynamicRegistration = true,
},
rangeFormatting = {
dynamicRegistration = true,
},
},
},
})
local expected_messages = 2 -- initialize, initialized
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'formatting',
method = 'textDocument/formatting',
registerOptions = {
documentSelector = {{
pattern = root_dir .. '/*.foo',
}},
},
},
},
}, { client_id = client_id })
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'range-formatting',
method = 'textDocument/rangeFormatting',
2023-12-22 15:03:13 +01:00
registerOptions = {
documentSelector = {
{
language = "dummy-lang"
},
}
}
2023-05-28 07:51:28 +02:00
},
},
}, { client_id = client_id })
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'completion',
method = 'textDocument/completion',
},
},
}, { client_id = client_id })
local result = {}
local function check(method, fname)
local bufnr = fname and vim.fn.bufadd(fname) or nil
local client = vim.lsp.get_client_by_id(client_id)
2023-12-22 15:03:13 +01:00
result[#result + 1] = {
method = method,
fname = fname,
supported = client.supports_method(method, {bufnr = bufnr})
}
2023-05-28 07:51:28 +02:00
end
check("textDocument/formatting")
check("textDocument/formatting", tmpfile)
check("textDocument/rangeFormatting")
check("textDocument/rangeFormatting", tmpfile)
check("textDocument/completion")
return result
]] ,
root_dir ,
tmpfile
)
eq ( 5 , # result )
eq ({ method = 'textDocument/formatting' , supported = false }, result [ 1 ])
eq ({ method = 'textDocument/formatting' , supported = true , fname = tmpfile }, result [ 2 ])
eq ({ method = 'textDocument/rangeFormatting' , supported = true }, result [ 3 ])
eq ({ method = 'textDocument/rangeFormatting' , supported = true , fname = tmpfile }, result [ 4 ])
eq ({ method = 'textDocument/completion' , supported = false }, result [ 5 ])
end )
end )
2023-03-05 00:52:27 -06:00
describe ( 'vim.lsp._watchfiles' , function ()
2024-02-07 11:24:44 +00:00
local function test_filechanges ( watchfunc )
it (
string.format ( 'sends notifications when files change (watchfunc=%s)' , watchfunc ),
function ()
2024-02-07 11:28:35 +00:00
if watchfunc == 'fswatch' then
2024-03-29 06:26:01 +08:00
skip ( is_os ( 'win' ), 'not supported on windows' )
skip ( is_os ( 'mac' ), 'flaky test on mac' )
2024-02-07 11:28:35 +00:00
skip (
not is_ci () and fn.executable ( 'fswatch' ) == 0 ,
'fswatch not installed and not on CI'
)
end
2024-03-29 06:26:01 +08:00
if watchfunc == 'watch' then
skip ( is_os ( 'mac' ), 'flaky test on mac' )
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
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
local root_dir = tmpname ()
os.remove ( root_dir )
mkdir ( root_dir )
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
exec_lua ( create_server_definition )
local result = exec_lua (
[[
local root_dir, watchfunc = ...
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,
},
2023-05-20 00:45:39 -05:00
},
},
2024-02-07 11:24:44 +00:00
})
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
require('vim.lsp._watchfiles')._watchfunc = require('vim._watch')[watchfunc]
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
local expected_messages = 0
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
local msg_wait_timeout = watchfunc == 'watch' and 200 or 2500
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
local function wait_for_message(incr)
expected_messages = expected_messages + (incr or 1)
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_message(2) -- initialize, initialized
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-0',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = {
{
globPattern = '**/watch',
kind = 7,
},
2023-03-05 00:52:27 -06:00
},
},
},
},
2024-02-07 11:24:44 +00:00
}, { client_id = client_id })
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
if watchfunc ~= 'watch' then
vim.wait(100)
end
2023-03-26 04:41:27 -05:00
2024-02-07 11:24:44 +00:00
local path = root_dir .. '/watch'
local tmp = vim.fn.tempname()
io.open(tmp, 'w'):close()
vim.uv.fs_rename(tmp, path)
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
wait_for_message()
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
os.remove(path)
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
wait_for_message()
2023-03-05 00:52:27 -06:00
2024-02-07 13:05:33 +00:00
vim.lsp.stop_client(client_id)
2024-02-07 11:24:44 +00:00
return server.messages
]] ,
root_dir ,
watchfunc
)
2023-03-05 00:52:27 -06:00
2024-02-07 11:24:44 +00:00
local uri = vim.uri_from_fname ( root_dir .. '/watch' )
2023-03-05 00:52:27 -06:00
2024-02-07 13:05:33 +00:00
eq ( 6 , # result )
2024-02-07 11:24:44 +00:00
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
test_filechanges ( 'watch' )
test_filechanges ( 'watchdirs' )
2024-02-07 11:28:35 +00:00
test_filechanges ( 'fswatch' )
2023-03-05 00:52:27 -06:00
it ( 'correctly registers and unregisters' , function ()
2023-05-09 11:12:54 -05:00
local root_dir = '/some_dir'
2023-03-05 00:52:27 -06:00
exec_lua ( create_server_definition )
local result = exec_lua (
[[
local root_dir = ...
local server = _create_server()
local client_id = vim.lsp.start({
name = 'watchfiles-test',
cmd = server.cmd,
root_dir = root_dir,
2023-05-20 00:45:39 -05:00
capabilities = {
workspace = {
didChangeWatchedFiles = {
dynamicRegistration = true,
},
},
},
2023-03-05 00:52:27 -06:00
})
local expected_messages = 2 -- initialize, initialized
local function wait_for_messages()
assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
end
wait_for_messages()
local send_event
require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
2023-04-04 19:07:33 +02:00
local stopped = false
2023-03-05 00:52:27 -06:00
send_event = function(...)
2023-04-04 19:07:33 +02:00
if not stopped then
2023-03-05 00:52:27 -06:00
callback(...)
end
end
return function()
2023-04-04 19:07:33 +02:00
stopped = true
2023-03-05 00:52:27 -06:00
end
end
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-0',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = {
{
globPattern = '**/*.watch0',
},
},
},
},
},
}, { client_id = client_id })
send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created)
send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created)
expected_messages = expected_messages + 1
wait_for_messages()
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-1',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = {
{
globPattern = '**/*.watch1',
},
},
},
},
},
}, { client_id = client_id })
vim.lsp.handlers['client/unregisterCapability'](nil, {
unregisterations = {
{
id = 'watchfiles-test-0',
method = 'workspace/didChangeWatchedFiles',
},
},
}, { client_id = client_id })
send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created)
send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created)
expected_messages = expected_messages + 1
wait_for_messages()
return server.messages
]] ,
root_dir
)
local function watched_uri ( fname )
2024-03-25 19:06:28 +00:00
return vim.uri_from_fname ( root_dir .. '/' .. fname )
2023-03-05 00:52:27 -06:00
end
eq ( 4 , # result )
eq ( 'workspace/didChangeWatchedFiles' , result [ 3 ]. method )
eq ({
changes = {
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Created]] ),
uri = watched_uri ( 'file.watch0' ),
},
},
}, result [ 3 ]. params )
eq ( 'workspace/didChangeWatchedFiles' , result [ 4 ]. method )
eq ({
changes = {
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Created]] ),
uri = watched_uri ( 'file.watch1' ),
},
},
}, result [ 4 ]. params )
end )
it ( 'correctly handles the registered watch kind' , function ()
local root_dir = 'some_dir'
exec_lua ( create_server_definition )
local result = exec_lua (
[[
local root_dir = ...
local server = _create_server()
local client_id = vim.lsp.start({
name = 'watchfiles-test',
cmd = server.cmd,
root_dir = root_dir,
2023-05-20 00:45:39 -05:00
capabilities = {
workspace = {
didChangeWatchedFiles = {
dynamicRegistration = true,
},
},
},
2023-03-05 00:52:27 -06:00
})
local expected_messages = 2 -- initialize, initialized
local function wait_for_messages()
assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
end
wait_for_messages()
local watch_callbacks = {}
local function send_event(...)
for _, cb in ipairs(watch_callbacks) do
cb(...)
end
end
require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
table.insert(watch_callbacks, callback)
return function()
-- noop because this test never stops the watch
end
end
local protocol = require('vim.lsp.protocol')
local watchers = {}
local max_kind = protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete
for i = 0, max_kind do
table.insert(watchers, {
globPattern = {
2023-05-09 11:12:54 -05:00
baseUri = vim.uri_from_fname('/dir'),
2023-03-05 00:52:27 -06:00
pattern = 'watch'..tostring(i),
},
kind = i,
})
end
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-kind',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = watchers,
},
},
},
}, { client_id = client_id })
for i = 0, max_kind do
2023-05-09 11:12:54 -05:00
local filename = '/dir/watch' .. tostring(i)
2023-03-05 00:52:27 -06:00
send_event(filename, vim._watch.FileChangeType.Created)
send_event(filename, vim._watch.FileChangeType.Changed)
send_event(filename, vim._watch.FileChangeType.Deleted)
end
expected_messages = expected_messages + 1
wait_for_messages()
return server.messages
]] ,
root_dir
)
local function watched_uri ( fname )
2024-03-25 19:06:28 +00:00
return vim.uri_from_fname ( '/dir/' .. fname )
2023-03-05 00:52:27 -06:00
end
eq ( 3 , # result )
eq ( 'workspace/didChangeWatchedFiles' , result [ 3 ]. method )
eq ({
changes = {
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Created]] ),
uri = watched_uri ( 'watch1' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Changed]] ),
uri = watched_uri ( 'watch2' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Created]] ),
uri = watched_uri ( 'watch3' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Changed]] ),
uri = watched_uri ( 'watch3' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Deleted]] ),
uri = watched_uri ( 'watch4' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Created]] ),
uri = watched_uri ( 'watch5' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Deleted]] ),
uri = watched_uri ( 'watch5' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Changed]] ),
uri = watched_uri ( 'watch6' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Deleted]] ),
uri = watched_uri ( 'watch6' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Created]] ),
uri = watched_uri ( 'watch7' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Changed]] ),
uri = watched_uri ( 'watch7' ),
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Deleted]] ),
uri = watched_uri ( 'watch7' ),
},
},
}, result [ 3 ]. params )
end )
it ( 'prunes duplicate events' , function ()
local root_dir = 'some_dir'
exec_lua ( create_server_definition )
local result = exec_lua (
[[
local root_dir = ...
local server = _create_server()
local client_id = vim.lsp.start({
name = 'watchfiles-test',
cmd = server.cmd,
root_dir = root_dir,
2023-05-20 00:45:39 -05:00
capabilities = {
workspace = {
didChangeWatchedFiles = {
dynamicRegistration = true,
},
},
},
2023-03-05 00:52:27 -06:00
})
local expected_messages = 2 -- initialize, initialized
local function wait_for_messages()
assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
end
wait_for_messages()
local send_event
require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
send_event = callback
return function()
-- noop because this test never stops the watch
end
end
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-kind',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = {
{
globPattern = '**/*',
},
},
},
},
},
}, { client_id = client_id })
send_event('file1', vim._watch.FileChangeType.Created)
send_event('file1', vim._watch.FileChangeType.Created) -- pruned
send_event('file1', vim._watch.FileChangeType.Changed)
send_event('file2', vim._watch.FileChangeType.Created)
send_event('file1', vim._watch.FileChangeType.Changed) -- pruned
expected_messages = expected_messages + 1
wait_for_messages()
return server.messages
]] ,
root_dir
)
eq ( 3 , # result )
eq ( 'workspace/didChangeWatchedFiles' , result [ 3 ]. method )
eq ({
changes = {
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Created]] ),
2024-03-25 19:06:28 +00:00
uri = vim.uri_from_fname ( 'file1' ),
2023-03-05 00:52:27 -06:00
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Changed]] ),
2024-03-25 19:06:28 +00:00
uri = vim.uri_from_fname ( 'file1' ),
2023-03-05 00:52:27 -06:00
},
{
type = exec_lua ( [[return vim.lsp.protocol.FileChangeType.Created]] ),
2024-03-25 19:06:28 +00:00
uri = vim.uri_from_fname ( 'file2' ),
2023-03-05 00:52:27 -06:00
},
},
}, result [ 3 ]. params )
end )
2023-05-20 00:45:39 -05:00
it ( "ignores registrations by servers when the client doesn't advertise support" , function ()
exec_lua ( create_server_definition )
2023-08-04 07:10:54 +01:00
exec_lua ( [[
server = _create_server()
2023-05-20 00:45:39 -05:00
require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
-- Since the registration is ignored, this should not execute and `watching` should stay false
watching = true
return function() end
end
2023-08-04 07:10:54 +01:00
]] )
2023-05-20 00:45:39 -05:00
2023-08-04 07:10:54 +01:00
local function check_registered ( capabilities )
return exec_lua (
[[
watching = false
local client_id = vim.lsp.start({
name = 'watchfiles-test',
cmd = server.cmd,
root_dir = 'some_dir',
capabilities = ...,
}, {
reuse_client = function() return false end,
})
vim.lsp.handlers['client/registerCapability'](nil, {
registrations = {
{
id = 'watchfiles-test-kind',
method = 'workspace/didChangeWatchedFiles',
registerOptions = {
watchers = {
{
globPattern = '**/*',
},
2023-05-20 00:45:39 -05:00
},
},
},
},
2023-08-04 07:10:54 +01:00
}, { client_id = client_id })
-- Ensure no errors occur when unregistering something that was never really registered.
vim.lsp.handlers['client/unregisterCapability'](nil, {
unregisterations = {
{
id = 'watchfiles-test-kind',
method = 'workspace/didChangeWatchedFiles',
},
2023-05-20 00:45:39 -05:00
},
2023-08-04 07:10:54 +01:00
}, { client_id = client_id })
2023-05-20 00:45:39 -05:00
2023-08-04 07:10:54 +01:00
vim.lsp.stop_client(client_id, true)
return watching
]] ,
capabilities
)
end
2023-05-20 00:45:39 -05:00
2024-05-10 13:47:30 +01:00
eq ( is_os ( 'mac' ) or is_os ( 'win' ), check_registered ( nil )) -- start{_client}() defaults to make_client_capabilities().
2023-08-04 07:10:54 +01:00
eq ( false , check_registered ( vim.empty_dict ()))
eq (
false ,
check_registered ({
workspace = {
ignoreMe = true ,
},
})
2024-01-03 02:09:18 +01:00
)
2023-08-04 07:10:54 +01:00
eq (
false ,
check_registered ({
workspace = {
didChangeWatchedFiles = {
dynamicRegistration = false ,
},
},
})
2024-01-03 02:09:18 +01:00
)
2023-08-04 07:10:54 +01:00
eq (
true ,
check_registered ({
workspace = {
didChangeWatchedFiles = {
dynamicRegistration = true ,
},
},
})
2024-01-03 02:09:18 +01:00
)
2023-05-20 00:45:39 -05:00
end )
2023-03-05 00:52:27 -06:00
end )
2020-04-20 18:35:54 +03:00
end )