diff options
Diffstat (limited to 'test/functional/plugin/lsp_spec.lua')
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 3807 |
1 files changed, 2118 insertions, 1689 deletions
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index c95a96baca..9956fdf628 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -33,12 +33,38 @@ 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 +local create_tcp_echo_server = t_lsp.create_tcp_echo_server local function get_buf_option(name, bufnr) - bufnr = bufnr or 'BUFFER' - return exec_lua( - string.format("return vim.api.nvim_get_option_value('%s', { buf = %s })", name, bufnr) + return exec_lua(function() + bufnr = bufnr or _G.BUFFER + return vim.api.nvim_get_option_value(name, { buf = bufnr }) + end) +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 + +--- @param edits [integer, integer, integer, integer, string|string[]][] +--- @param encoding? string +local function apply_text_edits(edits, encoding) + local edits1 = vim.tbl_map( + --- @param edit [integer, integer, integer, integer, string|string[]] + function(edit) + return make_edit(unpack(edit)) + end, + edits ) + exec_lua(function() + vim.lsp.util.apply_text_edits(edits1, 1, encoding or 'utf-16') + end) end -- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837 @@ -46,111 +72,156 @@ if skip(is_os('win')) then return end -teardown(function() - os.remove(fake_lsp_logfile) -end) - describe('LSP', function() before_each(function() clear_notrace() - - -- Run an instance of nvim on the file which contains our "scripts". - -- Pass TEST_NAME to pick the script. - local test_name = 'basic_init' - exec_lua( - [=[ - lsp = require('vim.lsp') - local test_name, fake_lsp_code, fake_lsp_logfile = ... - function test__start_client() - return lsp.start_client { - cmd_env = { - NVIM_LOG_FILE = fake_lsp_logfile; - NVIM_APPNAME = "nvim_lsp_test"; - }; - cmd = { - vim.v.progpath, '-l', fake_lsp_code, test_name; - }; - workspace_folders = {{ - uri = 'file://' .. vim.uv.cwd(), - name = 'test_folder', - }}; - } - end - TEST_CLIENT1 = test__start_client() - ]=], - test_name, - fake_lsp_code, - fake_lsp_logfile - ) end) after_each(function() + stop() + exec_lua('lsp.stop_client(lsp.get_clients(), true)') api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) - -- exec_lua("lsp.stop_all_clients(true)") + end) + + teardown(function() + os.remove(fake_lsp_logfile) end) describe('server_name specified', function() + before_each(function() + -- 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(function() + _G.lsp = require('vim.lsp') + function _G.test__start_client() + return vim.lsp.start_client { + cmd_env = { + NVIM_LOG_FILE = fake_lsp_logfile, + NVIM_APPNAME = 'nvim_lsp_test', + }, + cmd = { + vim.v.progpath, + '-l', + fake_lsp_code, + test_name, + }, + workspace_folders = { + { + uri = 'file://' .. vim.uv.cwd(), + name = 'test_folder', + }, + }, + } + end + _G.TEST_CLIENT1 = _G.test__start_client() + end) + end) + it('start_client(), stop_client()', function() retry(nil, 4000, function() - eq(1, exec_lua('return #lsp.get_clients()')) + eq( + 1, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) eq( 2, - exec_lua([[ - TEST_CLIENT2 = test__start_client() - return TEST_CLIENT2 - ]]) + exec_lua(function() + _G.TEST_CLIENT2 = _G.test__start_client() + return _G.TEST_CLIENT2 + end) ) eq( 3, - exec_lua([[ - TEST_CLIENT3 = test__start_client() - return TEST_CLIENT3 - ]]) + exec_lua(function() + _G.TEST_CLIENT3 = _G.test__start_client() + return _G.TEST_CLIENT3 + end) ) retry(nil, 4000, function() - eq(3, exec_lua('return #lsp.get_clients()')) + eq( + 3, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) 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()') + eq( + false, + exec_lua(function() + return vim.lsp.get_client_by_id(_G.TEST_CLIENT1) == nil + end) + ) + eq( + false, + exec_lua(function() + return vim.lsp.get_client_by_id(_G.TEST_CLIENT1).is_stopped() + end) + ) + exec_lua(function() + return vim.lsp.get_client_by_id(_G.TEST_CLIENT1).stop() + end) retry(nil, 4000, function() - eq(2, exec_lua('return #lsp.get_clients()')) + eq( + 2, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) - eq(true, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil')) + eq( + true, + exec_lua(function() + return vim.lsp.get_client_by_id(_G.TEST_CLIENT1) == nil + end) + ) - exec_lua('lsp.stop_client({TEST_CLIENT2, TEST_CLIENT3})') + exec_lua(function() + vim.lsp.stop_client({ _G.TEST_CLIENT2, _G.TEST_CLIENT3 }) + end) retry(nil, 4000, function() - eq(0, exec_lua('return #lsp.get_clients()')) + eq( + 0, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) end) it('stop_client() also works on client objects', function() - exec_lua([[ - TEST_CLIENT2 = test__start_client() - TEST_CLIENT3 = test__start_client() - ]]) + exec_lua(function() + _G.TEST_CLIENT2 = _G.test__start_client() + _G.TEST_CLIENT3 = _G.test__start_client() + end) retry(nil, 4000, function() - eq(3, exec_lua('return #lsp.get_clients()')) + eq( + 3, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) -- Stop all clients. - exec_lua('lsp.stop_client(lsp.get_clients())') + exec_lua(function() + vim.lsp.stop_client(vim.lsp.get_clients()) + end) retry(nil, 4000, function() - eq(0, exec_lua('return #lsp.get_clients()')) + eq( + 0, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) end) end) -end) -describe('LSP', function() describe('basic_init test', function() - after_each(function() - stop() - exec_lua('lsp.stop_client(lsp.get_clients(), true)') - api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) - end) - it('should run correctly', function() local expected_handlers = { { NIL, {}, { method = 'test', client_id = 1 } }, @@ -221,28 +292,28 @@ describe('LSP', function() function() clear() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server({ - capabilities = { - positionEncoding = "utf-8" - }, - }) + local result = exec_lua(function() + local server = _G._create_server({ + capabilities = { + positionEncoding = 'utf-8', + }, + }) - local client_id = vim.lsp.start({ - name = 'dummy', - cmd = server.cmd, - }) + 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 + 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 - ]]) + 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 + end) eq('utf-8', result) end ) @@ -255,7 +326,7 @@ describe('LSP', function() return end local expected_handlers = { - { NIL, {}, { method = 'shutdown', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'shutdown', bufnr = 1, client_id = 1, version = 0 } }, { NIL, {}, { method = 'test', client_id = 1 } }, } test_rpc_server { @@ -285,14 +356,24 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_finish', on_setup = function() - 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") - ]] + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + end) + eq( + true, + exec_lua(function() + return vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) + ) + eq( + true, + exec_lua(function() + return vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) + ) + exec_lua(function() + vim.cmd(_G.BUFFER .. 'bwipeout') + end) end, on_init = function(_client) client = _client @@ -305,8 +386,15 @@ describe('LSP', function() 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)')) + exec_lua(function() + return vim.lsp.buf_detach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) + eq( + false, + exec_lua(function() + return vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) + ) client.stop() end end, @@ -318,31 +406,38 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) + exec_lua(function() + _G.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 + local client0 = assert(vim.lsp.get_client_by_id(args.data.client_id)) + vim.g.lsp_attached = client0.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 + local client0 = assert(vim.lsp.get_client_by_id(args.data.client_id)) + vim.g.lsp_detached = client0.name end, }) - ]] + end) end, on_init = function(_client) client = _client - eq(true, exec_lua('return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)')) + eq( + true, + exec_lua(function() + return vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) + ) client.notify('finish') end, on_handler = function(_, _, ctx) if ctx.method == 'finish' then eq('basic_init', api.nvim_get_var('lsp_attached')) - exec_lua('return lsp.buf_detach_client(BUFFER, TEST_RPC_CLIENT_ID)') + exec_lua(function() + return vim.lsp.buf_detach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) eq('basic_init', api.nvim_get_var('lsp_detached')) client.stop() end @@ -356,10 +451,10 @@ describe('LSP', function() test_name = 'set_defaults_all_capabilities', on_init = function(_client) client = _client - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - ]] + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) end, on_handler = function(_, _, ctx) if ctx.method == 'test' then @@ -369,13 +464,13 @@ describe('LSP', function() 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) + exec_lua(function() + local keymap --- @type table<string,any> + vim._with({ buf = _G.BUFFER }, function() + keymap = vim.fn.maparg('K', 'n', false, true) + end) + return keymap.callback == vim.lsp.buf.hover end) - return keymap.callback == vim.lsp.buf.hover - ]] ) client.stop() end @@ -386,13 +481,13 @@ describe('LSP', function() eq('', get_buf_option('formatexpr')) eq( '', - exec_lua [[ - local keymap - vim.api.nvim_buf_call(BUFFER, function() - keymap = vim.fn.maparg("K", "n", false, false) + exec_lua(function() + local keymap --- @type string + vim._with({ buf = _G.BUFFER }, function() + keymap = vim.fn.maparg('K', 'n', false, false) + end) + return keymap end) - return keymap - ]] ) end, } @@ -400,40 +495,42 @@ describe('LSP', function() it('should overwrite options set by ftplugins', function() local client --- @type vim.lsp.Client + local BUFFER_1 --- @type integer + local BUFFER_2 --- @type integer test_rpc_server { test_name = 'set_defaults_all_capabilities', on_init = function(_client) client = _client - exec_lua [[ + exec_lua(function() 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) vim.api.nvim_set_option_value('filetype', 'man', { buf = BUFFER_1 }) vim.api.nvim_set_option_value('filetype', 'xml', { buf = BUFFER_2 }) - ]] + end) -- Sanity check to ensure that some values are set after setting filetype. - 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')) + 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)) - exec_lua [[ - lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID) - lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID) - ]] + exec_lua(function() + vim.lsp.buf_attach_client(BUFFER_1, _G.TEST_RPC_CLIENT_ID) + vim.lsp.buf_attach_client(BUFFER_2, _G.TEST_RPC_CLIENT_ID) + end) 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')) + 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')) + eq('', get_buf_option('tagfunc', BUFFER_1)) + eq('', get_buf_option('omnifunc', BUFFER_2)) + eq('', get_buf_option('formatexpr', BUFFER_2)) end, } end) @@ -444,13 +541,13 @@ describe('LSP', function() test_name = 'set_defaults_all_capabilities', on_init = function(_client) client = _client - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - 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 }) - lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - ]] + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_option_value('tagfunc', 'tfu', { buf = _G.BUFFER }) + vim.api.nvim_set_option_value('omnifunc', 'ofu', { buf = _G.BUFFER }) + vim.api.nvim_set_option_value('formatexpr', 'fex', { buf = _G.BUFFER }) + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) end, on_handler = function(_, _, ctx) if ctx.method == 'test' then @@ -471,19 +568,19 @@ describe('LSP', function() it('should detach buffer on bufwipe', function() clear() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server() + local result = exec_lua(function() + local server = _G._create_server() local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_set_current_buf(bufnr) local detach_called = false - vim.api.nvim_create_autocmd("LspDetach", { + vim.api.nvim_create_autocmd('LspDetach', { callback = function() detach_called = true - end + end, }) local client_id = vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd }) - assert(client_id, "lsp.start must return client_id") - local client = vim.lsp.get_client_by_id(client_id) + assert(client_id, 'lsp.start must return client_id') + local client = assert(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) @@ -494,7 +591,7 @@ describe('LSP', function() num_attached_after = num_attached_after, detach_called = detach_called, } - ]]) + end) eq(true, result ~= nil, 'exec_lua must return result') eq(1, result.num_attached_before) eq(0, result.num_attached_after) @@ -504,30 +601,62 @@ describe('LSP', function() 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({ + exec_lua(function() + local server = _G._create_server({ handlers = { - initialize = function(method, params, callback) + initialize = function(_, _, callback) vim.schedule(function() callback(nil, { capabilities = {} }) end) - end - } + end, + }, }) local bufnr = vim.api.nvim_create_buf(false, true) local on_init_called = false - local client_id = vim.lsp.start({ + local client_id = assert(vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd, on_init = function() vim.api.nvim_buf_delete(bufnr, {}) on_init_called = true - end - }) + 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") - ]]) + local ok = vim.wait(1000, function() + return on_init_called + end) + assert(ok, 'on_init was not called') + end) + end) + + it('should allow on_lines + nvim_buf_delete during LSP initialization #28575', function() + clear() + exec_lua(create_server_definition) + exec_lua(function() + local initialized = false + local server = _G._create_server({ + handlers = { + initialize = function(_, _, 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) + 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) end) it('client should return settings via workspace/configuration handler', function() @@ -560,19 +689,25 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'start' then - exec_lua([=[ - local client = vim.lsp.get_client_by_id(TEST_RPC_CLIENT_ID) - client.settings = { - testSetting1 = true; - testSetting2 = false; - test = {Setting3 = 'nested' }; - }]=]) + exec_lua(function() + local client0 = vim.lsp.get_client_by_id(_G.TEST_RPC_CLIENT_ID) + client0.settings = { + testSetting1 = true, + testSetting2 = false, + test = { Setting3 = 'nested' }, + } + end) end if ctx.method == 'workspace/configuration' then local server_result = exec_lua( - [=[ + [[ local method, params = ... - return require'vim.lsp.handlers'['workspace/configuration'](err, params, {method=method, client_id=TEST_RPC_CLIENT_ID})]=], + return require 'vim.lsp.handlers'['workspace/configuration']( + err, + params, + { method = method, client_id = _G.TEST_RPC_CLIENT_ID } + ) + ]], ctx.method, result ) @@ -584,6 +719,7 @@ describe('LSP', function() end, } end) + it( 'workspace/configuration returns NIL per section if client was started without config.settings', function() @@ -594,15 +730,19 @@ describe('LSP', function() c.stop() end, on_setup = function() - result = exec_lua [[ - local result = { - items = { - {section = 'foo'}, - {section = 'bar'}, + result = exec_lua(function() + local result0 = { + items = { + { section = 'foo' }, + { section = 'bar' }, + }, } - } - return vim.lsp.handlers['workspace/configuration'](nil, result, {client_id=TEST_RPC_CLIENT_ID}) - ]] + return vim.lsp.handlers['workspace/configuration']( + nil, + result0, + { client_id = _G.TEST_RPC_CLIENT_ID } + ) + end) end, } eq({ NIL, NIL }, result) @@ -617,7 +757,9 @@ describe('LSP', function() test_name = 'basic_check_capabilities', on_init = function(client) client.stop() - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq({ includeText = false }, client.server_capabilities().textDocumentSync.save) eq(false, client.server_capabilities().codeLensProvider) @@ -650,11 +792,11 @@ describe('LSP', function() 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) - vim.api.nvim_exec_autocmds('BufWritePost', { buffer = BUFFER, modeline = false }) - ]=]) + exec_lua(function() + _G.BUFFER = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) + end) else client.stop() end @@ -665,21 +807,21 @@ describe('LSP', function() it('BufWritePre does not send notifications if server lacks willSave capabilities', function() clear() exec_lua(create_server_definition) - local messages = exec_lua([[ - local server = _create_server({ + local messages = exec_lua(function() + local server = _G._create_server({ capabilities = { textDocumentSync = { willSave = false, willSaveWaitUntil = false, - } + }, }, }) - local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local client_id = assert(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 - ]]) + end) eq(4, #messages) eq('initialize', messages[1].method) eq('initialized', messages[2].method) @@ -690,13 +832,13 @@ describe('LSP', function() it('BufWritePre sends willSave / willSaveWaitUntil, applies textEdits', function() clear() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server({ + local result = exec_lua(function() + local server = _G._create_server({ capabilities = { textDocumentSync = { willSave = true, willSaveWaitUntil = true, - } + }, }, handlers = { ['textDocument/willSaveWaitUntil'] = function(_, _, callback) @@ -705,21 +847,21 @@ describe('LSP', function() start = { line = 0, character = 0 }, ['end'] = { line = 0, character = 0 }, }, - newText = 'Hello' + newText = 'Hello', } - callback(nil, { text_edit, }) - end + callback(nil, { text_edit }) + end, }, }) local buf = vim.api.nvim_get_current_buf() - local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local client_id = assert(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) + lines = vim.api.nvim_buf_get_lines(buf, 0, -1, true), } - ]]) + end) local messages = result.messages eq('textDocument/willSave', messages[3].method) eq('textDocument/willSaveWaitUntil', messages[4].method) @@ -745,20 +887,16 @@ describe('LSP', function() eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'start' then local tmpfile_old = tmpname() - local tmpfile_new = tmpname() - 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 - ) + local tmpfile_new = tmpname(false) + exec_lua(function() + _G.BUFFER = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_name(_G.BUFFER, tmpfile_old) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, true, { 'help me' }) + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + vim._with({ buf = _G.BUFFER }, function() + vim.cmd('saveas ' .. tmpfile_new) + end) + end) else client.stop() end @@ -784,12 +922,12 @@ describe('LSP', function() 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) - vim.api.nvim_exec_autocmds('BufWritePost', { buffer = BUFFER, modeline = false }) - ]=]) + exec_lua(function() + _G.BUFFER = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, true, { 'help me' }) + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) + end) else client.stop() end @@ -843,16 +981,18 @@ describe('LSP', function() test_rpc_server { test_name = 'capabilities_for_client_supports_method', on_setup = function() - exec_lua([=[ - BUFFER = vim.api.nvim_get_current_buf() - lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - vim.lsp.handlers['textDocument/typeDefinition'] = function() end - vim.cmd(BUFFER.."bwipeout") - ]=]) + exec_lua(function() + _G.BUFFER = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + vim.lsp.handlers['textDocument/typeDefinition'] = function() end + vim.cmd(_G.BUFFER .. 'bwipeout') + end) end, on_init = function(client) client.stop() - exec_lua('vim.lsp.buf.type_definition()') + exec_lua(function() + vim.lsp.buf.type_definition() + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -873,13 +1013,15 @@ describe('LSP', function() test_rpc_server { test_name = 'capabilities_for_client_supports_method', on_setup = function() - exec_lua([=[ + exec_lua(function() vim.lsp.handlers['textDocument/typeDefinition'] = function() end - ]=]) + end) end, on_init = function(client) client.stop() - exec_lua('vim.lsp.buf.type_definition()') + exec_lua(function() + vim.lsp.buf.type_definition() + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -920,7 +1062,11 @@ describe('LSP', function() it('should forward ContentModified to callback', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { { code = -32801 }, NIL, { method = 'error_code_test', bufnr = 1, client_id = 1 } }, + { + { code = -32801 }, + NIL, + { method = 'error_code_test', bufnr = 1, client_id = 1, version = 0 }, + }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -950,7 +1096,7 @@ describe('LSP', function() it('should track pending requests to the language server', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 0 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -958,7 +1104,9 @@ describe('LSP', function() on_init = function(_client) client = _client client.request('slow_request') - local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=]) + local request = exec_lua(function() + return _G.TEST_RPC_CLIENT.requests[2] + end) eq('slow_request', request.method) eq('pending', request.type) client.notify('release') @@ -971,8 +1119,10 @@ describe('LSP', function() 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) + local request = exec_lua(function() + return _G.TEST_RPC_CLIENT.requests[2] + end) + eq(nil, request) client.notify('finish') end if ctx.method == 'finish' then @@ -993,7 +1143,9 @@ describe('LSP', function() client = _client client.request('slow_request') client.cancel_request(2) - local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=]) + local request = exec_lua(function() + return _G.TEST_RPC_CLIENT.requests[2] + end) eq('slow_request', request.method) eq('cancel', request.type) client.notify('release') @@ -1005,8 +1157,10 @@ describe('LSP', function() 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) + local request = exec_lua(function() + return _G.TEST_RPC_CLIENT.requests[2] + end) + eq(nil, request) if ctx.method == 'finish' then client.stop() end @@ -1017,7 +1171,7 @@ describe('LSP', function() it('should clear pending and cancel requests on reply', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 0 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1025,11 +1179,15 @@ describe('LSP', function() on_init = function(_client) client = _client client.request('slow_request') - local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=]) + local request = exec_lua(function() + return _G.TEST_RPC_CLIENT.requests[2] + end) eq('slow_request', request.method) eq('pending', request.type) client.cancel_request(2) - request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=]) + request = exec_lua(function() + return _G.TEST_RPC_CLIENT.requests[2] + end) eq('slow_request', request.method) eq('cancel', request.type) client.notify('release') @@ -1042,8 +1200,10 @@ describe('LSP', function() 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) + local request = exec_lua(function() + return _G.TEST_RPC_CLIENT.requests[2] + end) + eq(nil, request) client.notify('finish') end if ctx.method == 'finish' then @@ -1056,7 +1216,7 @@ describe('LSP', function() it('should trigger LspRequest autocmd when requests table changes', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 0 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1098,21 +1258,23 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_finish', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - 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") - ]] + assert(_G.TEST_RPC_CLIENT_ID == 1) + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + assert(vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + vim.cmd(_G.BUFFER .. 'bwipeout') + end) end, on_init = function(_client) client = _client - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) client.notify('finish') @@ -1140,25 +1302,30 @@ describe('LSP', function() 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(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + end) + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_init = function(_client) client = _client - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Already attached, returns true") - ]] + exec_lua(function() + assert( + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID), + 'Already attached, returns true' + ) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1186,22 +1353,24 @@ describe('LSP', function() 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(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1229,22 +1398,24 @@ describe('LSP', function() 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"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1252,11 +1423,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + 'boop', }) - ]] + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1277,23 +1448,25 @@ describe('LSP', function() 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"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - vim.bo[BUFFER].eol = false - ]] + vim.bo[_G.BUFFER].eol = false + end) end, on_init = function(_client) client = _client - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1301,11 +1474,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + 'boop', }) - ]] + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1336,6 +1509,7 @@ describe('LSP', function() }, bufnr = 2, client_id = 1, + version = 0, }, }, { NIL, {}, { method = 'start', client_id = 1 } }, @@ -1344,21 +1518,21 @@ describe('LSP', function() 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"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - vim.bo[BUFFER].eol = false - ]] + vim.bo[_G.BUFFER].eol = false + end) 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)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1366,9 +1540,9 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.lsp.inlay_hint.enable(true, { bufnr = BUFFER }) - ]] + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { bufnr = _G.BUFFER }) + end) end if ctx.method == 'textDocument/inlayHint' then client.notify('finish') @@ -1394,23 +1568,24 @@ describe('LSP', function() 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"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local sync_kind = - exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + local sync_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Incremental + end) eq(sync_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1418,11 +1593,11 @@ describe('LSP', function() 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"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + '123boop', }) - ]] + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1432,6 +1607,7 @@ describe('LSP', function() end, } end) + it('should check the body and didChange incremental with debounce', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, @@ -1446,23 +1622,24 @@ describe('LSP', function() debounce_text_changes = 5, }, 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(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local sync_kind = - exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + local sync_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Incremental + end) eq(sync_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1470,11 +1647,11 @@ describe('LSP', function() 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"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + '123boop', }) - ]] + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1496,23 +1673,24 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_buffer_open_and_change_incremental_editing', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local sync_kind = - exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + local sync_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Incremental + end) eq(sync_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1541,22 +1719,24 @@ describe('LSP', function() 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"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local sync_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(sync_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1564,14 +1744,14 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "321"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + '321', }) - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + 'boop', }) - ]] + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1592,22 +1772,24 @@ describe('LSP', function() 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"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local sync_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(sync_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1615,15 +1797,15 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "321"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + '321', }) - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + 'boop', }) - vim.api.nvim_command(BUFFER.."bwipeout") - ]] + vim.api.nvim_command(_G.BUFFER .. 'bwipeout') + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1679,19 +1861,19 @@ describe('LSP', function() 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"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1706,22 +1888,58 @@ describe('LSP', function() } end) end) -end) -describe('LSP', function() - before_each(function() - clear_notrace() - end) + describe('apply vscode text_edits', function() + it('single replace', function() + insert('012345678901234567890123456789') + apply_text_edits({ + { 0, 3, 0, 6, { 'Hello' } }, + }) + eq({ '012Hello678901234567890123456789' }, buf_lines(1)) + 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 + it('two replaces', function() + insert('012345678901234567890123456789') + apply_text_edits({ + { 0, 3, 0, 6, { 'Hello' } }, + { 0, 6, 0, 9, { 'World' } }, + }) + eq({ '012HelloWorld901234567890123456789' }, buf_lines(1)) + end) + + it('same start pos insert are kept in order', function() + insert('012345678901234567890123456789') + apply_text_edits({ + { 0, 3, 0, 3, { 'World' } }, + { 0, 3, 0, 3, { 'Hello' } }, + }) + eq({ '012WorldHello345678901234567890123456789' }, buf_lines(1)) + end) + + it('same start pos insert and replace are kept in order', function() + insert('012345678901234567890123456789') + apply_text_edits({ + { 0, 3, 0, 3, { 'World' } }, + { 0, 3, 0, 3, { 'Hello' } }, + { 0, 3, 0, 8, { 'No' } }, + }) + eq({ '012WorldHelloNo8901234567890123456789' }, buf_lines(1)) + end) + + it('multiline', function() + exec_lua(function() + vim.api.nvim_buf_set_lines(1, 0, 0, true, { ' {', ' "foo": "bar"', ' }' }) + end) + eq({ ' {', ' "foo": "bar"', ' }', '' }, buf_lines(1)) + apply_text_edits({ + { 0, 0, 3, 0, { '' } }, + { 3, 0, 3, 0, { '{\n' } }, + { 3, 0, 3, 0, { ' "foo": "bar"\n' } }, + { 3, 0, 3, 0, { '}\n' } }, + }) + eq({ '{', ' "foo": "bar"', '}', '' }, buf_lines(1)) + end) + end) describe('apply_text_edits', function() before_each(function() @@ -1732,14 +1950,14 @@ describe('LSP', function() Fourth line of text aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄]])) end) + it('applies simple edits', function() - local edits = { - make_edit(0, 0, 0, 0, { '123' }), - make_edit(1, 0, 1, 1, { '2' }), - make_edit(2, 0, 2, 2, { '3' }), - make_edit(3, 2, 3, 4, { '' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 0, 0, { '123' } }, + { 1, 0, 1, 1, { '2' } }, + { 2, 0, 2, 2, { '3' } }, + { 3, 2, 3, 4, { '' } }, + }) eq({ '123First line of text', '2econd line of text', @@ -1748,18 +1966,18 @@ describe('LSP', function() 'aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄', }, buf_lines(1)) end) + it('applies complex edits', function() - local edits = { - make_edit(0, 0, 0, 0, { '', '12' }), - make_edit(0, 0, 0, 0, { '3', 'foo' }), - make_edit(0, 1, 0, 1, { 'bar', '123' }), - make_edit(0, #'First ', 0, #'First line of text', { 'guy' }), - make_edit(1, 0, 1, #'Second', { 'baz' }), - make_edit(2, #'Th', 2, #'Third', { 'e next' }), - make_edit(3, #'', 3, #'Fourth', { 'another line of text', 'before this' }), - make_edit(3, #'Fourth', 3, #'Fourth line of text', { '!' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 0, 0, { '', '12' } }, + { 0, 0, 0, 0, { '3', 'foo' } }, + { 0, 1, 0, 1, { 'bar', '123' } }, + { 0, #'First ', 0, #'First line of text', { 'guy' } }, + { 1, 0, 1, #'Second', { 'baz' } }, + { 2, #'Th', 2, #'Third', { 'e next' } }, + { 3, #'', 3, #'Fourth', { 'another line of text', 'before this' } }, + { 3, #'Fourth', 3, #'Fourth line of text', { '!' } }, + }) eq({ '', '123', @@ -1772,18 +1990,18 @@ describe('LSP', function() 'aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄', }, buf_lines(1)) end) + it('applies complex edits (reversed range)', function() - local edits = { - make_edit(0, 0, 0, 0, { '', '12' }), - make_edit(0, 0, 0, 0, { '3', 'foo' }), - make_edit(0, 1, 0, 1, { 'bar', '123' }), - make_edit(0, #'First line of text', 0, #'First ', { 'guy' }), - make_edit(1, #'Second', 1, 0, { 'baz' }), - make_edit(2, #'Third', 2, #'Th', { 'e next' }), - make_edit(3, #'Fourth', 3, #'', { 'another line of text', 'before this' }), - make_edit(3, #'Fourth line of text', 3, #'Fourth', { '!' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 0, 0, { '', '12' } }, + { 0, 0, 0, 0, { '3', 'foo' } }, + { 0, 1, 0, 1, { 'bar', '123' } }, + { 0, #'First line of text', 0, #'First ', { 'guy' } }, + { 1, #'Second', 1, 0, { 'baz' } }, + { 2, #'Third', 2, #'Th', { 'e next' } }, + { 3, #'Fourth', 3, #'', { 'another line of text', 'before this' } }, + { 3, #'Fourth line of text', 3, #'Fourth', { '!' } }, + }) eq({ '', '123', @@ -1796,11 +2014,11 @@ describe('LSP', function() 'aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄', }, buf_lines(1)) end) + it('applies non-ASCII characters edits', function() - local edits = { - make_edit(4, 3, 4, 4, { 'ä' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 4, 3, 4, 4, { 'ä' } }, + }) eq({ 'First line of text', 'Second line of text', @@ -1809,11 +2027,11 @@ describe('LSP', function() 'aÌŠ ä ɧ æ±‰è¯ â†¥ 🤦 🦄', }, buf_lines(1)) end) + it('applies text edits at the end of the document', function() - local edits = { - make_edit(5, 0, 5, 0, 'foobar'), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 5, 0, 5, 0, 'foobar' }, + }) eq({ 'First line of text', 'Second line of text', @@ -1823,12 +2041,12 @@ describe('LSP', function() 'foobar', }, buf_lines(1)) end) + 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'), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 4, 0, 5, 0, '' }, + { 5, 0, 5, 0, 'foobar' }, + }) eq({ 'First line of text', 'Second line of text', @@ -1837,62 +2055,56 @@ describe('LSP', function() 'foobar', }, buf_lines(1)) end) + it('it restores marks', function() - local edits = { - make_edit(1, 0, 2, 5, 'foobar'), - make_edit(4, 0, 5, 0, 'barfoo'), - } eq(true, api.nvim_buf_set_mark(1, 'a', 2, 1, {})) - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 2, 5, 'foobar' }, + { 4, 0, 5, 0, 'barfoo' }, + }) eq({ 'First line of text', 'foobar line of text', 'Fourth line of text', 'barfoo', }, buf_lines(1)) - local mark = api.nvim_buf_get_mark(1, 'a') - eq({ 2, 1 }, mark) + eq({ 2, 1 }, api.nvim_buf_get_mark(1, 'a')) 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'), - } eq(true, api.nvim_buf_set_mark(1, 'a', 2, 10, {})) - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 2, 15, 'foobar' }, + { 4, 0, 5, 0, 'barfoo' }, + }) eq({ 'First line of text', 'foobarext', 'Fourth line of text', 'barfoo', }, buf_lines(1)) - local mark = api.nvim_buf_get_mark(1, 'a') - eq({ 2, 9 }, mark) + eq({ 2, 9 }, api.nvim_buf_get_mark(1, 'a')) 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'), - } eq(true, api.nvim_buf_set_mark(1, 'a', 4, 1, {})) - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 4, 5, 'foobar' }, + { 4, 0, 5, 0, 'barfoo' }, + }) eq({ 'First line of text', 'foobaro', }, buf_lines(1)) - local mark = api.nvim_buf_get_mark(1, 'a') - eq({ 2, 1 }, mark) + eq({ 2, 1 }, api.nvim_buf_get_mark(1, 'a')) end) describe('cursor position', function() it("don't fix the cursor if the range contains the cursor", function() api.nvim_win_set_cursor(0, { 2, 6 }) - local edits = { - make_edit(1, 0, 1, 19, 'Second line of text'), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 1, 19, 'Second line of text' }, + }) eq({ 'First line of text', 'Second line of text', @@ -1905,11 +2117,10 @@ describe('LSP', function() it('fix the cursor to the valid col if the content was removed', function() api.nvim_win_set_cursor(0, { 2, 6 }) - local edits = { - make_edit(1, 0, 1, 6, ''), - make_edit(1, 6, 1, 19, ''), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 1, 6, '' }, + { 1, 6, 1, 19, '' }, + }) eq({ 'First line of text', '', @@ -1922,11 +2133,10 @@ describe('LSP', function() it('fix the cursor to the valid row if the content was removed', function() api.nvim_win_set_cursor(0, { 2, 6 }) - local edits = { - make_edit(1, 0, 1, 6, ''), - make_edit(0, 18, 5, 0, ''), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 1, 6, '' }, + { 0, 18, 5, 0, '' }, + }) eq({ 'First line of text', }, buf_lines(1)) @@ -1935,10 +2145,9 @@ describe('LSP', function() it('fix the cursor row', function() api.nvim_win_set_cursor(0, { 3, 0 }) - local edits = { - make_edit(1, 0, 2, 0, ''), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 2, 0, '' }, + }) eq({ 'First line of text', 'Third line of text', @@ -1953,10 +2162,9 @@ describe('LSP', function() api.nvim_buf_set_lines(1, -1, -1, true, { '' }) api.nvim_win_set_cursor(0, { 2, 11 }) - local edits = { - make_edit(1, 7, 1, 11, ''), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 7, 1, 11, '' }, + }) eq({ 'First line of text', 'Second of text', @@ -1970,10 +2178,9 @@ describe('LSP', function() it('fix the cursor row and col', function() api.nvim_win_set_cursor(0, { 2, 12 }) - local edits = { - make_edit(0, 11, 1, 12, ''), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 11, 1, 12, '' }, + }) eq({ 'First line of text', 'Third line of text', @@ -1986,24 +2193,23 @@ describe('LSP', function() describe('with LSP end line after what Vim considers to be the end line', function() it('applies edits when the last linebreak is considered a new line', function() - local edits = { - make_edit(0, 0, 5, 0, { 'All replaced' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 5, 0, { 'All replaced' } }, + }) eq({ 'All replaced' }, buf_lines(1)) end) + it("applies edits when the end line is 2 larger than vim's", function() - local edits = { - make_edit(0, 0, 6, 0, { 'All replaced' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 6, 0, { 'All replaced' } }, + }) eq({ 'All replaced' }, buf_lines(1)) end) + it('applies edits with a column offset', function() - local edits = { - make_edit(0, 0, 5, 2, { 'All replaced' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 5, 2, { 'All replaced' } }, + }) eq({ 'All replaced' }, buf_lines(1)) end) end) @@ -2015,38 +2221,38 @@ describe('LSP', function() 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') + apply_text_edits({ + { 0, 0, 1, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' } }, + }, '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') + apply_text_edits({ + { 0, 0, 0, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' } }, + }, 'utf-8') eq( { '#include "whatever.h"', '#include <algorithm>', 'Test line two 21 char' }, buf_lines(1) ) 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') + apply_text_edits({ + { 0, 2, 1, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' } }, + }, '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') + apply_text_edits({ + { 0, 2, 0, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' } }, + }, 'utf-8') eq( { 'Te#include "whatever.h"', '#include <algorithm>', 'Test line two 21 char' }, buf_lines(1) @@ -2057,6 +2263,7 @@ describe('LSP', function() describe('apply_text_document_edit', function() local target_bufnr --- @type integer + local text_document_edit = function(editVersion) return { edits = { @@ -2068,50 +2275,43 @@ describe('LSP', function() }, } end + before_each(function() - target_bufnr = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local lines = {"1st line of text", "2nd line of è¯text"} + target_bufnr = exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri') + local lines = { '1st line of text', '2nd line of è¯text' } vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr - ]] + end) end) + it('correctly goes ahead with the edit if all is normal', function() - exec_lua("vim.lsp.util.apply_text_document_edit(..., nil, 'utf-16')", text_document_edit(5)) + exec_lua(function(text_edit) + vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16') + end, text_document_edit(5)) eq({ 'First ↥ 🤦 🦄 line of text', '2nd line of è¯text', }, buf_lines(target_bufnr)) end) + it('always accepts edit with version = 0', function() - exec_lua( - [[ - local args = {...} - local bufnr = select(1, ...) - local text_edit = select(2, ...) - vim.lsp.util.buf_versions[bufnr] = 10 + exec_lua(function(text_edit) + vim.lsp.util.buf_versions[target_bufnr] = 10 vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16') - ]], - target_bufnr, - text_document_edit(0) - ) + end, text_document_edit(0)) eq({ 'First ↥ 🤦 🦄 line of text', '2nd line of è¯text', }, buf_lines(target_bufnr)) end) + it('skips the edit if the version of the edit is behind the local buffer ', function() local apply_edit_mocking_current_version = function(edit, versionedBuf) - exec_lua( - [[ - local args = {...} - local versionedBuf = args[2] + exec_lua(function() vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion - vim.lsp.util.apply_text_document_edit(args[1], nil, 'utf-16') - ]], - edit, - versionedBuf - ) + vim.lsp.util.apply_text_document_edit(edit, nil, 'utf-16') + end) end local baseText = { @@ -2164,13 +2364,17 @@ describe('LSP', function() } 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}) - ]] + exec_lua(function() + local apply_edit = { + label = nil, + edit = {}, + } + return vim.lsp.handlers['workspace/applyEdit']( + nil, + apply_edit, + { client_id = _G.TEST_RPC_CLIENT_ID } + ) + end) ) eq(table.remove(expected_handlers), { ... }) end, @@ -2200,34 +2404,30 @@ describe('LSP', function() } end - local target_bufnr, changedtick = nil, nil + local target_bufnr --- @type integer + local changedtick --- @type integer before_each(function() - local ret = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") + exec_lua(function() + target_bufnr = vim.uri_to_bufnr('file:///fake/uri') local lines = { - "Original Line #1", - "Original Line #2" + 'Original Line #1', + 'Original Line #2', } - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + vim.api.nvim_buf_set_lines(target_bufnr, 0, -1, false, lines) - local update_changed_tick = function() - vim.lsp.util.buf_versions[bufnr] = vim.api.nvim_buf_get_var(bufnr, 'changedtick') + local function update_changed_tick() + vim.lsp.util.buf_versions[target_bufnr] = vim.b[target_bufnr].changedtick end update_changed_tick() - vim.api.nvim_buf_attach(bufnr, false, { - on_changedtick = function() - update_changed_tick() - end + vim.api.nvim_buf_attach(target_bufnr, false, { + on_changedtick = update_changed_tick, }) - return {bufnr, vim.api.nvim_buf_get_var(bufnr, 'changedtick')} - ]] - - target_bufnr = ret[1] - changedtick = ret[2] + changedtick = vim.b[target_bufnr].changedtick + end) end) it('apply_workspace_edit applies a single edit', function() @@ -2245,19 +2445,11 @@ describe('LSP', function() 'First Line', 'Original Line #2', }, - exec_lua( - [[ - local args = {...} - local workspace_edits = args[1] - local target_bufnr = args[2] - - vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') + exec_lua(function(workspace_edits) + vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') - return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) - ]], - make_workspace_edit(edits), - target_bufnr - ) + return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) + end, make_workspace_edit(edits)) ) end) @@ -2274,24 +2466,15 @@ describe('LSP', function() eq( new_lines, - exec_lua( - [[ - local args = {...} - local workspace_edits = args[1] - local target_bufnr = args[2] - - vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') - - return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) - ]], - make_workspace_edit(edits), - target_bufnr - ) + exec_lua(function(workspace_edits) + vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') + return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) + end, make_workspace_edit(edits)) ) end) + it('Supports file creation with CreateFile payload', function() - local tmpfile = tmpname() - os.remove(tmpfile) -- Should not exist, only interested in a tmpname + local tmpfile = tmpname(false) local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local edit = { documentChanges = { @@ -2301,15 +2484,16 @@ describe('LSP', function() }, }, } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') + exec_lua(function() + vim.lsp.util.apply_workspace_edit(edit, 'utf-16') + end) eq(true, vim.uv.fs_stat(tmpfile) ~= nil) end) + it( 'Supports file creation in folder that needs to be created with CreateFile payload', function() - local tmpfile = tmpname() - os.remove(tmpfile) -- Should not exist, only interested in a tmpname - tmpfile = tmpfile .. '/dummy/x/' + local tmpfile = tmpname(false) .. '/dummy/x/' local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local edit = { documentChanges = { @@ -2319,10 +2503,13 @@ describe('LSP', function() }, }, } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') + exec_lua(function() + vim.lsp.util.apply_workspace_edit(edit, 'utf-16') + end) eq(true, vim.uv.fs_stat(tmpfile) ~= nil) end ) + it('createFile does not touch file if it exists and ignoreIfExists is set', function() local tmpfile = tmpname() write_file(tmpfile, 'Dummy content') @@ -2342,6 +2529,7 @@ describe('LSP', function() eq(true, vim.uv.fs_stat(tmpfile) ~= nil) eq('Dummy content', read_file(tmpfile)) end) + it('createFile overrides file if overwrite is set', function() local tmpfile = tmpname() write_file(tmpfile, 'Dummy content') @@ -2362,18 +2550,15 @@ describe('LSP', function() eq(true, vim.uv.fs_stat(tmpfile) ~= nil) eq('', read_file(tmpfile)) end) + it('DeleteFile delete file and buffer', function() local tmpfile = tmpname() write_file(tmpfile, 'Be gone') - local uri = exec_lua( - [[ - local fname = select(1, ...) - local bufnr = vim.fn.bufadd(fname) + local uri = exec_lua(function() + local bufnr = vim.fn.bufadd(tmpfile) vim.fn.bufload(bufnr) - return vim.uri_from_fname(fname) - ]], - tmpfile - ) + return vim.uri_from_fname(tmpfile) + end) local edit = { documentChanges = { { @@ -2386,9 +2571,9 @@ describe('LSP', function() eq(false, vim.uv.fs_stat(tmpfile) ~= nil) eq(false, api.nvim_buf_is_loaded(fn.bufadd(tmpfile))) end) + it('DeleteFile fails if file does not exist and ignoreIfNotExists is false', function() - local tmpfile = tmpname() - os.remove(tmpfile) + local tmpfile = tmpname(false) local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local edit = { documentChanges = { @@ -2412,12 +2597,8 @@ describe('LSP', function() it('Can rename an existing file', function() local old = tmpname() write_file(old, 'Test content') - local new = tmpname() - os.remove(new) -- only reserve the name, file must not exist for the test scenario - local lines = exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) + local new = tmpname(false) + local lines = exec_lua(function() local old_bufnr = vim.fn.bufadd(old) vim.fn.bufload(old_bufnr) vim.lsp.util.rename(old, new) @@ -2425,10 +2606,7 @@ describe('LSP', function() 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) - ]], - old, - new - ) + end) eq({ 'Test content' }, lines) local exists = vim.uv.fs_stat(old) ~= nil eq(false, exists) @@ -2436,24 +2614,18 @@ describe('LSP', function() eq(true, exists) os.remove(new) end) + it('Can rename a directory', function() -- only reserve the name, file must not exist for the test scenario - local old_dir = tmpname() - local new_dir = tmpname() - os.remove(old_dir) - os.remove(new_dir) + local old_dir = tmpname(false) + local new_dir = tmpname(false) n.mkdir_p(old_dir) local file = 'file.txt' write_file(old_dir .. pathsep .. file, 'Test content') - local lines = exec_lua( - [[ - local old_dir = select(1, ...) - local new_dir = select(2, ...) - local pathsep = select(3, ...) - local file = select(4, ...) + local lines = exec_lua(function() local old_bufnr = vim.fn.bufadd(old_dir .. pathsep .. file) vim.fn.bufload(old_bufnr) vim.lsp.util.rename(old_dir, new_dir) @@ -2461,12 +2633,7 @@ describe('LSP', function() 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) - ]], - old_dir, - new_dir, - pathsep, - file - ) + end) eq({ 'Test content' }, lines) eq(false, vim.uv.fs_stat(old_dir) ~= nil) eq(true, vim.uv.fs_stat(new_dir) ~= nil) @@ -2474,47 +2641,41 @@ describe('LSP', function() os.remove(new_dir) end) + it('Does not touch buffers that do not match path prefix', function() - local old = tmpname() - local new = tmpname() - os.remove(old) - os.remove(new) + local old = tmpname(false) + local new = tmpname(false) n.mkdir_p(old) - 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' + eq( + true, + exec_lua(function() + 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) + 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) + 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 + 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 + end) ) - eq(true, result) os.remove(new) end) + it( 'Does not rename file if target exists and ignoreIfExists is set or overwrite is false', function() @@ -2523,45 +2684,28 @@ describe('LSP', function() local new = tmpname() write_file(new, 'New file') - exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) - - vim.lsp.util.rename(old, new, { ignoreIfExists = true }) - ]], - old, - new - ) + exec_lua(function() + vim.lsp.util.rename(old, new, { ignoreIfExists = true }) + end) eq(true, vim.uv.fs_stat(old) ~= nil) 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 - ) + exec_lua(function() + vim.lsp.util.rename(old, new, { overwrite = false }) + end) eq(true, vim.uv.fs_stat(old) ~= nil) eq('New file', read_file(new)) end ) + it('Maintains undo information for loaded buffer', function() local old = tmpname() write_file(old, 'line') - local new = tmpname() - os.remove(new) + local new = tmpname(false) - local undo_kept = exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) + local undo_kept = exec_lua(function() vim.opt.undofile = true vim.cmd.edit(old) vim.cmd.normal('dd') @@ -2574,24 +2718,18 @@ describe('LSP', function() undotree.save_last = undotree.save_last + 1 undotree.entries[1].save = undotree.entries[1].save + 1 return vim.deep_equal(undotree, vim.fn.undotree()) - ]], - old, - new - ) + end) eq(false, vim.uv.fs_stat(old) ~= nil) eq(true, vim.uv.fs_stat(new) ~= nil) 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 new = tmpname(false) - local undo_kept = exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) + local undo_kept = exec_lua(function() vim.opt.undofile = true vim.cmd.split(old) vim.cmd.normal('dd') @@ -2601,54 +2739,39 @@ describe('LSP', function() vim.lsp.util.rename(old, new) vim.cmd.edit(new) return vim.deep_equal(undotree, vim.fn.undotree()) - ]], - old, - new - ) + end) eq(false, vim.uv.fs_stat(old) ~= nil) eq(true, vim.uv.fs_stat(new) ~= nil) eq(true, undo_kept) end) + 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 - ) + local new = tmpname(false) + + local lines = exec_lua(function() + 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) + end) eq({ 'conflict' }, lines) eq('Old File', read_file(old)) end) + it('Does override target if overwrite is true', function() local old = tmpname() write_file(old, 'Old file') local new = tmpname() write_file(new, 'New file') - exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) - + exec_lua(function() vim.lsp.util.rename(old, new, { overwrite = true }) - ]], - old, - new - ) + end) eq(false, vim.uv.fs_stat(old) ~= nil) eq(true, vim.uv.fs_stat(new) ~= nil) @@ -2658,44 +2781,59 @@ describe('LSP', function() describe('lsp.util.locations_to_items', function() it('Convert Location[] to items', function() - local expected = { + local expected_template = { { filename = '/fake/uri', lnum = 1, + end_lnum = 2, col = 3, + end_col = 4, text = 'testing', - user_data = { + user_data = {}, + }, + } + local test_params = { + { + { uri = 'file:///fake/uri', range = { start = { line = 0, character = 2 }, - ['end'] = { line = 0, character = 3 }, + ['end'] = { line = 1, character = 3 }, }, }, }, - } - local actual = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local lines = {"testing", "123"} - vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) - local locations = { + { { uri = 'file:///fake/uri', range = { start = { line = 0, character = 2 }, - ['end'] = { line = 0, character = 3 }, - } + -- LSP spec: if character > line length, default to the line length. + ['end'] = { line = 1, character = 10000 }, + }, }, - } - return vim.lsp.util.locations_to_items(locations, 'utf-16') - ]] - eq(expected, actual) + }, + } + for _, params in ipairs(test_params) do + local actual = exec_lua(function(params0) + local bufnr = vim.uri_to_bufnr('file:///fake/uri') + local lines = { 'testing', '123' } + vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) + return vim.lsp.util.locations_to_items(params0, 'utf-16') + end, params) + local expected = vim.deepcopy(expected_template) + expected[1].user_data = params[1] + eq(expected, actual) + end end) + it('Convert LocationLink[] to items', function() local expected = { { filename = '/fake/uri', lnum = 1, + end_lnum = 1, col = 3, + end_col = 4, text = 'testing', user_data = { targetUri = 'file:///fake/uri', @@ -2710,9 +2848,9 @@ describe('LSP', function() }, }, } - local actual = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local lines = {"testing", "123"} + local actual = exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri') + local lines = { 'testing', '123' } vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) local locations = { { @@ -2724,11 +2862,11 @@ describe('LSP', function() targetSelectionRange = { start = { line = 0, character = 2 }, ['end'] = { line = 0, character = 3 }, - } + }, }, } return vim.lsp.util.locations_to_items(locations, 'utf-16') - ]] + end) eq(expected, actual) end) end) @@ -2761,94 +2899,95 @@ describe('LSP', function() } eq( expected, - exec_lua [[ - local doc_syms = { - { - deprecated = false, - detail = "A", - kind = 1, - name = "TestA", - range = { - start = { - character = 0, - line = 1 + exec_lua(function() + local doc_syms = { + { + deprecated = false, + detail = 'A', + kind = 1, + name = 'TestA', + range = { + start = { + character = 0, + line = 1, + }, + ['end'] = { + character = 0, + line = 2, + }, }, - ["end"] = { - character = 0, - line = 2 - } - }, - selectionRange = { - start = { - character = 0, - line = 1 + selectionRange = { + start = { + character = 0, + line = 1, + }, + ['end'] = { + character = 4, + line = 1, + }, }, - ["end"] = { - character = 4, - line = 1 - } - }, - children = { - { - children = {}, - deprecated = false, - detail = "B", - kind = 2, - name = "TestB", - range = { - start = { - character = 0, - line = 3 + children = { + { + children = {}, + deprecated = false, + detail = 'B', + kind = 2, + name = 'TestB', + range = { + start = { + character = 0, + line = 3, + }, + ['end'] = { + character = 0, + line = 4, + }, }, - ["end"] = { - character = 0, - line = 4 - } - }, - selectionRange = { - start = { - character = 0, - line = 3 + selectionRange = { + start = { + character = 0, + line = 3, + }, + ['end'] = { + character = 4, + 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 + { + deprecated = false, + detail = 'C', + kind = 3, + name = 'TestC', + range = { + start = { + character = 0, + line = 5, + }, + ['end'] = { + character = 0, + line = 6, + }, }, - ["end"] = { - character = 4, - line = 5 - } - } + selectionRange = { + start = { + character = 0, + line = 5, + }, + ['end'] = { + character = 4, + line = 5, + }, + }, + }, } - } - return vim.lsp.util.symbols_to_items(doc_syms, nil) - ]] + return vim.lsp.util.symbols_to_items(doc_syms, nil) + end) ) end) + it('DocumentSymbol has no children', function() local expected = { { @@ -2868,66 +3007,67 @@ describe('LSP', function() } eq( expected, - exec_lua [[ - local doc_syms = { - { - deprecated = false, - detail = "A", - kind = 1, - name = "TestA", - range = { - start = { - character = 0, - line = 1 + exec_lua(function() + local doc_syms = { + { + deprecated = false, + detail = 'A', + kind = 1, + name = 'TestA', + range = { + start = { + character = 0, + line = 1, + }, + ['end'] = { + character = 0, + line = 2, + }, }, - ["end"] = { - character = 0, - line = 2 - } - }, - selectionRange = { - start = { - character = 0, - line = 1 + selectionRange = { + start = { + character = 0, + line = 1, + }, + ['end'] = { + character = 4, + line = 1, + }, }, - ["end"] = { - character = 4, - line = 1 - } }, - }, - { - deprecated = false, - detail = "C", - kind = 3, - name = "TestC", - range = { - start = { - character = 0, - line = 5 + { + deprecated = false, + detail = 'C', + kind = 3, + name = 'TestC', + range = { + start = { + character = 0, + line = 5, + }, + ['end'] = { + character = 0, + line = 6, + }, }, - ["end"] = { - character = 0, - line = 6 - } - }, - selectionRange = { - start = { - character = 0, - line = 5 + selectionRange = { + start = { + character = 0, + line = 5, + }, + ['end'] = { + character = 4, + line = 5, + }, }, - ["end"] = { - character = 4, - line = 5 - } - } + }, } - } - return vim.lsp.util.symbols_to_items(doc_syms, nil) - ]] + return vim.lsp.util.symbols_to_items(doc_syms, nil) + end) ) end) end) + it('convert SymbolInformation[] to items', function() local expected = { { @@ -2947,62 +3087,88 @@ describe('LSP', function() } eq( expected, - exec_lua [[ + exec_lua(function() local sym_info = { { deprecated = false, kind = 1, - name = "TestA", + name = 'TestA', location = { range = { start = { character = 0, - line = 1 + line = 1, }, - ["end"] = { + ['end'] = { character = 0, - line = 2 - } + line = 2, + }, }, - uri = "file:///test_a" + uri = 'file:///test_a', }, - containerName = "TestAContainer" + containerName = 'TestAContainer', }, { deprecated = false, kind = 2, - name = "TestB", + name = 'TestB', location = { range = { start = { character = 0, - line = 3 + line = 3, }, - ["end"] = { + ['end'] = { character = 0, - line = 4 - } + line = 4, + }, }, - uri = "file:///test_b" + uri = 'file:///test_b', }, - containerName = "TestBContainer" - } + containerName = 'TestBContainer', + }, } return vim.lsp.util.symbols_to_items(sym_info, nil) - ]] + end) ) end) end) describe('lsp.util._get_symbol_kind_name', function() it('returns the name specified by protocol', function() - eq('File', exec_lua('return vim.lsp.util._get_symbol_kind_name(1)')) - eq('TypeParameter', exec_lua('return vim.lsp.util._get_symbol_kind_name(26)')) + eq( + 'File', + exec_lua(function() + return vim.lsp.util._get_symbol_kind_name(1) + end) + ) + eq( + 'TypeParameter', + exec_lua(function() + return vim.lsp.util._get_symbol_kind_name(26) + end) + ) end) + it('returns the name not specified by protocol', function() - eq('Unknown', exec_lua('return vim.lsp.util._get_symbol_kind_name(nil)')) - eq('Unknown', exec_lua('return vim.lsp.util._get_symbol_kind_name(vim.NIL)')) - eq('Unknown', exec_lua('return vim.lsp.util._get_symbol_kind_name(1000)')) + eq( + 'Unknown', + exec_lua(function() + return vim.lsp.util._get_symbol_kind_name(nil) + end) + ) + eq( + 'Unknown', + exec_lua(function() + return vim.lsp.util._get_symbol_kind_name(vim.NIL) + end) + ) + eq( + 'Unknown', + exec_lua(function() + return vim.lsp.util._get_symbol_kind_name(1000) + end) + ) end) end) @@ -3010,12 +3176,12 @@ describe('LSP', function() local target_bufnr --- @type integer before_each(function() - target_bufnr = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local lines = {"1st line of text", "aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄"} + target_bufnr = exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri') + local lines = { '1st line of text', 'aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄' } vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr - ]] + end) end) local location = function(start_line, start_char, end_line, end_char) @@ -3084,19 +3250,19 @@ describe('LSP', function() local target_bufnr2 --- @type integer before_each(function() - target_bufnr = exec_lua([[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local lines = {"1st line of text", "aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄"} + target_bufnr = exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri') + local lines = { '1st line of text', 'aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄' } vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr - ]]) + end) - target_bufnr2 = exec_lua([[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri2") - local lines = {"1st line of text", "aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄"} + target_bufnr2 = exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri2') + local lines = { '1st line of text', 'aÌŠ Ã¥ ɧ æ±‰è¯ â†¥ 🤦 🦄' } vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr - ]]) + end) end) local location = function(start_line, start_char, end_line, end_char, second_uri) @@ -3136,14 +3302,14 @@ describe('LSP', function() 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 = exec_lua(function() + local server = _G._create_server() + local client_id = assert(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 } + ['end'] = { line = 0, character = 9 }, }, takeFocus = true, } @@ -3154,9 +3320,9 @@ describe('LSP', function() vim.lsp.handlers['window/showDocument'](nil, result, ctx) vim.lsp.stop_client(client_id) return { - cursor = vim.api.nvim_win_get_cursor(0) + cursor = vim.api.nvim_win_get_cursor(0), } - ]]) + end) eq(1, result.cursor[1]) eq(9, result.cursor[2]) end) @@ -3231,7 +3397,9 @@ describe('LSP', function() api.nvim_win_set_buf(0, target_bufnr) api.nvim_win_set_cursor(0, { 2, 3 }) - exec_lua([[vim.cmd.new()]]) + exec_lua(function() + vim.cmd.new() + end) api.nvim_win_set_buf(0, target_bufnr2) api.nvim_win_set_cursor(0, { 2, 3 }) @@ -3247,7 +3415,9 @@ describe('LSP', function() api.nvim_win_set_buf(0, target_bufnr) local win = api.nvim_get_current_win() - exec_lua([[vim.cmd.new()]]) + exec_lua(function() + vim.cmd.new() + end) api.nvim_win_set_buf(0, target_bufnr2) api.nvim_win_set_cursor(0, { 2, 3 }) local split = api.nvim_get_current_win() @@ -3268,34 +3438,53 @@ describe('LSP', function() describe('lsp.util._make_floating_popup_size', function() before_each(function() - exec_lua [[ contents = - {"text tαxt txtα tex", - "text tααt tααt text", - "text tαxt tαxt"} - ]] + exec_lua(function() + _G.contents = { 'text tαxt txtα tex', 'text tααt tααt text', 'text tαxt tαxt' } + end) end) it('calculates size correctly', function() - eq({ 19, 3 }, exec_lua [[ return {vim.lsp.util._make_floating_popup_size(contents)} ]]) + eq( + { 19, 3 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents) } + end) + ) 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})} ]] + exec_lua(function() + return { + vim.lsp.util._make_floating_popup_size(_G.contents, { width = 15, wrap_at = 14 }), + } + end) ) end) 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', - } ]]) + exec_lua(function() + _G.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', + } + end) command('set list listchars=') - eq({ 20, 3 }, exec_lua [[ return {vim.lsp.util._make_floating_popup_size(contents)} ]]) + eq( + { 20, 3 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents) } + end) + ) command('set display+=uhex') - eq({ 40, 3 }, exec_lua [[ return {vim.lsp.util._make_floating_popup_size(contents)} ]]) + eq( + { 40, 3 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents) } + end) + ) end) end) @@ -3303,27 +3492,30 @@ describe('LSP', function() it('properly trims empty lines', function() eq( { { 'foo', 'bar' } }, - exec_lua [[ return vim.lsp.util.trim_empty_lines({{ "foo", "bar" }, nil}) ]] + exec_lua(function() + --- @diagnostic disable-next-line:deprecated + return vim.lsp.util.trim_empty_lines({ { 'foo', 'bar' }, nil }) + end) ) end) end) describe('lsp.util.convert_signature_help_to_markdown_lines', function() it('can handle negative activeSignature', function() - local result = exec_lua [[ + local result = exec_lua(function() local signature_help = { activeParameter = 0, activeSignature = -1, signatures = { { - documentation = "some doc", - label = "TestEntity.TestEntity()", - parameters = {} + documentation = 'some doc', + label = 'TestEntity.TestEntity()', + parameters = {}, }, - } + }, } - return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','}) - ]] + return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', { ',' }) + end) local expected = { '```cs', 'TestEntity.TestEntity()', '```', 'some doc' } eq(expected, result) end) @@ -3338,12 +3530,18 @@ describe('LSP', function() ]], shiftwidth )) - eq(tabsize, exec_lua('return vim.lsp.util.get_effective_tabstop()')) + eq( + tabsize, + exec_lua(function() + return vim.lsp.util.get_effective_tabstop() + end) + ) end it('with shiftwidth = 1', function() test_tabstop(1, 1) end) + it('with shiftwidth = 0', function() test_tabstop(2, 0) end) @@ -3351,57 +3549,61 @@ describe('LSP', function() describe('vim.lsp.buf.outgoing_calls', function() it('does nothing for an empty response', function() - local qflist_count = exec_lua([=[ - require'vim.lsp.handlers'['callHierarchy/outgoingCalls'](nil, nil, {}, nil) + local qflist_count = exec_lua(function() + require 'vim.lsp.handlers'['callHierarchy/outgoingCalls'](nil, nil, {}, nil) return #vim.fn.getqflist() - ]=]) + end) 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 + local qflist = exec_lua(function() + local rust_analyzer_response = { + { + fromRanges = { + { + ['end'] = { + character = 7, + line = 3, + }, + start = { + character = 4, + line = 3, + }, }, - start = { - character = 0, - line = 0 - } }, - selectionRange = { - ['end'] = { - character = 6, - line = 0 + to = { + detail = 'fn foo()', + kind = 12, + name = 'foo', + range = { + ['end'] = { + character = 11, + line = 0, + }, + start = { + character = 0, + line = 0, + }, }, - start = { - character = 3, - line = 0 - } + selectionRange = { + ['end'] = { + character = 6, + line = 0, + }, + start = { + character = 3, + line = 0, + }, + }, + uri = 'file:///src/main.rs', }, - uri = "file:///src/main.rs" - } - } } - local handler = require'vim.lsp.handlers'['callHierarchy/outgoingCalls'] + }, + } + local handler = require 'vim.lsp.handlers'['callHierarchy/outgoingCalls'] handler(nil, rust_analyzer_response, {}) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3426,58 +3628,62 @@ describe('LSP', function() describe('vim.lsp.buf.incoming_calls', function() it('does nothing for an empty response', function() - local qflist_count = exec_lua([=[ - require'vim.lsp.handlers'['callHierarchy/incomingCalls'](nil, nil, {}) + local qflist_count = exec_lua(function() + require 'vim.lsp.handlers'['callHierarchy/incomingCalls'](nil, nil, {}) return #vim.fn.getqflist() - ]=]) + end) 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 + local qflist = exec_lua(function() + local rust_analyzer_response = { + { + from = { + detail = 'fn main()', + kind = 12, + name = 'main', + range = { + ['end'] = { + character = 1, + line = 4, + }, + start = { + character = 0, + line = 2, + }, }, - start = { - character = 0, - line = 2 - } + selectionRange = { + ['end'] = { + character = 7, + line = 2, + }, + start = { + character = 3, + line = 2, + }, + }, + uri = 'file:///src/main.rs', }, - selectionRange = { - ['end'] = { - character = 7, - line = 2 + fromRanges = { + { + ['end'] = { + character = 7, + line = 3, + }, + start = { + character = 4, + line = 3, + }, }, - start = { - character = 3, - line = 2 - } }, - uri = "file:///src/main.rs" }, - fromRanges = { { - ['end'] = { - character = 7, - line = 3 - }, - start = { - character = 4, - line = 3 - } - } } - } } + } - local handler = require'vim.lsp.handlers'['callHierarchy/incomingCalls'] + local handler = require 'vim.lsp.handlers'['callHierarchy/incomingCalls'] handler(nil, rust_analyzer_response, {}) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3502,103 +3708,126 @@ describe('LSP', function() describe('vim.lsp.buf.typehierarchy subtypes', function() it('does nothing for an empty response', function() - local qflist_count = exec_lua([=[ - require'vim.lsp.handlers'['typeHierarchy/subtypes'](nil, nil, {}) + local qflist_count = exec_lua(function() + require 'vim.lsp.handlers'['typeHierarchy/subtypes'](nil, nil, {}) return #vim.fn.getqflist() - ]=]) + end) 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 + local qflist = exec_lua(function() + local clangd_response = { + { + data = { + parents = { + { + parents = { + { + parents = { + { + parents = {}, + symbolID = '62B3D268A01B9978', + }, + }, + symbolID = 'DC9B0AD433B43BEC', + }, + }, + symbolID = '06B5F6A19BA9F6A8', + }, + }, + symbolID = 'EDC336589C09ABB2', }, - start = { - character = 6, - line = 9 - } - }, - selectionRange = { - ["end"] = { - character = 8, - line = 9 + kind = 5, + name = 'D2', + range = { + ['end'] = { + character = 8, + line = 3, + }, + start = { + character = 6, + line = 3, + }, }, - 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 + selectionRange = { + ['end'] = { + character = 8, + line = 3, + }, + start = { + character = 6, + line = 3, + }, }, - start = { - character = 6, - line = 8 - } + uri = 'file:///home/jiangyinzuo/hello.cpp', }, - selectionRange = { - ["end"] = { - character = 8, - line = 8 + { + data = { + parents = { + { + parents = { + { + parents = { + { + parents = {}, + symbolID = '62B3D268A01B9978', + }, + }, + symbolID = 'DC9B0AD433B43BEC', + }, + }, + symbolID = '06B5F6A19BA9F6A8', + }, + }, + symbolID = 'AFFCAED15557EF08', }, - start = { - character = 6, - line = 8 - } + kind = 5, + name = 'D1', + range = { + ['end'] = { + character = 8, + line = 2, + }, + start = { + character = 6, + line = 2, + }, + }, + selectionRange = { + ['end'] = { + character = 8, + line = 2, + }, + start = { + character = 6, + line = 2, + }, + }, + uri = 'file:///home/jiangyinzuo/hello.cpp', }, - uri = "file:///home/jiangyinzuo/hello.cpp" - } } + } - local server = _create_server({ + local server = _G._create_server({ capabilities = { - positionEncoding = "utf-8" + 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 }) + local handler = require 'vim.lsp.handlers'['typeHierarchy/subtypes'] + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { + 'class B : public A{};', + 'class C : public B{};', + 'class D1 : public C{};', + 'class D2 : public C{};', + 'class E : public D1, D2 {};', + }) + handler(nil, clangd_response, { client_id = client_id, bufnr = bufnr }) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3606,7 +3835,7 @@ describe('LSP', function() col = 7, end_col = 0, end_lnum = 0, - lnum = 10, + lnum = 4, module = '', nr = 0, pattern = '', @@ -3620,7 +3849,7 @@ describe('LSP', function() col = 7, end_col = 0, end_lnum = 0, - lnum = 9, + lnum = 3, module = '', nr = 0, pattern = '', @@ -3637,7 +3866,7 @@ describe('LSP', function() it('opens the quickfix list with the right subtypes and details', function() clear() exec_lua(create_server_definition) - local qflist = exec_lua([=[ + local qflist = exec_lua(function() local jdtls_response = { { data = { element = '=hello-java_ed323c3c/_<{Main.java[Main[A' }, @@ -3673,16 +3902,24 @@ describe('LSP', function() }, } - local server = _create_server({ + local server = _G._create_server({ capabilities = { - positionEncoding = "utf-8" + 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 }) + local handler = require 'vim.lsp.handlers'['typeHierarchy/subtypes'] + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { + 'package mylist;', + '', + 'public class MyList {', + ' static class Inner extends MyList{}', + '~}', + }) + handler(nil, jdtls_response, { client_id = client_id, bufnr = bufnr }) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3720,103 +3957,127 @@ describe('LSP', function() describe('vim.lsp.buf.typehierarchy supertypes', function() it('does nothing for an empty response', function() - local qflist_count = exec_lua([=[ - require'vim.lsp.handlers'['typeHierarchy/supertypes'](nil, nil, {}) + local qflist_count = exec_lua(function() + require 'vim.lsp.handlers'['typeHierarchy/supertypes'](nil, nil, {}) return #vim.fn.getqflist() - ]=]) + end) 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 + local qflist = exec_lua(function() + local clangd_response = { + { + data = { + parents = { + { + parents = { + { + parents = { + { + parents = {}, + symbolID = '62B3D268A01B9978', + }, + }, + symbolID = 'DC9B0AD433B43BEC', + }, + }, + symbolID = '06B5F6A19BA9F6A8', + }, + }, + symbolID = 'EDC336589C09ABB2', }, - start = { - character = 6, - line = 9 - } - }, - selectionRange = { - ["end"] = { - character = 8, - line = 9 + kind = 5, + name = 'D2', + range = { + ['end'] = { + character = 8, + line = 3, + }, + start = { + character = 6, + line = 3, + }, }, - 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 + selectionRange = { + ['end'] = { + character = 8, + line = 3, + }, + start = { + character = 6, + line = 3, + }, }, - start = { - character = 6, - line = 8 - } + uri = 'file:///home/jiangyinzuo/hello.cpp', }, - selectionRange = { - ["end"] = { - character = 8, - line = 8 + { + data = { + parents = { + { + parents = { + { + parents = { + { + parents = {}, + symbolID = '62B3D268A01B9978', + }, + }, + symbolID = 'DC9B0AD433B43BEC', + }, + }, + symbolID = '06B5F6A19BA9F6A8', + }, + }, + symbolID = 'AFFCAED15557EF08', }, - start = { - character = 6, - line = 8 - } + kind = 5, + name = 'D1', + range = { + ['end'] = { + character = 8, + line = 2, + }, + start = { + character = 6, + line = 2, + }, + }, + selectionRange = { + ['end'] = { + character = 8, + line = 2, + }, + start = { + character = 6, + line = 2, + }, + }, + uri = 'file:///home/jiangyinzuo/hello.cpp', }, - uri = "file:///home/jiangyinzuo/hello.cpp" - } } + } - local server = _create_server({ + local server = _G._create_server({ capabilities = { - positionEncoding = "utf-8" + 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 }) + local handler = require 'vim.lsp.handlers'['typeHierarchy/supertypes'] + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { + 'class B : public A{};', + 'class C : public B{};', + 'class D1 : public C{};', + 'class D2 : public C{};', + 'class E : public D1, D2 {};', + }) + + handler(nil, clangd_response, { client_id = client_id, bufnr = bufnr }) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3824,7 +4085,7 @@ describe('LSP', function() col = 7, end_col = 0, end_lnum = 0, - lnum = 10, + lnum = 4, module = '', nr = 0, pattern = '', @@ -3838,7 +4099,7 @@ describe('LSP', function() col = 7, end_col = 0, end_lnum = 0, - lnum = 9, + lnum = 3, module = '', nr = 0, pattern = '', @@ -3855,7 +4116,7 @@ describe('LSP', function() it('opens the quickfix list with the right supertypes and details', function() clear() exec_lua(create_server_definition) - local qflist = exec_lua([=[ + local qflist = exec_lua(function() local jdtls_response = { { data = { element = '=hello-java_ed323c3c/_<{Main.java[Main[A' }, @@ -3891,16 +4152,24 @@ describe('LSP', function() }, } - local server = _create_server({ + local server = _G._create_server({ capabilities = { - positionEncoding = "utf-8" + 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 }) + local handler = require 'vim.lsp.handlers'['typeHierarchy/supertypes'] + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { + 'package mylist;', + '', + 'public class MyList {', + ' static class Inner extends MyList{}', + '~}', + }) + handler(nil, jdtls_response, { client_id = client_id, bufnr = bufnr }) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3984,18 +4253,19 @@ describe('LSP', function() eq(true, client.server_capabilities().renameProvider.prepareProvider) end, on_setup = function() - exec_lua([=[ - local bufnr = vim.api.nvim_get_current_buf() - lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) - vim.lsp._stubs = {} - vim.fn.input = function(opts, on_confirm) - vim.lsp._stubs.input_prompt = opts.prompt - vim.lsp._stubs.input_text = opts.default - return 'renameto' -- expect this value in fake lsp - end - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'', 'this is line two'}) - vim.fn.cursor(2, 13) -- the space between "line" and "two" - ]=]) + exec_lua(function() + local bufnr = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + vim.lsp._stubs = {} + --- @diagnostic disable-next-line:duplicate-set-field + vim.fn.input = function(opts, _) + vim.lsp._stubs.input_prompt = opts.prompt + vim.lsp._stubs.input_text = opts.default + return 'renameto' -- expect this value in fake lsp + end + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '', 'this is line two' }) + vim.fn.cursor(2, 13) -- the space between "line" and "two" + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -4009,12 +4279,24 @@ describe('LSP', function() eq(table.remove(test.expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'start' then - exec_lua('vim.lsp.buf.rename()') + exec_lua(function() + vim.lsp.buf.rename() + end) 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')) + eq( + 'New Name: ', + exec_lua(function() + return vim.lsp._stubs.input_prompt + end) + ) + eq( + test.expected_text, + exec_lua(function() + return vim.lsp._stubs.input_text + end) + ) end client.stop() end @@ -4044,25 +4326,31 @@ describe('LSP', function() 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 + exec_lua(function() + vim.lsp.commands['dummy1'] = function(_) + 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.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + --- @diagnostic disable-next-line:duplicate-set-field vim.fn.inputlist = function() return 1 end vim.lsp.buf.code_action() - ]]) + end) elseif ctx.method == 'shutdown' then - eq('function', exec_lua [[return type(vim.lsp.commands['dummy2'])]]) + eq( + 'function', + exec_lua(function() + return type(vim.lsp.commands['dummy2']) + end) + ) client.stop() end end, } end) + it('Calls workspace/executeCommand if no client side command', function() local client --- @type vim.lsp.Client local expected_handlers = { @@ -4089,20 +4377,21 @@ describe('LSP', function() ctx.version = nil eq(table.remove(expected_handlers), { err, result, ctx }) if ctx.method == 'start' then - exec_lua([[ + exec_lua(function() local bufnr = vim.api.nvim_get_current_buf() - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) vim.fn.inputlist = function() return 1 end vim.lsp.buf.code_action() - ]]) + end) elseif ctx.method == 'shutdown' then client.stop() end end, }) end) + it('Filters and automatically applies action if requested', function() local client --- @type vim.lsp.Client local expected_handlers = { @@ -4122,83 +4411,102 @@ describe('LSP', function() 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 + exec_lua(function() + vim.lsp.commands['preferred_command'] = function(_) + vim.lsp.commands['executed_preferred'] = function() end end - vim.lsp.commands['type_annotate_command'] = function(cmd) - vim.lsp.commands['executed_type_annotate'] = function() - end + vim.lsp.commands['type_annotate_command'] = function(_) + vim.lsp.commands['executed_type_annotate'] = function() end end 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, }) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + vim.lsp.buf.code_action({ + filter = function(a) + return a.isPreferred + end, + apply = true, + }) vim.lsp.buf.code_action({ - -- expect to be returned actions 'type-annotate' and 'type-annotate.foo' - context = { only = { 'type-annotate' }, }, - apply = true, - filter = function(a) - if a.kind == 'type-annotate.foo' then - vim.lsp.commands['filtered_type_annotate_foo'] = function() end - return false - elseif a.kind == 'type-annotate' then - return true - else - assert(nil, 'unreachable') - end - end, + -- expect to be returned actions 'type-annotate' and 'type-annotate.foo' + context = { only = { 'type-annotate' } }, + apply = true, + filter = function(a) + if a.kind == 'type-annotate.foo' then + vim.lsp.commands['filtered_type_annotate_foo'] = function() end + return false + elseif a.kind == 'type-annotate' then + return true + else + assert(nil, 'unreachable') + end + end, }) - ]]) + end) elseif ctx.method == 'shutdown' then - eq('function', exec_lua [[return type(vim.lsp.commands['executed_preferred'])]]) - 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'])]]) + eq( + 'function', + exec_lua(function() + return type(vim.lsp.commands['executed_preferred']) + end) + ) + eq( + 'function', + exec_lua(function() + return type(vim.lsp.commands['filtered_type_annotate_foo']) + end) + ) + eq( + 'function', + exec_lua(function() + return type(vim.lsp.commands['executed_type_annotate']) + end) + ) client.stop() end end, } end) + it('Fallback to command execution on resolve error', function() clear() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server({ + local result = exec_lua(function() + local server = _G._create_server({ capabilities = { executeCommandProvider = { - commands = {"command:1"}, + commands = { 'command:1' }, }, codeActionProvider = { - resolveProvider = true - } + resolveProvider = true, + }, }, handlers = { - ["textDocument/codeAction"] = function(_, _, callback) + ['textDocument/codeAction'] = function(_, _, callback) callback(nil, { { - title = "Code Action 1", + title = 'Code Action 1', command = { - title = "Command 1", - command = "command:1", - } - } + title = 'Command 1', + command = 'command:1', + }, + }, }) end, - ["codeAction/resolve"] = function(_, _, callback) - callback("resolve failed", nil) + ['codeAction/resolve'] = function(_, _, callback) + callback('resolve failed', nil) end, - } + }, }) - local client_id = vim.lsp.start({ - name = "dummy", + local client_id = assert(vim.lsp.start({ + name = 'dummy', cmd = server.cmd, - }) + })) vim.lsp.buf.code_action({ apply = true }) vim.lsp.stop_client(client_id) return server.messages - ]]) + end) eq('codeAction/resolve', result[4].method) eq('workspace/executeCommand', result[5].method) eq('command:1', result[5].params.command) @@ -4212,6 +4520,7 @@ describe('LSP', function() 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', @@ -4241,32 +4550,32 @@ describe('LSP', function() 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 cmd = exec_lua(function() local bufnr = vim.uri_to_bufnr(fake_uri) vim.fn.bufload(bufnr) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'One line'}) + 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 } + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 8 }, }, - command = { title = 'Lens1', command = 'Dummy' } + command = { title = 'Lens1', command = 'Dummy' }, }, } - vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr}) + 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 + vim.lsp.commands['Dummy'] = function(command0) + cmd_called = command0 end vim.api.nvim_set_current_buf(bufnr) vim.lsp.codelens.run() return cmd_called - ]], - fake_uri - ) + end) eq({ command = 'Dummy', title = 'Lens1' }, cmd) elseif ctx.method == 'shutdown' then client.stop() @@ -4287,20 +4596,20 @@ describe('LSP', function() 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 - ]=]) + exec_lua(function() + 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, _G.TEST_RPC_CLIENT_ID) + + _G.CALLED = false + _G.RESPONSE = nil + local on_codelens = vim.lsp.codelens.on_codelens + vim.lsp.codelens.on_codelens = function(err, result, ...) + _G.CALLED = true + _G.RESPONSE = { err = err, result = result } + return on_codelens(err, result, ...) + end + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -4310,42 +4619,52 @@ describe('LSP', function() eq(table.remove(expected_handlers), { err, result, ctx }) if ctx.method == 'start' then -- 1. first codelens request errors - local response = exec_lua([=[ - CALLED = false + local response = exec_lua(function() + _G.CALLED = false vim.lsp.codelens.refresh() - vim.wait(100, function () return CALLED end) - return RESPONSE - ]=]) + vim.wait(100, function() + return _G.CALLED + end) + return _G.RESPONSE + end) 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 + response = exec_lua(function() + _G.CALLED = false + local cmd_called --- @type string? + vim.lsp.commands['Dummy'] = function(command0) + cmd_called = command0 end vim.lsp.codelens.refresh() - vim.wait(100, function () return CALLED end) + vim.wait(100, function() + return _G.CALLED + end) vim.lsp.codelens.run() - vim.wait(100, function () return cmd_called end) + vim.wait(100, function() + return cmd_called ~= nil + end) return cmd_called - ]=]) + end) 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 + response = exec_lua(function() + _G.CALLED = false + local cmd_called --- @type string? + vim.lsp.commands['Dummy'] = function(command0) + cmd_called = command0 end vim.lsp.codelens.refresh() - vim.wait(100, function () return CALLED end) + vim.wait(100, function() + return _G.CALLED + end) vim.lsp.codelens.run() - vim.wait(100, function () return cmd_called end) + vim.wait(100, function() + return cmd_called ~= nil + end) return cmd_called - ]=]) + end) eq({ command = 'Dummy', title = 'Lens2' }, response) elseif ctx.method == 'shutdown' then client.stop() @@ -4363,79 +4682,73 @@ describe('LSP', function() exec_lua(create_server_definition) -- setup lsp - exec_lua( - [[ - local lens_title_per_fake_uri = ... - local server = _create_server({ - capabilities = { - codeLensProvider = { - resolveProvider = true - }, + exec_lua(function() + local server = _G._create_server({ + capabilities = { + codeLensProvider = { + resolveProvider = true, }, - handlers = { - ["textDocument/codeLens"] = function(method, params, callback) - 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', - }, + }, + handlers = { + ['textDocument/codeLens'] = function(_, params, callback) + local lenses = { + { + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 }, }, - } - callback(nil, lenses) - end, - } - }) + command = { + title = lens_title_per_fake_uri[params.textDocument.uri], + command = 'Dummy', + }, + }, + } + callback(nil, lenses) + end, + }, + }) - CLIENT_ID = vim.lsp.start({ - name = "dummy", - cmd = server.cmd, - }) - ]], - lens_title_per_fake_uri - ) + _G.CLIENT_ID = vim.lsp.start({ + name = 'dummy', + cmd = server.cmd, + }) + end) -- 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 - ) + exec_lua(function() + 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, _G.CLIENT_ID) + end + vim.api.nvim_buf_delete(default_buf, { force = true }) + + _G.REQUEST_COUNT = vim.tbl_count(lens_title_per_fake_uri) + _G.RESPONSES = {} + local on_codelens = vim.lsp.codelens.on_codelens + vim.lsp.codelens.on_codelens = function(err, result, ctx, ...) + table.insert(_G.RESPONSES, { err = err, result = result, ctx = ctx }) + return on_codelens(err, result, ctx, ...) + end + end) -- call codelens refresh - local cmds = exec_lua([[ - RESPONSES = {} + local cmds = exec_lua(function() + _G.RESPONSES = {} vim.lsp.codelens.refresh() - vim.wait(100, function () return #RESPONSES >= REQUEST_COUNT end) + vim.wait(100, function() + return #_G.RESPONSES >= _G.REQUEST_COUNT + end) local cmds = {} - for _, resp in ipairs(RESPONSES) do + for _, resp in ipairs(_G.RESPONSES) do local uri = resp.ctx.params.textDocument.uri cmds[uri] = resp.result[1].command end return cmds - ]]) + end) eq({ command = 'Dummy', title = 'Lens1' }, cmds['file:///fake/uri1']) eq({ command = 'Dummy', title = 'Lens2' }, cmds['file:///fake/uri2']) end) @@ -4450,23 +4763,24 @@ describe('LSP', function() client = c end, on_handler = function() - local notify_msg = exec_lua([[ + local notify_msg = exec_lua(function() local bufnr = vim.api.nvim_get_current_buf() - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) - local notify_msg + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + local notify_msg --- @type string? local notify = vim.notify - vim.notify = function(msg, log_level) + vim.notify = function(msg, _) notify_msg = msg end vim.lsp.buf.format({ name = 'does-not-exist' }) vim.notify = notify return notify_msg - ]]) + end) 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 } }, @@ -4481,25 +4795,114 @@ describe('LSP', function() on_handler = function(_, _, ctx) table.remove(expected_handlers) if ctx.method == 'start' then - local notify_msg = exec_lua([[ + local notify_msg = exec_lua(function() local bufnr = vim.api.nvim_get_current_buf() - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) - local notify_msg + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + local notify_msg --- @type string? local notify = vim.notify - vim.notify = function(msg, log_level) + vim.notify = function(msg, _) notify_msg = msg end vim.lsp.buf.format({ bufnr = bufnr }) vim.notify = notify return notify_msg - ]]) - eq(NIL, notify_msg) + end) + eq(nil, notify_msg) + elseif ctx.method == 'shutdown' then + client.stop() + end + end, + } + end) + + it('Sends textDocument/rangeFormatting request to format a range', function() + local expected_handlers = { + { NIL, {}, { method = 'shutdown', client_id = 1 } }, + { NIL, {}, { method = 'start', client_id = 1 } }, + } + local client --- @type vim.lsp.Client + test_rpc_server { + test_name = 'range_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(function() + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'foo', 'bar' }) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + local notify_msg --- @type string? + local notify = vim.notify + vim.notify = function(msg, _) + notify_msg = msg + end + vim.lsp.buf.format({ + bufnr = bufnr, + range = { + start = { 1, 1 }, + ['end'] = { 1, 1 }, + }, + }) + vim.notify = notify + return notify_msg + end) + eq(nil, notify_msg) + elseif ctx.method == 'shutdown' then + client.stop() + end + end, + } + end) + + it('Sends textDocument/rangesFormatting request to format multiple ranges', function() + local expected_handlers = { + { NIL, {}, { method = 'shutdown', client_id = 1 } }, + { NIL, {}, { method = 'start', client_id = 1 } }, + } + local client --- @type vim.lsp.Client + test_rpc_server { + test_name = 'ranges_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(function() + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'foo', 'bar', 'baz' }) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + local notify_msg --- @type string? + local notify = vim.notify + vim.notify = function(msg, _) + notify_msg = msg + end + vim.lsp.buf.format({ + bufnr = bufnr, + range = { + { + start = { 1, 1 }, + ['end'] = { 1, 1 }, + }, + { + start = { 2, 2 }, + ['end'] = { 2, 2 }, + }, + }, + }) + vim.notify = notify + return notify_msg + end) + eq(nil, notify_msg) elseif ctx.method == 'shutdown' then client.stop() end end, } end) + it('Can format async', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, @@ -4514,29 +4917,31 @@ describe('LSP', function() on_handler = function(_, _, ctx) table.remove(expected_handlers) if ctx.method == 'start' then - local result = exec_lua([[ + local result = exec_lua(function() local bufnr = vim.api.nvim_get_current_buf() - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) - local notify_msg + local notify_msg --- @type string? local notify = vim.notify - vim.notify = function(msg, log_level) + vim.notify = function(msg, _) notify_msg = msg end local handler = vim.lsp.handlers['textDocument/formatting'] local handler_called = false - vim.lsp.handlers['textDocument/formatting'] = function(...) + 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.wait(1000, function() + return handler_called + end) vim.notify = notify vim.lsp.handlers['textDocument/formatting'] = handler - return {notify = notify_msg, handler_called = handler_called} - ]]) + return { notify = notify_msg, handler_called = handler_called } + end) eq({ handler_called = true }, result) elseif ctx.method == 'shutdown' then client.stop() @@ -4544,24 +4949,27 @@ describe('LSP', function() end, } end) + it('format formats range in visual mode', function() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server({ capabilities = { - documentFormattingProvider = true, - documentRangeFormattingProvider = true, - }}) + local result = exec_lua(function() + local server = _G._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 }) + local client_id = assert(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'}) + 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 }) vim.lsp.stop_client(client_id) return server.messages - ]]) + end) eq('textDocument/rangeFormatting', result[3].method) local expected_range = { start = { line = 0, character = 0 }, @@ -4569,17 +4977,20 @@ describe('LSP', function() } eq(expected_range, result[3].params.range) end) + 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 result = exec_lua(function() + local server = _G._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 }) + local client_id = assert(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_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 }) @@ -4587,7 +4998,7 @@ describe('LSP', function() -- Format again with visual lines going from bottom to top -- Must result in same formatting - vim.cmd.normal("<ESC>") + 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 }) @@ -4595,7 +5006,7 @@ describe('LSP', function() vim.lsp.stop_client(client_id) return server.messages - ]]) + end) local expected_methods = { 'initialize', 'initialized', @@ -4620,40 +5031,57 @@ describe('LSP', function() eq(expected_range, result[3].params.range) eq(expected_range, result[5].params.range) end) + it('Aborts with notify if no clients support requested method', function() exec_lua(create_server_definition) - exec_lua([[ + exec_lua(function() vim.notify = function(msg, _) - notify_msg = msg + _G.notify_msg = msg end - ]]) + end) local fail_msg = '[LSP] Format request failed, no matching language servers.' --- @param name string --- @param formatting boolean --- @param range_formatting boolean 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, - }}) + exec_lua(function() + local server = _G._create_server({ + capabilities = { + documentFormattingProvider = formatting, + documentRangeFormattingProvider = range_formatting, + }, + }) vim.lsp.start({ name = name, cmd = server.cmd }) - notify_msg = nil + _G.notify_msg = nil vim.lsp.buf.format({ name = name, timeout_ms = 1 }) - ]], - formatting, - range_formatting, - name + end) + eq( + formatting and timeout_msg or fail_msg, + exec_lua(function() + return _G.notify_msg + end) + ) + exec_lua(function() + _G.notify_msg = nil + vim.lsp.buf.format({ + name = name, + timeout_ms = 1, + range = { + start = { 1, 0 }, + ['end'] = { + 1, + 0, + }, + }, + }) + end) + eq( + range_formatting and timeout_msg or fail_msg, + exec_lua(function() + return _G.notify_msg + end) ) - 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) @@ -4683,10 +5111,9 @@ describe('LSP', function() }, } exec_lua(create_server_definition) - exec_lua( - [[ - _G.mock_locations = ... - _G.server = _create_server({ + exec_lua(function() + _G.mock_locations = mock_locations + _G.server = _G._create_server({ ---@type lsp.ServerCapabilities capabilities = { definitionProvider = true, @@ -4710,26 +5137,25 @@ describe('LSP', function() name = 'vim.foobar', kind = 12, ---@type lsp.SymbolKind location = _G.mock_locations[2], - } + }, }) end, }, }) - _G.client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]], - mock_locations - ) + _G.client_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) end) + after_each(function() - exec_lua [[ + exec_lua(function() vim.lsp.stop_client(_G.client_id) - ]] + end) end) it('with flags=c, returns matching tags using textDocument/definition', function() - local result = exec_lua [[ + local result = exec_lua(function() return vim.lsp.tagfunc('foobar', 'c') - ]] + end) eq({ { cmd = '/\\%6l\\%1c/', -- for location (5, 23) @@ -4740,9 +5166,9 @@ describe('LSP', function() end) it('without flags=c, returns all matching tags using workspace/symbol', function() - local result = exec_lua [[ + local result = exec_lua(function() return vim.lsp.tagfunc('foobar', '') - ]] + end) eq({ { cmd = '/\\%6l\\%1c/', -- for location (5, 23) @@ -4761,80 +5187,81 @@ describe('LSP', function() end) describe('cmd', function() - it('can connect to lsp server via rpc.connect', function() - local result = exec_lua [[ - local uv = vim.uv - 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 + it('connects to lsp server via rpc.connect using ip address', function() + exec_lua(create_tcp_echo_server) + local result = exec_lua(function() + local server, port, last_message = _G._create_tcp_server('127.0.0.1') 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") + vim.wait(1000, function() + return last_message() ~= nil + end) + local init = last_message() + assert(init, 'server must receive `initialize` request') + server:close() + server:shutdown() + return vim.json.decode(init) + end) + eq('initialize', result.method) + end) + + it('connects to lsp server via rpc.connect using hostname', function() + skip(is_os('bsd'), 'issue with host resolution in ci') + exec_lua(create_tcp_echo_server) + local result = exec_lua(function() + local server, port, last_message = _G._create_tcp_server('::1') + vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('localhost', port) }) + vim.wait(1000, function() + return last_message() ~= nil + end) + local init = last_message() + assert(init, 'server must receive `initialize` request') server:close() server:shutdown() return vim.json.decode(init) - ]] + end) eq('initialize', result.method) end) + it('can connect to lsp server via pipe or domain_socket', function() - local tmpfile --- @type string - if is_os('win') then - tmpfile = '\\\\.\\\\pipe\\pipe.test' - else - tmpfile = tmpname() - os.remove(tmpfile) - end - local result = exec_lua( - [[ - local SOCK = ... + local tmpfile = is_os('win') and '\\\\.\\\\pipe\\pipe.test' or tmpname(false) + local result = exec_lua(function() local uv = vim.uv - local server = uv.new_pipe(false) - server:bind(SOCK) + local server = assert(uv.new_pipe(false)) + server:bind(tmpfile) 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)) + assert(not err, err) + local client = assert(vim.uv.new_pipe()) + server:accept(client) + client:read_start(require('vim.lsp.rpc').create_read_loop(function(body) + init = body + client:close() + end)) + end) + vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect(tmpfile) }) + vim.wait(1000, function() + return init ~= nil end) - vim.lsp.start({ name = "dummy", cmd = vim.lsp.rpc.connect(SOCK) }) - vim.wait(1000, function() return init ~= nil end) - assert(init, "server must receive `initialize` request") + assert(init, 'server must receive `initialize` request') server:close() server:shutdown() return vim.json.decode(init) - ]], - tmpfile - ) + end) eq('initialize', result.method) end) end) describe('handlers', function() it('handler can return false as response', function() - local result = exec_lua [[ - local uv = vim.uv - local server = uv.new_tcp() + local result = exec_lua(function() + local server = assert(vim.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() + local socket = assert(vim.uv.new_tcp()) server:accept(socket) socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body) local payload = vim.json.decode(body) @@ -4845,10 +5272,10 @@ describe('LSP', function() id = payload.id, jsonrpc = '2.0', result = { - capabilities = {} + capabilities = {}, }, }) - socket:write(table.concat({'Content-Length: ', tostring(#msg), '\r\n\r\n', msg})) + 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, @@ -4856,7 +5283,7 @@ describe('LSP', function() method = 'dummy', params = {}, }) - socket:write(table.concat({'Content-Length: ', tostring(#msg), '\r\n\r\n', msg})) + socket:write(table.concat({ 'Content-Length: ', tostring(#msg), '\r\n\r\n', msg })) end else table.insert(responses, payload) @@ -4866,20 +5293,24 @@ describe('LSP', function() end) local port = server:getsockname().port local handler_called = false - vim.lsp.handlers['dummy'] = function(err, result) + vim.lsp.handlers['dummy'] = function(_, _) 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) + local client_id = + assert(vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('127.0.0.1', port) })) + 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 } - ]] + responses = responses, + } + end) local expected = { messages = { 'initialize', 'initialized' }, handler_called = true, @@ -4897,9 +5328,7 @@ describe('LSP', function() describe('#dynamic vim.lsp._dynamic', function() it('supports dynamic registration', function() - ---@type string - local root_dir = tmpname() - os.remove(root_dir) + local root_dir = tmpname(false) mkdir(root_dir) local tmpfile = root_dir .. '/dynamic.foo' local file = io.open(tmpfile, 'w') @@ -4908,17 +5337,14 @@ describe('LSP', function() end exec_lua(create_server_definition) - local result = exec_lua( - [[ - local root_dir, tmpfile = ... - - local server = _create_server() - local client_id = vim.lsp.start({ + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ name = 'dynamic-test', cmd = server.cmd, root_dir = root_dir, get_language_id = function() - return "dummy-lang" + return 'dummy-lang' end, capabilities = { textDocument = { @@ -4930,9 +5356,7 @@ describe('LSP', function() }, }, }, - }) - - local expected_messages = 2 -- initialize, initialized + })) vim.lsp.handlers['client/registerCapability'](nil, { registrations = { @@ -4940,9 +5364,11 @@ describe('LSP', function() id = 'formatting', method = 'textDocument/formatting', registerOptions = { - documentSelector = {{ - pattern = root_dir .. '/*.foo', - }}, + documentSelector = { + { + pattern = root_dir .. '/*.foo', + }, + }, }, }, }, @@ -4954,12 +5380,12 @@ describe('LSP', function() id = 'range-formatting', method = 'textDocument/rangeFormatting', registerOptions = { - documentSelector = { + documentSelector = { { - language = "dummy-lang" + language = 'dummy-lang', }, - } - } + }, + }, }, }, }, { client_id = client_id }) @@ -4976,26 +5402,22 @@ describe('LSP', function() 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) + local client = assert(vim.lsp.get_client_by_id(client_id)) result[#result + 1] = { method = method, fname = fname, - supported = client.supports_method(method, {bufnr = bufnr}) + supported = client.supports_method(method, { bufnr = bufnr }), } end - - check("textDocument/formatting") - check("textDocument/formatting", tmpfile) - check("textDocument/rangeFormatting") - check("textDocument/rangeFormatting", tmpfile) - check("textDocument/completion") + check('textDocument/formatting') + check('textDocument/formatting', tmpfile) + check('textDocument/rangeFormatting') + check('textDocument/rangeFormatting', tmpfile) + check('textDocument/completion') return result - ]], - root_dir, - tmpfile - ) + end) eq(5, #result) eq({ method = 'textDocument/formatting', supported = false }, result[1]) @@ -5007,16 +5429,26 @@ describe('LSP', function() end) describe('vim.lsp._watchfiles', function() + --- @type integer, integer, integer + local created, changed, deleted + + setup(function() + clear() + created = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]) + changed = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]) + deleted = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]) + end) + local function test_filechanges(watchfunc) it( string.format('sends notifications when files change (watchfunc=%s)', watchfunc), function() - if watchfunc == 'fswatch' then + if watchfunc == 'inotify' then skip(is_os('win'), 'not supported on windows') skip(is_os('mac'), 'flaky test on mac') skip( - not is_ci() and fn.executable('fswatch') == 0, - 'fswatch not installed and not on CI' + not is_ci() and fn.executable('inotifywait') == 0, + 'inotify-tools not installed and not on CI' ) end @@ -5033,87 +5465,80 @@ describe('LSP', function() ) end - local root_dir = tmpname() - os.remove(root_dir) + local root_dir = tmpname(false) mkdir(root_dir) 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, + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = root_dir, + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, }, }, - }, - }) + })) - require('vim.lsp._watchfiles')._watchfunc = require('vim._watch')[watchfunc] + require('vim.lsp._watchfiles')._watchfunc = require('vim._watch')[watchfunc] - local expected_messages = 0 + local expected_messages = 0 - local msg_wait_timeout = watchfunc == 'watch' and 200 or 2500 + local msg_wait_timeout = watchfunc == 'watch' and 200 or 2500 - 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 + 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 + 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, + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-0', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/watch', + kind = 7, + }, }, }, }, }, - }, - }, { client_id = client_id }) + }, { client_id = client_id }) - if watchfunc ~= 'watch' then - vim.wait(100) - end + if watchfunc ~= 'watch' then + vim.wait(100) + end - local path = root_dir .. '/watch' - local tmp = vim.fn.tempname() - io.open(tmp, 'w'):close() - vim.uv.fs_rename(tmp, path) + local path = root_dir .. '/watch' + local tmp = vim.fn.tempname() + io.open(tmp, 'w'):close() + vim.uv.fs_rename(tmp, path) - wait_for_message() + wait_for_message() - os.remove(path) + os.remove(path) - wait_for_message() + wait_for_message() - vim.lsp.stop_client(client_id) + vim.lsp.stop_client(client_id) - return server.messages - ]], - root_dir, - watchfunc - ) + return server.messages + end) local uri = vim.uri_from_fname(root_dir .. '/watch') @@ -5124,7 +5549,7 @@ describe('LSP', function() params = { changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = uri, }, }, @@ -5136,7 +5561,7 @@ describe('LSP', function() params = { changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + type = deleted, uri = uri, }, }, @@ -5148,17 +5573,14 @@ describe('LSP', function() test_filechanges('watch') test_filechanges('watchdirs') - test_filechanges('fswatch') + test_filechanges('inotify') it('correctly registers and unregisters', 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({ + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, @@ -5169,16 +5591,22 @@ describe('LSP', function() }, }, }, - }) + })) 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)) + 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 + local send_event --- @type function require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) local stopped = false send_event = function(...) @@ -5245,9 +5673,7 @@ describe('LSP', function() wait_for_messages() return server.messages - ]], - root_dir - ) + end) local function watched_uri(fname) return vim.uri_from_fname(root_dir .. '/' .. fname) @@ -5258,7 +5684,7 @@ describe('LSP', function() eq({ changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('file.watch0'), }, }, @@ -5267,7 +5693,7 @@ describe('LSP', function() eq({ changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('file.watch1'), }, }, @@ -5277,12 +5703,9 @@ describe('LSP', function() 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({ + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, @@ -5293,16 +5716,22 @@ describe('LSP', function() }, }, }, - }) + })) 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)) + 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 watch_callbacks = {} --- @type function[] local function send_event(...) for _, cb in ipairs(watch_callbacks) do cb(...) @@ -5318,12 +5747,14 @@ describe('LSP', function() local protocol = require('vim.lsp.protocol') local watchers = {} - local max_kind = protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete + local max_kind = protocol.WatchKind.Create + + protocol.WatchKind.Change + + protocol.WatchKind.Delete for i = 0, max_kind do table.insert(watchers, { globPattern = { baseUri = vim.uri_from_fname('/dir'), - pattern = 'watch'..tostring(i), + pattern = 'watch' .. tostring(i), }, kind = i, }) @@ -5351,9 +5782,7 @@ describe('LSP', function() wait_for_messages() return server.messages - ]], - root_dir - ) + end) local function watched_uri(fname) return vim.uri_from_fname('/dir/' .. fname) @@ -5364,51 +5793,51 @@ describe('LSP', function() eq({ changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('watch1'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + type = changed, uri = watched_uri('watch2'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('watch3'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + type = changed, uri = watched_uri('watch3'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + type = deleted, uri = watched_uri('watch4'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('watch5'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + type = deleted, uri = watched_uri('watch5'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + type = changed, uri = watched_uri('watch6'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + type = deleted, uri = watched_uri('watch6'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('watch7'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + type = changed, uri = watched_uri('watch7'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + type = deleted, uri = watched_uri('watch7'), }, }, @@ -5418,12 +5847,9 @@ describe('LSP', function() 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({ + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, @@ -5434,16 +5860,22 @@ describe('LSP', function() }, }, }, - }) + })) 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)) + 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 + local send_event --- @type function require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) send_event = callback return function() @@ -5477,24 +5909,22 @@ describe('LSP', function() wait_for_messages() return server.messages - ]], - root_dir - ) + end) eq(3, #result) eq('workspace/didChangeWatchedFiles', result[3].method) eq({ changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = vim.uri_from_fname('file1'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + type = changed, uri = vim.uri_from_fname('file1'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = vim.uri_from_fname('file2'), }, }, @@ -5503,27 +5933,28 @@ describe('LSP', function() it("ignores registrations by servers when the client doesn't advertise support", function() exec_lua(create_server_definition) - exec_lua([[ - server = _create_server() - require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) + exec_lua(function() + _G.server = _G._create_server() + require('vim.lsp._watchfiles')._watchfunc = function(_, _, _) -- Since the registration is ignored, this should not execute and `watching` should stay false - watching = true + _G.watching = true return function() end end - ]]) + end) local function check_registered(capabilities) - return exec_lua( - [[ - watching = false - local client_id = vim.lsp.start({ + return exec_lua(function() + _G.watching = false + local client_id = assert(vim.lsp.start({ name = 'watchfiles-test', - cmd = server.cmd, + cmd = _G.server.cmd, root_dir = 'some_dir', - capabilities = ..., + capabilities = capabilities, }, { - reuse_client = function() return false end, - }) + reuse_client = function() + return false + end, + })) vim.lsp.handlers['client/registerCapability'](nil, { registrations = { @@ -5552,10 +5983,8 @@ describe('LSP', function() }, { client_id = client_id }) vim.lsp.stop_client(client_id, true) - return watching - ]], - capabilities - ) + return _G.watching + end) end eq(is_os('mac') or is_os('win'), check_registered(nil)) -- start{_client}() defaults to make_client_capabilities(). |