diff options
Diffstat (limited to 'test/functional/plugin/lsp_spec.lua')
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 613 |
1 files changed, 465 insertions, 148 deletions
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 508a9f2aed..572573a3a6 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -6,6 +6,7 @@ local buf_lines = helpers.buf_lines local dedent = helpers.dedent local exec_lua = helpers.exec_lua local eq = helpers.eq +local matches = helpers.matches local pcall_err = helpers.pcall_err local pesc = helpers.pesc local insert = helpers.insert @@ -14,6 +15,7 @@ local retry = helpers.retry local NIL = helpers.NIL local read_file = require('test.helpers').read_file local write_file = require('test.helpers').write_file +local isCI = helpers.isCI -- Use these to get access to a coroutine so that I can run async tests and use -- yield. @@ -216,7 +218,7 @@ describe('LSP', function() it('should run correctly', function() local expected_handlers = { - {NIL, "test", {}, 1}; + {NIL, {}, {method="test", client_id=1}}; } test_rpc_server { test_name = "basic_init"; @@ -241,7 +243,7 @@ describe('LSP', function() it('should fail', function() local expected_handlers = { - {NIL, "test", {}, 1}; + {NIL, {}, {method="test", client_id=1}}; } test_rpc_server { test_name = "basic_init"; @@ -262,15 +264,15 @@ describe('LSP', function() end) it('should succeed with manual shutdown', function() - if 'openbsd' == helpers.uname() then - pending('hangs the build on openbsd #14028, re-enable with freeze timeout #14204') + if isCI() then + pending('hangs the build on CI #14028, re-enable with freeze timeout #14204') return elseif helpers.skip_fragile(pending) then return end local expected_handlers = { - {NIL, "shutdown", {}, 1, NIL}; - {NIL, "test", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="test", client_id=1}}; } test_rpc_server { test_name = "basic_init"; @@ -292,12 +294,12 @@ describe('LSP', function() it('client should return settings via workspace/configuration handler', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "workspace/configuration", { items = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, { items = { { section = "testSetting1" }; { section = "testSetting2" }; - }}, 1}; - {NIL, "start", {}, 1}; + }}, { method="workspace/configuration", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -309,9 +311,9 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'start' then + on_handler = function(err, result, ctx) + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'start' then exec_lua([=[ local client = vim.lsp.get_client_by_id(TEST_RPC_CLIENT_ID) client.config.settings = { @@ -319,13 +321,13 @@ describe('LSP', function() testSetting2 = false; }]=]) end - if method == 'workspace/configuration' then - local result = exec_lua([=[ + if ctx.method == 'workspace/configuration' then + local server_result = exec_lua([=[ local method, params = ... - return require'vim.lsp.handlers'['workspace/configuration'](err, method, params, TEST_RPC_CLIENT_ID)]=], method, params) - client.notify('workspace/configuration', result) + return require'vim.lsp.handlers'['workspace/configuration'](err, params, {method=method, client_id=TEST_RPC_CLIENT_ID})]=], ctx.method, result) + client.notify('workspace/configuration', server_result) end - if method == 'shutdown' then + if ctx.method == 'shutdown' then client.stop() end end; @@ -335,19 +337,19 @@ describe('LSP', function() clear_notrace() fake_lsp_server_setup('workspace/configuration no settings') eq({ NIL, NIL, }, exec_lua [[ - local params = { + local result = { items = { {section = 'foo'}, {section = 'bar'}, } } - return vim.lsp.handlers['workspace/configuration'](nil, nil, params, TEST_RPC_CLIENT_ID) + return vim.lsp.handlers['workspace/configuration'](nil, result, {client_id=TEST_RPC_CLIENT_ID}) ]]) end) it('should verify capabilities sent', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; } test_rpc_server { test_name = "basic_check_capabilities"; @@ -371,7 +373,7 @@ describe('LSP', function() it('client.supports_methods() should validate capabilities', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; } test_rpc_server { test_name = "capabilities_for_client_supports_method"; @@ -405,7 +407,7 @@ describe('LSP', function() it('should call unsupported_method when trying to call an unsupported method', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; } test_rpc_server { test_name = "capabilities_for_client_supports_method"; @@ -413,7 +415,8 @@ describe('LSP', 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(err, method) + vim.lsp.handlers['textDocument/typeDefinition'] = function(err, result, ctx) + local method = ctx.method vim.lsp._last_lsp_handler = { err = err; method = method } end vim.lsp._unsupported_method = function(method) @@ -446,7 +449,7 @@ describe('LSP', function() it('shouldn\'t call unsupported_method when no client and trying to call an unsupported method', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; } test_rpc_server { test_name = "capabilities_for_client_supports_method"; @@ -479,8 +482,8 @@ describe('LSP', function() it('should not send didOpen if the buffer closes before init', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; } local client test_rpc_server { @@ -511,9 +514,9 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'finish' then + on_handler = function(err, result, ctx) + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end end; @@ -522,9 +525,9 @@ describe('LSP', function() it('should check the body sent attaching before init', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; - {NIL, "start", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -554,12 +557,12 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - if method == 'start' then + on_handler = function(err, result, ctx) + if ctx.method == 'start' then client.notify('finish') end - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'finish' then + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end end; @@ -568,9 +571,9 @@ describe('LSP', function() it('should check the body sent attaching after init', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; - {NIL, "start", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -597,12 +600,12 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - if method == 'start' then + on_handler = function(err, result, ctx) + if ctx.method == 'start' then client.notify('finish') end - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'finish' then + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end end; @@ -611,9 +614,9 @@ describe('LSP', function() it('should check the body and didChange full', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; - {NIL, "start", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -640,8 +643,8 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - if method == 'start' then + on_handler = function(err, result, ctx) + if ctx.method == 'start' then exec_lua [[ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { "boop"; @@ -649,8 +652,8 @@ describe('LSP', function() ]] client.notify('finish') end - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'finish' then + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end end; @@ -659,9 +662,9 @@ describe('LSP', function() it('should check the body and didChange full with noeol', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; - {NIL, "start", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -689,8 +692,8 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - if method == 'start' then + on_handler = function(err, result, ctx) + if ctx.method == 'start' then exec_lua [[ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { "boop"; @@ -698,8 +701,8 @@ describe('LSP', function() ]] client.notify('finish') end - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'finish' then + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end end; @@ -708,9 +711,9 @@ describe('LSP', function() it('should check the body and didChange incremental', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; - {NIL, "start", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -738,8 +741,8 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - if method == 'start' then + on_handler = function(err, result, ctx) + if ctx.method == 'start' then exec_lua [[ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { "123boop"; @@ -747,8 +750,8 @@ describe('LSP', function() ]] client.notify('finish') end - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'finish' then + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end end; @@ -758,9 +761,9 @@ describe('LSP', function() -- TODO(askhan) we don't support full for now, so we can disable these tests. pending('should check the body and didChange incremental normal mode editing', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; - {NIL, "start", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -787,13 +790,13 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - if method == 'start' then + on_handler = function(err, result, ctx) + if ctx.method == 'start' then helpers.command("normal! 1Go") client.notify('finish') end - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'finish' then + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end end; @@ -802,9 +805,9 @@ describe('LSP', function() it('should check the body and didChange full with 2 changes', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; - {NIL, "start", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -831,8 +834,8 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - if method == 'start' then + on_handler = function(err, result, ctx) + if ctx.method == 'start' then exec_lua [[ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { "321"; @@ -843,8 +846,8 @@ describe('LSP', function() ]] client.notify('finish') end - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'finish' then + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end end; @@ -853,9 +856,9 @@ describe('LSP', function() it('should check the body and didChange full lifecycle', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; - {NIL, "start", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -882,8 +885,8 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - if method == 'start' then + on_handler = function(err, result,ctx) + if ctx.method == 'start' then exec_lua [[ vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { "321"; @@ -895,8 +898,8 @@ describe('LSP', function() ]] client.notify('finish') end - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'finish' then + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end end; @@ -907,9 +910,9 @@ describe('LSP', function() describe("parsing tests", function() it('should handle invalid content-length correctly', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; - {NIL, "start", {}, 1}; + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -924,22 +927,22 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") + on_handler = function(err, result, ctx) + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") end; } end) it('should not trim vim.NIL from the end of a list', function() local expected_handlers = { - {NIL, "shutdown", {}, 1}; - {NIL, "finish", {}, 1}; - {NIL, "workspace/executeCommand", { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL,{ arguments = { "EXTRACT_METHOD", {metadata = {}}, 3, 0, 6123, NIL }, command = "refactor.perform", title = "EXTRACT_METHOD" - }, 1}; - {NIL, "start", {}, 1}; + }, {method="workspace/executeCommand", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; } local client test_rpc_server { @@ -963,9 +966,9 @@ describe('LSP', function() eq(0, code, "exit code", fake_lsp_logfile) eq(0, signal, "exit signal", fake_lsp_logfile) end; - on_handler = function(err, method, params, client_id) - eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler") - if method == 'finish' then + on_handler = function(err, result, ctx) + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end end; @@ -1014,31 +1017,6 @@ describe('LSP', function() } end - it('highlight groups', function() - eq({ - 'LspDiagnosticsDefaultError', - 'LspDiagnosticsDefaultHint', - 'LspDiagnosticsDefaultInformation', - 'LspDiagnosticsDefaultWarning', - 'LspDiagnosticsFloatingError', - 'LspDiagnosticsFloatingHint', - 'LspDiagnosticsFloatingInformation', - 'LspDiagnosticsFloatingWarning', - 'LspDiagnosticsSignError', - 'LspDiagnosticsSignHint', - 'LspDiagnosticsSignInformation', - 'LspDiagnosticsSignWarning', - 'LspDiagnosticsUnderlineError', - 'LspDiagnosticsUnderlineHint', - 'LspDiagnosticsUnderlineInformation', - 'LspDiagnosticsUnderlineWarning', - 'LspDiagnosticsVirtualTextError', - 'LspDiagnosticsVirtualTextHint', - 'LspDiagnosticsVirtualTextInformation', - 'LspDiagnosticsVirtualTextWarning', - }, exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]])) - end) - describe('apply_text_edits', function() before_each(function() insert(dedent([[ @@ -1088,6 +1066,30 @@ describe('LSP', function() 'å å ɧ 汉语 ↥ 🤦 🦄'; }, buf_lines(1)) end) + it('applies complex edits (reversed range)', function() + local edits = { + make_edit(0, 0, 0, 0, {"", "12"}); + make_edit(0, 0, 0, 0, {"3", "foo"}); + make_edit(0, 1, 0, 1, {"bar", "123"}); + make_edit(0, #"First line of text", 0, #"First ", {"guy"}); + make_edit(1, #'Second', 1, 0, {"baz"}); + make_edit(2, #"Third", 2, #'Th', {"e next"}); + make_edit(3, #"Fourth", 3, #'', {"another line of text", "before this"}); + make_edit(3, #"Fourth line of text", 3, #'Fourth', {"!"}); + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + ''; + '123'; + 'fooFbar'; + '123irst guy'; + 'baz line of text'; + 'The next line of text'; + 'another line of text'; + 'before this!'; + 'å å ɧ 汉语 ↥ 🤦 🦄'; + }, buf_lines(1)) + end) it('applies non-ASCII characters edits', function() local edits = { make_edit(4, 3, 4, 4, {"ä"}); @@ -1116,6 +1118,86 @@ describe('LSP', function() }, buf_lines(1)) end) + describe('cursor position', function() + it('don\'t fix the cursor if the range contains the cursor', function() + funcs.nvim_win_set_cursor(0, { 2, 6 }) + local edits = { + make_edit(1, 0, 1, 19, 'Second line of text') + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + 'First line of text'; + 'Second line of text'; + 'Third line of text'; + 'Fourth line of text'; + 'å å ɧ 汉语 ↥ 🤦 🦄'; + }, buf_lines(1)) + eq({ 2, 6 }, funcs.nvim_win_get_cursor(0)) + end) + + it('fix the cursor to the valid column if the content was removed', function() + funcs.nvim_win_set_cursor(0, { 2, 6 }) + local edits = { + make_edit(1, 0, 1, 19, '') + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + 'First line of text'; + ''; + 'Third line of text'; + 'Fourth line of text'; + 'å å ɧ 汉语 ↥ 🤦 🦄'; + }, buf_lines(1)) + eq({ 2, 0 }, funcs.nvim_win_get_cursor(0)) + end) + + it('fix the cursor row', function() + funcs.nvim_win_set_cursor(0, { 3, 0 }) + local edits = { + make_edit(1, 0, 2, 0, '') + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + 'First line of text'; + 'Third line of text'; + 'Fourth line of text'; + 'å å ɧ 汉语 ↥ 🤦 🦄'; + }, buf_lines(1)) + eq({ 2, 0 }, funcs.nvim_win_get_cursor(0)) + end) + + it('fix the cursor col', function() + funcs.nvim_win_set_cursor(0, { 2, 11 }) + local edits = { + make_edit(1, 7, 1, 11, '') + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + 'First line of text'; + 'Second of text'; + 'Third line of text'; + 'Fourth line of text'; + 'å å ɧ 汉语 ↥ 🤦 🦄'; + }, buf_lines(1)) + eq({ 2, 7 }, funcs.nvim_win_get_cursor(0)) + end) + + it('fix the cursor row and col', function() + funcs.nvim_win_set_cursor(0, { 2, 12 }) + local edits = { + make_edit(0, 11, 1, 12, '') + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + 'First line of text'; + 'Third line of text'; + 'Fourth line of text'; + 'å å ɧ 汉语 ↥ 🤦 🦄'; + }, buf_lines(1)) + eq({ 1, 11 }, funcs.nvim_win_get_cursor(0)) + end) + end) + describe('with LSP end line after what Vim considers to be the end line', function() it('applies edits when the last linebreak is considered a new line', function() local edits = { @@ -1149,14 +1231,14 @@ describe('LSP', function() make_edit(0, 0, 0, 3, "First ↥ 🤦 🦄") }, textDocument = { - uri = "file://fake/uri"; + uri = "file:///fake/uri"; version = editVersion } } end before_each(function() target_bufnr = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file://fake/uri") + 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 @@ -1223,7 +1305,7 @@ describe('LSP', function() label = nil; edit = {}; } - return vim.lsp.handlers['workspace/applyEdit'](nil, nil, apply_edit) + return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit) ]]) end) end) @@ -1236,7 +1318,7 @@ describe('LSP', function() make_edit(row, 0, row, 1000, new_line) }, textDocument = { - uri = "file://fake/uri"; + uri = "file:///fake/uri"; version = editVersion } } @@ -1254,7 +1336,7 @@ describe('LSP', function() before_each(function() local ret = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file://fake/uri") + local bufnr = vim.uri_to_bufnr("file:///fake/uri") local lines = { "Original Line #1", "Original Line #2" @@ -1441,8 +1523,10 @@ describe('LSP', function() { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} }, -- nested snippet tokens { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} }, + -- braced tabstop + { label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} }, -- plain text - { label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} }, + { label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} }, } local completion_list_items = {items=completion_list} local expected = { @@ -1454,8 +1538,9 @@ describe('LSP', function() { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="f", textEdit={newText='foobar'} } } } } }, { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } }, { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar()', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } }, } eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix)) @@ -1534,19 +1619,19 @@ describe('LSP', function() it('Convert Location[] to items', function() local expected = { { - filename = 'fake/uri', + filename = '/fake/uri', lnum = 1, col = 3, text = 'testing' }, } local actual = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file://fake/uri") + 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', + uri = 'file:///fake/uri', range = { start = { line = 0, character = 2 }, ['end'] = { line = 0, character = 3 }, @@ -1560,14 +1645,14 @@ describe('LSP', function() it('Convert LocationLink[] to items', function() local expected = { { - filename = 'fake/uri', + filename = '/fake/uri', lnum = 1, col = 3, text = 'testing' }, } local actual = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file://fake/uri") + 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 = { @@ -1781,14 +1866,14 @@ describe('LSP', function() local expected = { { col = 1, - filename = 'test_a', + filename = '/test_a', kind = 'File', lnum = 2, text = '[File] TestA' }, { col = 1, - filename = 'test_b', + filename = '/test_b', kind = 'Module', lnum = 4, text = '[Module] TestB' @@ -1811,7 +1896,7 @@ describe('LSP', function() line = 2 } }, - uri = "file://test_a" + uri = "file:///test_a" }, contanerName = "TestAContainer" }, @@ -1830,7 +1915,7 @@ describe('LSP', function() line = 4 } }, - uri = "file://test_b" + uri = "file:///test_b" }, contanerName = "TestBContainer" } @@ -1869,7 +1954,7 @@ describe('LSP', function() before_each(function() target_bufnr = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file://fake/uri") + local bufnr = vim.uri_to_bufnr("file:///fake/uri") local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"} vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr @@ -1878,7 +1963,7 @@ describe('LSP', function() local location = function(start_line, start_char, end_line, end_char) return { - uri = "file://fake/uri", + uri = "file:///fake/uri", range = { start = { line = start_line, character = start_char }, ["end"] = { line = end_line, character = end_char }, @@ -1903,7 +1988,7 @@ describe('LSP', function() it('jumps to a LocationLink', function() local pos = jump({ - targetUri = "file://fake/uri", + targetUri = "file:///fake/uri", targetSelectionRange = { start = { line = 0, character = 4 }, ["end"] = { line = 0, character = 4 }, @@ -1937,6 +2022,83 @@ describe('LSP', function() end) end) + describe('lsp.util.make_floating_popup_options', function() + before_each(function() + exec_lua [[ + local bufnr = vim.uri_to_bufnr("file:///fake/uri") + local winheight = vim.fn.winheight(0) + for i = 1, winheight do + vim.api.nvim_buf_set_lines(bufnr, 0, 0, false, {''}) + end + vim.api.nvim_win_set_buf(0, bufnr) + vim.api.nvim_win_set_cursor(0, {winheight, 0}) + ]] + end) + + local function popup_row(opts) + return exec_lua([[ + return vim.lsp.util.make_floating_popup_options(...).row + ]], 2, 2, opts) + end + + local err_pattern = "^Error executing lua: %.%.%./util%.lua:0: invalid floating preview border: .*%. :help vim%.api%.nvim_open_win%(%)$" + + it('calculates default border height correctly', function() + eq(0, popup_row()) + end) + + it('calculates string border height correctly', function() + eq(0, popup_row({border = 'none'})) + eq(-2, popup_row({border = 'single'})) + eq(-2, popup_row({border = 'double'})) + eq(-2, popup_row({border = 'rounded'})) + eq(-2, popup_row({border = 'solid'})) + eq(-1, popup_row({border = 'shadow'})) + end) + + it('error on invalid string border', function() + matches(err_pattern, pcall_err(popup_row, {border = ''})) + matches(err_pattern, pcall_err(popup_row, {border = 'invalid'})) + end) + + it('error on invalid array border length', function() + matches(err_pattern, pcall_err(popup_row, {border = {}})) + matches(err_pattern, pcall_err(popup_row, {border = {'', '', ''}})) + matches(err_pattern, pcall_err(popup_row, {border = {'', '', '', '', ''}})) + end) + + it('error on invalid array border member type', function() + matches(err_pattern, pcall_err(popup_row, {border = {0}})) + end) + + it('calculates 8-array border height correctly', function() + eq(0, popup_row({border = {'', '', '', '', '', '', '', ''}})) + eq(-2, popup_row({border = {'', '~', '', '~', '', '~', '', '~'}})) + eq(-1, popup_row({border = {'', '', '', '~', '', '~', '', ''}})) + eq(0, popup_row({border = {'', '', '', {'~', 'NormalFloat'}, '', '', '', {'~', 'NormalFloat'}}})) + eq(-2, popup_row({border = {'', {'~', 'NormalFloat'}, '', '', '', {'~', 'NormalFloat'}, '', ''}})) + end) + + it('calculates 4-array border height correctly', function() + eq(0, popup_row({border = {'', '', '', ''}})) + eq(-2, popup_row({border = {'', '~', '', '~'}})) + eq(0, popup_row({border = {'', '', '', {'~', 'NormalFloat'}}})) + eq(-2, popup_row({border = {'', {'~', 'NormalFloat'}, '', ''}})) + end) + + it('calculates 2-array border height correctly', function() + eq(0, popup_row({border = {'', ''}})) + eq(-2, popup_row({border = {'', '~'}})) + eq(-2, popup_row({border = {'', {'~', 'NormalFloat'}}})) + end) + + it('calculates 1-array border height correctly', function() + eq(0, popup_row({border = {''}})) + eq(-2, popup_row({border = {'~'}})) + eq(-2, popup_row({border = {{'~', 'NormalFloat'}}})) + end) + end) + describe('lsp.util._make_floating_popup_size', function() before_each(function() exec_lua [[ contents = @@ -1955,6 +2117,12 @@ describe('LSP', function() end) end) + describe('lsp.util.trim.trim_empty_lines', function() + it('properly trims empty lines', function() + eq({{"foo", "bar"}}, exec_lua[[ return vim.lsp.util.trim_empty_lines({{ "foo", "bar" }, nil}) ]]) + end) + end) + describe('lsp.util.get_effective_tabstop', function() local function test_tabstop(tabsize, softtabstop) exec_lua(string.format([[ @@ -1973,7 +2141,7 @@ 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']() + require'vim.lsp.handlers'['callHierarchy/outgoingCalls'](nil, nil, {}, nil) return #vim.fn.getqflist() ]=]) eq(0, qflist_count) @@ -2020,14 +2188,16 @@ describe('LSP', function() } } } local handler = require'vim.lsp.handlers'['callHierarchy/outgoingCalls'] - handler(nil, nil, rust_analyzer_response) + handler(nil, rust_analyzer_response, {}) return vim.fn.getqflist() ]=]) local expected = { { bufnr = 2, col = 5, + end_col = 0, lnum = 4, + end_lnum = 0, module = "", nr = 0, pattern = "", @@ -2044,7 +2214,7 @@ 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']() + require'vim.lsp.handlers'['callHierarchy/incomingCalls'](nil, nil, {}) return #vim.fn.getqflist() ]=]) eq(0, qflist_count) @@ -2092,14 +2262,16 @@ describe('LSP', function() } } local handler = require'vim.lsp.handlers'['callHierarchy/incomingCalls'] - handler(nil, nil, rust_analyzer_response) + handler(nil, rust_analyzer_response, {}) return vim.fn.getqflist() ]=]) local expected = { { bufnr = 2, col = 5, + end_col = 0, lnum = 4, + end_lnum = 0, module = "", nr = 0, pattern = "", @@ -2112,4 +2284,149 @@ describe('LSP', function() eq(expected, qflist) end) end) + + describe('vim.lsp.buf.rename', function() + for _, test in ipairs({ + { + it = "does not attempt to rename on nil response", + name = "prepare_rename_nil", + expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + }, + }, + { + it = "handles prepareRename placeholder response", + name = "prepare_rename_placeholder", + expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}}; + {NIL, {}, {method="start", client_id=1}}; + }, + expected_text = "placeholder", -- see fake lsp response + }, + { + it = "handles range response", + name = "prepare_rename_range", + expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}}; + {NIL, {}, {method="start", client_id=1}}; + }, + expected_text = "line", -- see test case and fake lsp response + }, + { + it = "handles error", + name = "prepare_rename_error", + expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}}; + {NIL, {}, {method="start", client_id=1}}; + }, + expected_text = "two", -- see test case + }, + }) do + it(test.it, function() + local client + test_rpc_server { + test_name = test.name; + on_init = function(_client) + client = _client + eq(true, client.resolved_capabilities().rename) + end; + on_setup = function() + exec_lua([=[ + local bufnr = vim.api.nvim_get_current_buf() + lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + vim.lsp._stubs = {} + vim.fn.input = function(prompt, text) + vim.lsp._stubs.input_prompt = prompt + vim.lsp._stubs.input_text = text + return 'renameto' -- expect this value in fake lsp + end + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'', 'this is line two'}) + vim.fn.cursor(2, 13) -- the space between "line" and "two" + ]=]) + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + on_handler = function(err, result, ctx) + -- Don't compare & assert params, they're not relevant for the testcase + -- This allows us to be lazy and avoid declaring them + ctx.params = nil + + eq(table.remove(test.expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'start' then + exec_lua("vim.lsp.buf.rename()") + end + if ctx.method == 'shutdown' then + if test.expected_text then + eq("New Name: ", exec_lua("return vim.lsp._stubs.input_prompt")) + eq(test.expected_text, exec_lua("return vim.lsp._stubs.input_text")) + end + client.stop() + end + end; + } + end) + end + end) + + describe('vim.lsp.buf.code_action', function() + it('Calls client side command if available', function() + local client + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + } + test_rpc_server { + test_name = 'code_action_with_resolve', + on_init = function(client_) + client = client_ + end, + on_setup = function() + end, + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end, + on_handler = function(err, result, ctx) + eq(table.remove(expected_handlers), {err, result, ctx}) + if ctx.method == 'start' then + exec_lua([[ + vim.lsp.commands['dummy1'] = function(cmd) + vim.lsp.commands['dummy2'] = function() + end + end + local bufnr = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + vim.fn.inputlist = function() + return 1 + end + vim.lsp.buf.code_action() + ]]) + elseif ctx.method == 'shutdown' then + eq('function', exec_lua[[return type(vim.lsp.commands['dummy2'])]]) + client.stop() + end + end + } + end) + end) + describe('vim.lsp.commands', function() + it('Accepts only string keys', function() + matches( + '.*The key for commands in `vim.lsp.commands` must be a string', + pcall_err(exec_lua, 'vim.lsp.commands[1] = function() end') + ) + end) + it('Accepts only function values', function() + matches( + '.*Command added to `vim.lsp.commands` must be a function', + pcall_err(exec_lua, 'vim.lsp.commands.dummy = 10') + ) + end) + end) end) |