From 454ae672aad172a299dcff7c33c5e61a3b631c90 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 14 Nov 2024 11:53:20 +0000 Subject: feat(lsp): deprecate non-method client functions Deprecated: - `client.request()` -> `client:request()` - `client.request_sync()` -> `client:request_sync()` - `client.notify()` -> `client:notify()` - `client.cancel_request()` -> `client:cancel_request()` - `client.stop()` -> `client:stop()` - `client.is_stopped()` `client:is_stopped()` - `client.supports_method()` -> `client:supports_method()` - `client.on_attach()` -> `client:on_attach()` Fixed docgen to link class fields to the full function doc. --- test/functional/plugin/lsp/testutil.lua | 13 +-- test/functional/plugin/lsp_spec.lua | 176 ++++++++++++++++---------------- 2 files changed, 95 insertions(+), 94 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/testutil.lua b/test/functional/plugin/lsp/testutil.lua index a36cbac568..95fc22b96b 100644 --- a/test/functional/plugin/lsp/testutil.lua +++ b/test/functional/plugin/lsp/testutil.lua @@ -182,16 +182,17 @@ function M.test_rpc_server(config) ) end local client = setmetatable({}, { - __index = function(_, name) + __index = function(t, name) -- Workaround for not being able to yield() inside __index for Lua 5.1 :( -- Otherwise I would just return the value here. - return function(...) + return function(arg1, ...) + local ismethod = arg1 == t return exec_lua(function(...) - if type(_G.TEST_RPC_CLIENT[name]) == 'function' then - return _G.TEST_RPC_CLIENT[name](...) - else - return _G.TEST_RPC_CLIENT[name] + local client = _G.TEST_RPC_CLIENT + if type(client[name]) == 'function' then + return client[name](ismethod and client or arg1, ...) end + return client[name] end, ...) end end, diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5222216faf..0f68b7eae2 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -232,7 +232,7 @@ describe('LSP', function() -- client is a dummy object which will queue up commands to be run -- once the server initializes. It can't accept lua callbacks or -- other types that may be unserializable for now. - client.stop() + client:stop() end, -- If the program timed out, then code will be nil. on_exit = function(code, signal) @@ -254,8 +254,8 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_init = function(client) - client.notify('test') - client.stop() + client:notify('test') + client:stop() end, on_exit = function(code, signal) eq(101, code, 'exit code') -- See fake-lsp-server.lua @@ -275,7 +275,7 @@ describe('LSP', function() test_rpc_server({ test_name = 'basic_init_did_change_configuration', on_init = function(client, _) - client.stop() + client:stop() end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -333,9 +333,9 @@ describe('LSP', function() test_name = 'basic_init', on_init = function(client) eq(0, client.server_capabilities().textDocumentSync.change) - client.request('shutdown') - client.notify('exit') - client.stop() + client:request('shutdown') + client:notify('exit') + client:stop() end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -377,7 +377,7 @@ describe('LSP', function() end, on_init = function(_client) client = _client - client.notify('finish') + client:notify('finish') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -395,7 +395,7 @@ describe('LSP', function() return vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) ) - client.stop() + client:stop() end end, } @@ -430,7 +430,7 @@ describe('LSP', function() return vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) ) - client.notify('finish') + client:notify('finish') end, on_handler = function(_, _, ctx) if ctx.method == 'finish' then @@ -439,7 +439,7 @@ describe('LSP', 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() + client:stop() end end, } @@ -472,7 +472,7 @@ describe('LSP', function() return keymap.callback == vim.lsp.buf.hover end) ) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -524,7 +524,7 @@ describe('LSP', function() 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() + client:stop() end end, on_exit = function(_, _) @@ -554,7 +554,7 @@ describe('LSP', function() eq('tfu', get_buf_option('tagfunc')) eq('ofu', get_buf_option('omnifunc')) eq('fex', get_buf_option('formatexpr')) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -711,10 +711,10 @@ describe('LSP', function() ctx.method, result ) - client.notify('workspace/configuration', server_result) + client:notify('workspace/configuration', server_result) end if ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -756,7 +756,7 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_capabilities', on_init = function(client) - client.stop() + client:stop() local full_kind = exec_lua(function() return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full end) @@ -798,7 +798,7 @@ describe('LSP', function() vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) end) else - client.stop() + client:stop() end end, } @@ -898,7 +898,7 @@ describe('LSP', function() end) end) else - client.stop() + client:stop() end end, }) @@ -929,20 +929,20 @@ describe('LSP', function() vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) end) else - client.stop() + client:stop() end end, } end) - it('client.supports_methods() should validate capabilities', function() + it('client:supports_methods() should validate capabilities', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, } test_rpc_server { test_name = 'capabilities_for_client_supports_method', on_init = function(client) - client.stop() + client:stop() local expected_sync_capabilities = { change = 1, openClose = true, @@ -958,11 +958,11 @@ describe('LSP', function() eq(true, client.server_capabilities().codeLensProvider.resolveProvider) -- known methods for resolved capabilities - eq(true, client.supports_method('textDocument/hover')) - eq(false, client.supports_method('textDocument/definition')) + eq(true, client:supports_method('textDocument/hover')) + eq(false, client:supports_method('textDocument/definition')) -- unknown methods are assumed to be supported. - eq(true, client.supports_method('unknown-method')) + eq(true, client:supports_method('unknown-method')) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -989,7 +989,7 @@ describe('LSP', function() end) end, on_init = function(client) - client.stop() + client:stop() exec_lua(function() vim.lsp.buf.type_definition() end) @@ -1018,7 +1018,7 @@ describe('LSP', function() end) end, on_init = function(client) - client.stop() + client:stop() exec_lua(function() vim.lsp.buf.type_definition() end) @@ -1042,7 +1042,7 @@ describe('LSP', function() test_rpc_server { test_name = 'check_forward_request_cancelled', on_init = function(_client) - _client.request('error_code_test') + _client:request('error_code_test') client = _client end, on_exit = function(code, signal) @@ -1053,7 +1053,7 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1072,7 +1072,7 @@ describe('LSP', function() test_rpc_server { test_name = 'check_forward_content_modified', on_init = function(_client) - _client.request('error_code_test') + _client:request('error_code_test') client = _client end, on_exit = function(code, signal) @@ -1084,10 +1084,10 @@ describe('LSP', function() eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') -- if ctx.method == 'error_code_test' then client.notify("finish") end if ctx.method ~= 'finish' then - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1103,13 +1103,13 @@ describe('LSP', function() test_name = 'check_pending_request_tracked', on_init = function(_client) client = _client - client.request('slow_request') + client:request('slow_request') 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') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1123,10 +1123,10 @@ describe('LSP', function() return _G.TEST_RPC_CLIENT.requests[2] end) eq(nil, request) - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1141,14 +1141,14 @@ describe('LSP', function() test_name = 'check_cancel_request_tracked', on_init = function(_client) client = _client - client.request('slow_request') - client.cancel_request(2) + client:request('slow_request') + client:cancel_request(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') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1162,7 +1162,7 @@ describe('LSP', function() end) eq(nil, request) if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1178,19 +1178,19 @@ describe('LSP', function() test_name = 'check_tracked_requests_cleared', on_init = function(_client) client = _client - client.request('slow_request') + client:request('slow_request') 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) + client:cancel_request(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') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1204,10 +1204,10 @@ describe('LSP', function() return _G.TEST_RPC_CLIENT.requests[2] end) eq(nil, request) - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1225,11 +1225,11 @@ describe('LSP', function() command('let g:requests = 0') command('autocmd LspRequest * let g:requests+=1') client = _client - client.request('slow_request') + client:request('slow_request') eq(1, eval('g:requests')) - client.cancel_request(2) + client:cancel_request(2) eq(2, eval('g:requests')) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1240,10 +1240,10 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'slow_request' then - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1277,7 +1277,7 @@ describe('LSP', function() end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - client.notify('finish') + client:notify('finish') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1286,7 +1286,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1333,11 +1333,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1378,11 +1378,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1428,11 +1428,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1479,11 +1479,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1529,7 +1529,7 @@ describe('LSP', function() end, on_init = function(_client) client = _client - eq(true, client.supports_method('textDocument/inlayHint')) + eq(true, client:supports_method('textDocument/inlayHint')) exec_lua(function() assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) end) @@ -1545,11 +1545,11 @@ describe('LSP', function() end) end if ctx.method == 'textDocument/inlayHint' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1598,11 +1598,11 @@ describe('LSP', function() '123boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1652,11 +1652,11 @@ describe('LSP', function() '123boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1699,11 +1699,11 @@ describe('LSP', function() on_handler = function(err, result, ctx) if ctx.method == 'start' then n.command('normal! 1Go') - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1752,11 +1752,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1806,11 +1806,11 @@ describe('LSP', function() }) vim.api.nvim_command(_G.BUFFER .. 'bwipeout') end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1830,7 +1830,7 @@ describe('LSP', function() on_setup = function() end, on_init = function(_client) client = _client - client.stop(true) + client:stop(true) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1882,7 +1882,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -2348,7 +2348,7 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_init = function(client, _) - client.stop() + client:stop() end, -- If the program timed out, then code will be nil. on_exit = function(code, signal) @@ -4284,7 +4284,7 @@ describe('LSP', function() end) ) end - client.stop() + client:stop() end end, } @@ -4331,7 +4331,7 @@ describe('LSP', function() return type(vim.lsp.commands['dummy2']) end) ) - client.stop() + client:stop() end end, } @@ -4372,7 +4372,7 @@ describe('LSP', function() vim.lsp.buf.code_action() end) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, }) @@ -4447,7 +4447,7 @@ describe('LSP', function() return type(vim.lsp.commands['executed_type_annotate']) end) ) - client.stop() + client:stop() end end, } @@ -4564,7 +4564,7 @@ describe('LSP', function() end) eq({ command = 'Dummy', title = 'Lens1' }, cmd) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4653,7 +4653,7 @@ describe('LSP', function() end) eq({ command = 'Dummy', title = 'Lens2' }, response) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4762,7 +4762,7 @@ describe('LSP', function() return notify_msg end) eq('[LSP] Format request failed, no matching language servers.', notify_msg) - client.stop() + client:stop() end, } end) @@ -4795,7 +4795,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4836,7 +4836,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4883,7 +4883,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4930,7 +4930,7 @@ describe('LSP', function() end) eq({ handler_called = true }, result) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -5477,7 +5477,7 @@ describe('LSP', function() result[#result + 1] = { method = method, fname = fname, - supported = client.supports_method(method, { bufnr = bufnr }), + supported = client:supports_method(method, { bufnr = bufnr }), } end -- cgit From 07db909eb5ae2a559771068be64439eba394cd61 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Wed, 20 Nov 2024 23:50:30 +0100 Subject: docs: misc (#31138) Co-authored-by: zeertzjq --- test/functional/plugin/lsp_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 0f68b7eae2..f14e24bb19 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3499,7 +3499,7 @@ describe('LSP', function() } return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'zig', { '(' }) end) - -- Note that although the higlight positions below are 0-indexed, the 2nd parameter + -- Note that although the highlight positions below are 0-indexed, the 2nd parameter -- corresponds to the 3rd line because the first line is the ``` from the -- Markdown block. local expected = { 3, 4, 3, 11 } -- cgit From 9a681ad09e2add96d47bf3f39cca8029f3bf09df Mon Sep 17 00:00:00 2001 From: andrew snelling <72226000+snelling-a@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:51:30 +0100 Subject: fix(lsp): hover keymap (#31208) * fix: use function call in keymap * fix: test --- test/functional/plugin/lsp_spec.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index f14e24bb19..332a1a48bb 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -466,10 +466,17 @@ describe('LSP', function() true, exec_lua(function() local keymap --- @type table + local called = false + local origin = vim.lsp.buf.hover + vim.lsp.buf.hover = function() + called = true + end vim._with({ buf = _G.BUFFER }, function() keymap = vim.fn.maparg('K', 'n', false, true) end) - return keymap.callback == vim.lsp.buf.hover + keymap.callback() + vim.lsp.buf.hover = origin + return called end) ) client:stop() @@ -480,13 +487,13 @@ describe('LSP', function() eq('', get_buf_option('omnifunc')) eq('', get_buf_option('formatexpr')) eq( - '', + true, exec_lua(function() local keymap --- @type string vim._with({ buf = _G.BUFFER }, function() keymap = vim.fn.maparg('K', 'n', false, false) end) - return keymap + return keymap:match('') ~= nil end) ) end, -- cgit From 2a1f604c77a161f076f7d520d66fc6f051b625e7 Mon Sep 17 00:00:00 2001 From: glepnir Date: Sat, 23 Nov 2024 19:11:30 +0800 Subject: fix(lsp): delete bufvar inside WinClosed event Problem: floaing preview window can be closed by some ex commands like `only` `fclose` which will not clean the bufvar Solution: use WinClosed event with floating_winnr for clean bufnr, and add test cases for vim.lsp.util.open_floating_preview --- test/functional/plugin/lsp/utils_spec.lua | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index 813b8de812..ce6e6b2535 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -5,6 +5,8 @@ local Screen = require('test.functional.ui.screen') local feed = n.feed local eq = t.eq local exec_lua = n.exec_lua +local command, api = n.command, n.api +local pcall_err = t.pcall_err describe('vim.lsp.util', function() before_each(n.clear) @@ -265,6 +267,38 @@ describe('vim.lsp.util', function() eq(56, opts.height) end) + + describe('vim.lsp.util.open_floating_preview', function() + local var_name = 'lsp_floating_preview' + local curbuf = api.nvim_get_current_buf() + + it('clean bufvar after fclose', function() + exec_lua(function() + vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) + end) + eq(true, api.nvim_win_is_valid(api.nvim_buf_get_var(curbuf, var_name))) + command('fclose') + eq( + 'Key not found: lsp_floating_preview', + pcall_err(api.nvim_buf_get_var, curbuf, var_name) + ) + end) + + it('clean bufvar after CursorMoved', function() + local result = exec_lua(function() + vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) + local winnr = vim.b[vim.api.nvim_get_current_buf()].lsp_floating_preview + local result = vim.api.nvim_win_is_valid(winnr) + vim.api.nvim_feedkeys(vim.keycode('G'), 'txn', false) + return result + end) + eq(true, result) + eq( + 'Key not found: lsp_floating_preview', + pcall_err(api.nvim_buf_get_var, curbuf, var_name) + ) + end) + end) end) end) end) -- cgit From 165b099fa38c5f4a9855cda3d13575bf63767647 Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Tue, 26 Nov 2024 00:06:05 +0800 Subject: refactor(lsp): rename `offset_encoding` to `position_encoding` #31286 Problem: LSP spec uses the term "position encoding" where we say "offset encoding". Solution: - Rename it everywhere except `vim.lsp.Client.offset_encoding` (which would be breaking). - Mention "position encoding" in the documentation for `vim.lsp.Client.offset_encoding`. --- test/functional/plugin/lsp/incremental_sync_spec.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index f60e159d64..0bca1fa4ca 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -23,7 +23,7 @@ before_each(function() -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})] --- @diagnostic disable-next-line:duplicate-set-field - function _G.test_register(bufnr, id, offset_encoding, line_ending) + function _G.test_register(bufnr, id, position_encoding, line_ending) local prev_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true) local function callback(_, bufnr0, _changedtick, firstline, lastline, new_lastline) @@ -38,7 +38,7 @@ before_each(function() firstline, lastline, new_lastline, - offset_encoding, + position_encoding, line_ending ) @@ -63,15 +63,15 @@ local function test_edit( prev_buffer, edit_operations, expected_text_changes, - offset_encoding, + position_encoding, line_ending ) - offset_encoding = offset_encoding or 'utf-16' + position_encoding = position_encoding or 'utf-16' line_ending = line_ending or '\n' api.nvim_buf_set_lines(0, 0, -1, true, prev_buffer) exec_lua(function() - return _G.test_register(0, 'test1', offset_encoding, line_ending) + return _G.test_register(0, 'test1', position_encoding, line_ending) end) for _, edit in ipairs(edit_operations) do -- cgit From 29c72cdf4a4913c152f037865cb28c78a8930340 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Mon, 25 Nov 2024 11:48:11 -0600 Subject: fix(lsp): retrigger diagnostics request on server cancellation (#31345) Co-authored-by: Jesse --- test/functional/plugin/lsp/diagnostic_spec.lua | 57 ++++++++++++++++++++++++++ test/functional/plugin/lsp_spec.lua | 34 ++++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 5afbe22793..ca9196562c 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -209,10 +209,16 @@ describe('vim.lsp.diagnostic', function() before_each(function() exec_lua(create_server_definition) exec_lua(function() + _G.requests = 0 _G.server = _G._create_server({ capabilities = { diagnosticProvider = {}, }, + handlers = { + [vim.lsp.protocol.Methods.textDocument_diagnostic] = function() + _G.requests = _G.requests + 1 + end, + }, }) function _G.get_extmarks(bufnr, client_id0) @@ -373,5 +379,56 @@ describe('vim.lsp.diagnostic', function() end) ) end) + + it('handles server cancellation', function() + eq( + 1, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + -- Empty data defaults to retriggering request + data = {}, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + + eq( + 2, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = true }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + + eq( + 2, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = false }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + end) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 332a1a48bb..e30d1ba411 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1066,6 +1066,39 @@ describe('LSP', function() } end) + it('should forward ServerCancelled to callback', function() + local expected_handlers = { + { NIL, {}, { method = 'finish', client_id = 1 } }, + { + { code = -32802 }, + NIL, + { method = 'error_code_test', bufnr = 1, client_id = 1, version = 0 }, + }, + } + local client --- @type vim.lsp.Client + test_rpc_server { + test_name = 'check_forward_server_cancelled', + on_init = function(_client) + _client:request('error_code_test') + client = _client + end, + on_exit = function(code, signal) + eq(0, code, 'exit code') + eq(0, signal, 'exit signal') + eq(0, #expected_handlers, 'did not call expected handler') + end, + on_handler = function(err, _, ctx) + eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') + if ctx.method ~= 'finish' then + client:notify('finish') + end + if ctx.method == 'finish' then + client:stop() + end + end, + } + end) + it('should forward ContentModified to callback', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, @@ -1089,7 +1122,6 @@ describe('LSP', function() end, on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') - -- if ctx.method == 'error_code_test' then client.notify("finish") end if ctx.method ~= 'finish' then client:notify('finish') end -- cgit From a1e313ded6e4c46c58012639e5c0c6d0b009d52a Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Fri, 29 Nov 2024 20:40:32 +0800 Subject: feat(lsp): support `textDocument/foldingRange` (#31311) * refactor(shared): extract `vim._list_insert` and `vim._list_remove` * feat(lsp): add `vim.lsp.foldexpr()` * docs(lsp): add a todo for state management * feat(lsp): add `vim.lsp.folding_range.foldclose()` * feat(lsp): schedule `foldclose()` if the buffer is not up-to-date * feat(lsp): add `vim.lsp.foldtext()` * feat(lsp): support multiple folding range providers * refactor(lsp): expose all folding related functions under `vim.lsp.*` * perf(lsp): add `lsp.MultiHandler` for do `foldupdate()` only once --- test/functional/plugin/lsp/folding_range_spec.lua | 647 ++++++++++++++++++++++ 1 file changed, 647 insertions(+) create mode 100644 test/functional/plugin/lsp/folding_range_spec.lua (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/folding_range_spec.lua b/test/functional/plugin/lsp/folding_range_spec.lua new file mode 100644 index 0000000000..7e68a598d2 --- /dev/null +++ b/test/functional/plugin/lsp/folding_range_spec.lua @@ -0,0 +1,647 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') +local t_lsp = require('test.functional.plugin.lsp.testutil') + +local eq = t.eq +local tempname = t.tmpname + +local clear_notrace = t_lsp.clear_notrace +local create_server_definition = t_lsp.create_server_definition + +local api = n.api +local exec_lua = n.exec_lua +local insert = n.insert +local command = n.command +local feed = n.feed + +describe('vim.lsp.folding_range', function() + local text = [[// foldLevel() {{{2 +/// @return fold level at line number "lnum" in the current window. +static int foldLevel(linenr_T lnum) +{ + // While updating the folds lines between invalid_top and invalid_bot have + // an undefined fold level. Otherwise update the folds first. + if (invalid_top == 0) { + checkupdate(curwin); + } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { + return prev_lnum_lvl; + } else if (lnum >= invalid_top && lnum <= invalid_bot) { + return -1; + } + + // Return quickly when there is no folding at all in this window. + if (!hasAnyFolding(curwin)) { + return 0; + } + + return foldLevelWin(curwin, lnum); +}]] + + local result = { + { + endLine = 19, + kind = 'region', + startCharacter = 1, + startLine = 3, + }, + { + endCharacter = 2, + endLine = 7, + kind = 'region', + startCharacter = 25, + startLine = 6, + }, + { + endCharacter = 2, + endLine = 9, + kind = 'region', + startCharacter = 55, + startLine = 8, + }, + { + endCharacter = 2, + endLine = 11, + kind = 'region', + startCharacter = 58, + startLine = 10, + }, + { + endCharacter = 2, + endLine = 16, + kind = 'region', + startCharacter = 31, + startLine = 15, + }, + { + endCharacter = 68, + endLine = 1, + kind = 'comment', + startCharacter = 2, + startLine = 0, + }, + { + endCharacter = 64, + endLine = 5, + kind = 'comment', + startCharacter = 4, + startLine = 4, + }, + } + + local bufnr ---@type integer + local client_id ---@type integer + + clear_notrace() + before_each(function() + clear_notrace() + + exec_lua(create_server_definition) + bufnr = n.api.nvim_get_current_buf() + client_id = exec_lua(function() + _G.server = _G._create_server({ + capabilities = { + foldingRangeProvider = true, + }, + handlers = { + ['textDocument/foldingRange'] = function(_, _, callback) + callback(nil, result) + end, + }, + }) + + vim.api.nvim_win_set_buf(0, bufnr) + + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) + command('set foldmethod=expr foldcolumn=1 foldlevel=999') + insert(text) + end) + after_each(function() + api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) + end) + + describe('setup()', function() + ---@type integer + local bufnr_set_expr + ---@type integer + local bufnr_never_set_expr + + local function buf_autocmd_num(bufnr_to_check) + return exec_lua(function() + return #vim.api.nvim_get_autocmds({ buffer = bufnr_to_check, event = 'LspNotify' }) + end) + end + + before_each(function() + command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]]) + exec_lua(function() + bufnr_set_expr = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(bufnr_set_expr) + end) + insert(text) + command('write ' .. tempname(false)) + command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]]) + exec_lua(function() + bufnr_never_set_expr = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(bufnr_never_set_expr) + end) + insert(text) + api.nvim_win_set_buf(0, bufnr_set_expr) + end) + + it('only create event hooks where foldexpr has been set', function() + eq(1, buf_autocmd_num(bufnr)) + eq(1, buf_autocmd_num(bufnr_set_expr)) + eq(0, buf_autocmd_num(bufnr_never_set_expr)) + end) + + it('does not create duplicate event hooks after reloaded', function() + command('edit') + eq(1, buf_autocmd_num(bufnr_set_expr)) + end) + + it('cleans up event hooks when buffer is unloaded', function() + command('bdelete') + eq(0, buf_autocmd_num(bufnr_set_expr)) + end) + end) + + describe('expr()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 45) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { bold = true, foreground = Screen.colors.Blue1 }, + [3] = { bold = true, reverse = true }, + [4] = { reverse = true }, + }) + command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) + command([[split]]) + end) + + it('can compute fold levels', function() + ---@type table + local foldlevels = {} + for i = 1, 21 do + foldlevels[i] = exec_lua('return vim.lsp.foldexpr(' .. i .. ')') + end + eq({ + [1] = '>1', + [2] = '<1', + [3] = '0', + [4] = '>1', + [5] = '>2', + [6] = '<2', + [7] = '>2', + [8] = '<2', + [9] = '>2', + [10] = '<2', + [11] = '>2', + [12] = '<2', + [13] = '1', + [14] = '1', + [15] = '1', + [16] = '>2', + [17] = '<2', + [18] = '1', + [19] = '1', + [20] = '<1', + [21] = '0', + }, foldlevels) + end) + + it('updates folds in all windows', function() + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + + it('persists wherever foldexpr is set', function() + command([[setlocal foldexpr=]]) + feed('zx') + screen:expect({ + grid = [[ +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| + | + ]], + }) + end) + + it('synchronizes changed rows with their previous foldlevels', function() + command('1,2d') + screen:expect({ + grid = [[ +{1: }^static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{2:~ }|*2 +{3:[No Name] [+] }| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{2:~ }|*2 +{4:[No Name] [+] }| + | +]], + }) + end) + + it('clears folds when sole client detaches', function() + exec_lua(function() + vim.lsp.buf_detach_client(bufnr, client_id) + end) + screen:expect({ + grid = [[ +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + + it('remains valid after the client re-attaches.', function() + exec_lua(function() + vim.lsp.buf_detach_client(bufnr, client_id) + vim.lsp.buf_attach_client(bufnr, client_id) + end) + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + end) + + describe('foldtext()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 23) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, + [3] = { bold = true, foreground = Screen.colors.Blue1 }, + [4] = { bold = true, reverse = true }, + [5] = { reverse = true }, + }) + command( + [[set foldexpr=v:lua.vim.lsp.foldexpr() foldtext=v:lua.vim.lsp.foldtext() foldlevel=1]] + ) + end) + + it('shows the first folded line if `collapsedText` does not exist', function() + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2: // While updating the folds lines between invalid_top and invalid_bot have···}| +{1:+}{2: if (invalid_top == 0) {······················································}| +{1:+}{2: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {························}| +{1:+}{2: } else if (lnum >= invalid_top && lnum <= invalid_bot) {·····················}| +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:+}{2: if (!hasAnyFolding(curwin)) {················································}| +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*6 + | + ]], + }) + end) + end) + + describe('foldclose()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 23) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, + [3] = { bold = true, foreground = Screen.colors.Blue1 }, + [4] = { bold = true, reverse = true }, + [5] = { reverse = true }, + }) + command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) + end) + + it('closes all folds of one kind immediately', function() + exec_lua(function() + vim.lsp.foldclose('comment') + end) + screen:expect({ + grid = [[ +{1:+}{2:+-- 2 lines: foldLevel()······················································}| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}| +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*3 + | + ]], + }) + end) + + it('closes the smallest fold first', function() + exec_lua(function() + vim.lsp.foldclose('region') + end) + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:+}{2:+-- 17 lines: {································································}| +{1: }^} | +{3:~ }|*17 + | + ]], + }) + command('4foldopen') + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:+}{2:+--- 2 lines: if (invalid_top == 0) {·········································}| +{1:+}{2:+--- 2 lines: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {···········}| +{1:+}{2:+--- 2 lines: } else if (lnum >= invalid_top && lnum <= invalid_bot) {········}| +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:+}{2:+--- 2 lines: if (!hasAnyFolding(curwin)) {···································}| +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*5 + | + ]], + }) + end) + + it('is defered when the buffer is not up-to-date', function() + exec_lua(function() + vim.lsp.foldclose('comment') + vim.lsp.util.buf_versions[bufnr] = 0 + end) + screen:expect({ + grid = [[ +{1:+}{2:+-- 2 lines: foldLevel()······················································}| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}| +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*3 + | + ]], + }) + end) + end) +end) -- cgit From e56437cd48f7df87ccdfb79812ee56241c0da0cb Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Wed, 4 Dec 2024 05:14:47 -0800 Subject: feat(lsp): deprecate vim.lsp.start_client #31341 Problem: LSP module has multiple "start" interfaces. Solution: - Enhance vim.lsp.start - Deprecate vim.lsp.start_client --- test/functional/plugin/lsp/diagnostic_spec.lua | 4 ++-- test/functional/plugin/lsp/semantic_tokens_spec.lua | 2 +- test/functional/plugin/lsp_spec.lua | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index ca9196562c..4ecb056d01 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -89,7 +89,7 @@ describe('vim.lsp.diagnostic', function() return extmarks end - client_id = assert(vim.lsp.start_client { + client_id = assert(vim.lsp.start({ cmd_env = { NVIM_LUA_NOTRACK = '1', }, @@ -101,7 +101,7 @@ describe('vim.lsp.diagnostic', function() '--headless', }, offset_encoding = 'utf-16', - }) + }, { attach = false })) end) fake_uri = 'file:///fake/uri' diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 280bd27207..9912bf2063 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -456,7 +456,7 @@ describe('semantic token highlighting', function() vim.notify = function(...) table.insert(_G.notifications, 1, { ... }) end - return vim.lsp.start_client({ name = 'dummy', cmd = _G.server.cmd }) + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }, { attach = false }) end) eq(false, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id)) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index e30d1ba411..e735e20ff5 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -95,7 +95,7 @@ describe('LSP', function() exec_lua(function() _G.lsp = require('vim.lsp') function _G.test__start_client() - return vim.lsp.start_client { + return vim.lsp.start({ cmd_env = { NVIM_LOG_FILE = fake_lsp_logfile, NVIM_APPNAME = 'nvim_lsp_test', @@ -112,7 +112,7 @@ describe('LSP', function() name = 'test_folder', }, }, - } + }, { attach = false }) end _G.TEST_CLIENT1 = _G.test__start_client() end) -- cgit From 5c245ec3e95570e515c1665a2ec694828706ac52 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 6 Dec 2024 17:09:49 +0000 Subject: fix: remove vim.lsp._with_extend Not used anywhere. --- test/functional/plugin/lsp/handler_spec.lua | 42 ----------------------------- 1 file changed, 42 deletions(-) delete mode 100644 test/functional/plugin/lsp/handler_spec.lua (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/handler_spec.lua b/test/functional/plugin/lsp/handler_spec.lua deleted file mode 100644 index 4b05b676a8..0000000000 --- a/test/functional/plugin/lsp/handler_spec.lua +++ /dev/null @@ -1,42 +0,0 @@ -local t = require('test.testutil') -local n = require('test.functional.testnvim')() - -local eq = t.eq -local exec_lua = n.exec_lua -local pcall_err = t.pcall_err -local matches = t.matches - -describe('lsp-handlers', function() - describe('vim.lsp._with_extend', function() - it('should return a table with the default keys', function() - eq( - { hello = 'world' }, - exec_lua(function() - return vim.lsp._with_extend('test', { hello = 'world' }) - end) - ) - end) - - it('should override with config keys', function() - eq( - { hello = 'universe', other = true }, - exec_lua(function() - return vim.lsp._with_extend( - 'test', - { other = true, hello = 'world' }, - { hello = 'universe' } - ) - end) - ) - end) - - it('should not allow invalid keys', function() - matches( - '.*Invalid option for `test`.*', - pcall_err(exec_lua, function() - return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true }) - end) - ) - end) - end) -end) -- cgit From 3f1d09bc94d02266d6fa588a2ccd1be1ca084cf7 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 1 Nov 2024 16:31:51 +0000 Subject: feat(lsp): add vim.lsp.config and vim.lsp.enable Design goals/requirements: - Default configuration of a server can be distributed across multiple sources. - And via RTP discovery. - Default configuration can be specified for all servers. - Configuration _can_ be project specific. Solution: - Two new API's: - `vim.lsp.config(name, cfg)`: - Used to define default configurations for servers of name. - Can be used like a table or called as a function. - Use `vim.lsp.confg('*', cfg)` to specify default config for all servers. - `vim.lsp.enable(name)` - Used to enable servers of name. Uses configuration defined via `vim.lsp.config()`. --- test/functional/plugin/lsp_spec.lua | 93 +++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 9 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index e735e20ff5..79952cb933 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -6098,15 +6098,6 @@ describe('LSP', function() end eq(is_os('mac') or is_os('win'), check_registered(nil)) -- start{_client}() defaults to make_client_capabilities(). - eq(false, check_registered(vim.empty_dict())) - eq( - false, - check_registered({ - workspace = { - ignoreMe = true, - }, - }) - ) eq( false, check_registered({ @@ -6129,4 +6120,88 @@ describe('LSP', function() ) end) end) + + describe('vim.lsp.config() and vim.lsp.enable()', function() + it('can merge settings from "*"', function() + eq( + { + name = 'foo', + cmd = { 'foo' }, + root_markers = { '.git' }, + }, + exec_lua(function() + vim.lsp.config('*', { root_markers = { '.git' } }) + vim.lsp.config('foo', { cmd = { 'foo' } }) + + return vim.lsp._resolve_config('foo') + end) + ) + end) + + it('sets up an autocmd', function() + eq( + 1, + exec_lua(function() + vim.lsp.config('foo', { + cmd = { 'foo' }, + root_markers = { '.foorc' }, + }) + vim.lsp.enable('foo') + return #vim.api.nvim_get_autocmds({ + group = 'nvim.lsp.enable', + event = 'FileType', + }) + end) + ) + end) + + it('attaches to buffers', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + local tmp2 = t.tmpname(true) + + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_markers = { '.foorc' }, + }) + + vim.lsp.config('bar', { + cmd = server.cmd, + filetypes = { 'bar' }, + root_markers = { '.foorc' }, + }) + + vim.lsp.enable('foo') + vim.lsp.enable('bar') + + vim.cmd.edit(tmp1) + vim.bo.filetype = 'foo' + _G.foo_buf = vim.api.nvim_get_current_buf() + + vim.cmd.edit(tmp2) + vim.bo.filetype = 'bar' + _G.bar_buf = vim.api.nvim_get_current_buf() + end) + + eq( + { 1, 'foo', 1, 'bar' }, + exec_lua(function() + local foos = vim.lsp.get_clients({ bufnr = assert(_G.foo_buf) }) + local bars = vim.lsp.get_clients({ bufnr = assert(_G.bar_buf) }) + return { #foos, foos[1].name, #bars, bars[1].name } + end) + ) + end) + end) end) -- cgit From 9c20342297391c4076809964e799f2c7705b819b Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 13 Dec 2024 10:51:33 +0000 Subject: fix(lsp): reuse client if configs match and no root dir Problem: An LSP configuration that creates client with no root_dir or workspace_folders can result in vim.lsp.enable attaching to it multiple times. Solution: When checking existing clients, reuse a client if it wasn't initially configured have any workspace_folders. This more closely matches the behaviour we had prior to d9235ef --- test/functional/plugin/lsp/completion_spec.lua | 17 +++++++------- test/functional/plugin/lsp_spec.lua | 32 ++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 10 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 39b6ddc105..15ac9da657 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -731,9 +731,10 @@ describe('vim.lsp.completion: item conversion', function() ) end) +--- @param name string --- @param completion_result lsp.CompletionList --- @return integer -local function create_server(completion_result) +local function create_server(name, completion_result) return exec_lua(function() local server = _G._create_server({ capabilities = { @@ -751,7 +752,7 @@ local function create_server(completion_result) local bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_win_set_buf(0, bufnr) return vim.lsp.start({ - name = 'dummy', + name = name, cmd = server.cmd, on_attach = function(client, bufnr0) vim.lsp.completion.enable(true, client.id, bufnr0, { @@ -800,7 +801,7 @@ describe('vim.lsp.completion: protocol', function() end it('fetches completions and shows them using complete on trigger', function() - create_server({ + create_server('dummy', { isIncomplete = false, items = { { @@ -892,7 +893,7 @@ describe('vim.lsp.completion: protocol', function() end) it('merges results from multiple clients', function() - create_server({ + create_server('dummy1', { isIncomplete = false, items = { { @@ -900,7 +901,7 @@ describe('vim.lsp.completion: protocol', function() }, }, }) - create_server({ + create_server('dummy2', { isIncomplete = false, items = { { @@ -933,7 +934,7 @@ describe('vim.lsp.completion: protocol', function() }, }, } - local client_id = create_server(completion_list) + local client_id = create_server('dummy', completion_list) exec_lua(function() _G.called = false @@ -970,7 +971,7 @@ describe('vim.lsp.completion: protocol', function() end) it('enable(…,{convert=fn}) custom word/abbr format', function() - create_server({ + create_server('dummy', { isIncomplete = false, items = { { @@ -1012,7 +1013,7 @@ describe('vim.lsp.completion: integration', function() exec_lua(function() vim.o.completeopt = 'menuone,noselect' end) - create_server(completion_list) + create_server('dummy', completion_list) feed('i world0ih') retry(nil, nil, function() eq( diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 79952cb933..d2ef166983 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -5184,8 +5184,8 @@ describe('LSP', function() local win = vim.api.nvim_get_current_win() vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'local x = 10', '', 'print(x)' }) vim.api.nvim_win_set_cursor(win, { 3, 6 }) - local client_id1 = assert(vim.lsp.start({ name = 'dummy', cmd = server1.cmd })) - local client_id2 = assert(vim.lsp.start({ name = 'dummy', cmd = server2.cmd })) + local client_id1 = assert(vim.lsp.start({ name = 'dummy1', cmd = server1.cmd })) + local client_id2 = assert(vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })) local response vim.lsp.buf.definition({ on_list = function(r) @@ -6203,5 +6203,33 @@ describe('LSP', function() end) ) end) + + it('does not attach to buffers more than once if no root_dir', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + + eq( + 1, + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { cmd = server.cmd, filetypes = { 'foo' } }) + vim.lsp.enable('foo') + + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + vim.bo.filetype = 'foo' + + return #vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() }) + end) + ) + end) end) end) -- cgit From 47f2769b462eb6bd1c10efec3c32ed55134ce628 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 13 Dec 2024 14:22:59 +0000 Subject: fix(Man): completion on Mac Problem: `man -w` does not work on recent versions of MacOs. Solution: Make it so an empty result is interpreted as an error unless silent=true --- test/functional/plugin/man_spec.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 8906e60dce..e3f3de6252 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -263,4 +263,14 @@ describe(':Man', function() { '1', 'other_man' }, }, get_search_history('other_man(1)')) end) + + it('can complete', function() + t.skip(t.is_os('mac') and t.is_arch('x86_64'), 'not supported on intel mac') + eq( + true, + exec_lua(function() + return #require('man').man_complete('f', 'Man g') > 0 + end) + ) + end) end) -- cgit From 888a803755c58db56b5b20fcf6b812de877056c9 Mon Sep 17 00:00:00 2001 From: phanium <91544758+phanen@users.noreply.github.com> Date: Wed, 18 Dec 2024 22:37:12 +0800 Subject: fix(lsp): vim.lsp.start fails if existing client has no workspace_folders #31608 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: regression since https://github.com/neovim/neovim/pull/31340 `nvim -l repro.lua`: ```lua vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls' } vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls', root_dir = 'foo' } -- swapped case will be ok: -- vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls', root_dir = 'foo' } -- vim.lsp.start { cmd = { 'lua-language-server' }, name = 'lua_ls' } ``` Failure: ``` E5113: Error while calling lua chunk: /…/lua/vim/lsp.lua:214: bad argument #1 to 'ipairs' (table expected, got nil) stack traceback: [C]: in function 'ipairs' /…/lua/vim/lsp.lua:214: in function 'reuse_client' /…/lua/vim/lsp.lua:629: in function 'start' repro.lua:34: in main chunk ``` --- test/functional/plugin/lsp_spec.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index d2ef166983..1f246b0914 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1854,6 +1854,20 @@ describe('LSP', function() end, } end) + + it('vim.lsp.start when existing client has no workspace_folders', function() + exec_lua(create_server_definition) + eq( + { 2, 'foo', 'foo' }, + exec_lua(function() + local server = _G._create_server() + vim.lsp.start { cmd = server.cmd, name = 'foo' } + vim.lsp.start { cmd = server.cmd, name = 'foo', root_dir = 'bar' } + local foos = vim.lsp.get_clients() + return { #foos, foos[1].name, foos[2].name } + end) + ) + end) end) describe('parsing tests', function() -- cgit From 7121983c45d92349a6532f32dcde9f425e30781e Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 16 Dec 2024 16:16:57 +0000 Subject: refactor(man.lua): various changes - Replace all uses of vim.regex with simpler Lua patterns. - Replace all uses of vim.fn.substitute with string.gsub. - Rework error handling so expected errors are passed back via a return. - These get routed up an passed to `vim.notify()` - Any other errors will cause a stack trace. - Reworked the module initialization of `localfile_arg` - Updated all type annotations. - Refactored CLI completion by introduction a parse_cmdline() function. - Simplified `show_toc()` - Refactor highlighting - Inline some functions - Fix completion on MacOS 13 and earlier. - Prefer `manpath -q` over `man -w` - Make completion more efficient by avoiding vim.fn.sort and vim.fn.uniq - Reimplement using a single loop --- test/functional/plugin/man_spec.lua | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index e3f3de6252..c1dbc6dac3 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -21,13 +21,12 @@ local function get_search_history(name) local man = require('man') local res = {} --- @diagnostic disable-next-line:duplicate-set-field - man.find_path = function(sect, name0) + man._find_path = function(name0, sect) table.insert(res, { sect, name0 }) return nil end - local ok, rv = pcall(man.open_page, -1, { tab = 0 }, args) - assert(not ok) - assert(rv and rv:match('no manual entry')) + local err = man.open_page(-1, { tab = 0 }, args) + assert(err and err:match('no manual entry')) return res end) end @@ -225,7 +224,7 @@ describe(':Man', function() matches('^/.+', actual_file) local args = { nvim_prog, '--headless', '+:Man ' .. actual_file, '+q' } matches( - ('Error detected while processing command line:\r\n' .. 'man.lua: "no manual entry for %s"'):format( + ('Error detected while processing command line:\r\n' .. 'man.lua: no manual entry for %s'):format( pesc(actual_file) ), fn.system(args, { '' }) @@ -235,8 +234,8 @@ describe(':Man', function() it('tries variants with spaces, underscores #22503', function() eq({ - { '', 'NAME WITH SPACES' }, - { '', 'NAME_WITH_SPACES' }, + { vim.NIL, 'NAME WITH SPACES' }, + { vim.NIL, 'NAME_WITH_SPACES' }, }, get_search_history('NAME WITH SPACES')) eq({ { '3', 'some other man' }, @@ -255,8 +254,8 @@ describe(':Man', function() { 'n', 'some_other_man' }, }, get_search_history('n some other man')) eq({ - { '', '123some other man' }, - { '', '123some_other_man' }, + { vim.NIL, '123some other man' }, + { vim.NIL, '123some_other_man' }, }, get_search_history('123some other man')) eq({ { '1', 'other_man' }, @@ -265,11 +264,10 @@ describe(':Man', function() end) it('can complete', function() - t.skip(t.is_os('mac') and t.is_arch('x86_64'), 'not supported on intel mac') eq( true, exec_lua(function() - return #require('man').man_complete('f', 'Man g') > 0 + return #require('man').man_complete('f', 'Man f') > 0 end) ) end) -- cgit From 35247b00a44e838ed7d657a9b94964dc0664d28d Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Fri, 27 Dec 2024 10:09:22 -0600 Subject: feat(lsp): support function for client root_dir (#31630) If root_dir is a function it is evaluated when the client is created to determine the root directory. This enables dynamically determining the root directory based on e.g. project or directory structure (example: finding a parent Cargo.toml file that contains "[workspace]" in a Rust project). --- test/functional/plugin/lsp_spec.lua | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 1f246b0914..f396c837f9 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -6245,5 +6245,38 @@ describe('LSP', function() end) ) end) + + it('supports a function for root_dir', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + + eq( + 'some_dir', + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_dir = function(cb) + cb('some_dir') + end, + }) + vim.lsp.enable('foo') + + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + + return vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })[1].root_dir + end) + ) + end) end) end) -- cgit From e00cd1ab4060915d86b8536b082e663818268b69 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Sun, 29 Dec 2024 13:44:42 +0100 Subject: feat(lsp): return resolved config for vim.lsp.config[name] Allows to retrieve the configuration as it will be used by `lsp.enable` - including the parts merged from `*` and rtp. This is useful for explicit startup control (`vim.lsp.start(vim.lsp.config[name])`) Closes https://github.com/neovim/neovim/issues/31640 --- test/functional/plugin/lsp_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index f396c837f9..9cefe96e79 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -6147,7 +6147,7 @@ describe('LSP', function() vim.lsp.config('*', { root_markers = { '.git' } }) vim.lsp.config('foo', { cmd = { 'foo' } }) - return vim.lsp._resolve_config('foo') + return vim.lsp.config['foo'] end) ) end) -- cgit From 86770108e2c6e08c2b8b95f1611923ba99b854dd Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Mon, 6 Jan 2025 15:05:50 +0100 Subject: fix(lsp): open_floating_preview() zindex relative to current window #31886 Problem: open_floating_preview() may be hidden behind current window if that is floating and has a higher zindex. Solution: Open floating preview with zindex higher than current window. --- test/functional/plugin/lsp/utils_spec.lua | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index ce6e6b2535..1e3e759e0b 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -301,4 +301,32 @@ describe('vim.lsp.util', function() end) end) end) + + it('open_floating_preview zindex greater than current window', function() + local screen = Screen.new() + exec_lua(function() + vim.api.nvim_open_win(0, true, { + relative = 'editor', + border = 'single', + height = 11, + width = 51, + row = 2, + col = 2, + }) + vim.keymap.set('n', 'K', function() + vim.lsp.util.open_floating_preview({ 'foo' }, '', { border = 'single' }) + end, {}) + end) + feed('K') + screen:expect([[ + ┌───────────────────────────────────────────────────┐| + │{4:^ }│| + │┌───┐{11: }│| + ││{4:foo}│{11: }│| + │└───┘{11: }│| + │{11:~ }│|*7 + └───────────────────────────────────────────────────┘| + | + ]]) + end) end) -- cgit From b12b91c2743954dbe8599caa60e58e5d74aa4e76 Mon Sep 17 00:00:00 2001 From: glepnir Date: Wed, 8 Jan 2025 00:09:01 +0800 Subject: feat(health): show :checkhealth in floating window #31086 Problem: health can not shown in a floating window Solution: add g:health variable --- test/functional/plugin/health_spec.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 753da64522..406b5c3c16 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -66,6 +66,18 @@ describe(':checkhealth', function() eq({}, getcompletion('', 'checkhealth')) assert_alive() end) + + it('vim.g.health', function() + clear() + command("let g:health = {'style':'float'}") + command('checkhealth lsp') + eq( + 'editor', + exec_lua([[ + return vim.api.nvim_win_get_config(0).relative + ]]) + ) + end) end) describe('vim.health', function() -- cgit From a4f575abd85e734340ee303daace1a63e5ca9782 Mon Sep 17 00:00:00 2001 From: Xuyuan Pang Date: Tue, 14 Jan 2025 07:17:23 +0800 Subject: fix(lsp): minimum height for floating popup #31990 Problem: The floating window for hover and signature help always cuts off a few lines, because the `_make_floating_popup_size` function counts empty lines as having zero height. Solution: Ensure the height is at least 1. --- test/functional/plugin/lsp_spec.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 9cefe96e79..5e9766c784 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3501,6 +3501,19 @@ describe('LSP', function() end) ) end) + it('handles empty line', function() + exec_lua(function() + _G.contents = { + '', + } + end) + eq( + { 20, 1 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents, { width = 20 }) } + end) + ) + end) end) describe('lsp.util.trim.trim_empty_lines', function() -- cgit From e8a6c1b02122852da83dc52184e78369598d8240 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 14 Jan 2025 08:19:54 -0600 Subject: fix(lsp): schedule call to vim.lsp.start for async root_dir (#31998) When `root_dir` is a function it can (and often will) call the provided callback function in a fast API context (e.g. in the `on_exit` handler of `vim.system`). When the callback function is executed we should ensure that it runs vim.lsp.start on the main event loop. --- test/functional/plugin/lsp_spec.lua | 53 ++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 24 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5e9766c784..db3ab8ed98 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -6259,37 +6259,42 @@ describe('LSP', function() ) end) - it('supports a function for root_dir', function() + it('supports async function for root_dir', function() exec_lua(create_server_definition) local tmp1 = t.tmpname(true) + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) - eq( - 'some_dir', - exec_lua(function() - local server = _G._create_server({ - handlers = { - initialize = function(_, _, callback) - callback(nil, { capabilities = {} }) - end, - }, - }) - - vim.lsp.config('foo', { - cmd = server.cmd, - filetypes = { 'foo' }, - root_dir = function(cb) + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_dir = function(cb) + vim.system({ 'sleep', '0' }, {}, function() cb('some_dir') - end, - }) - vim.lsp.enable('foo') + end) + end, + }) + vim.lsp.enable('foo') - vim.cmd.edit(assert(tmp1)) - vim.bo.filetype = 'foo' + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + end) - return vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })[1].root_dir - end) - ) + retry(nil, 1000, function() + eq( + 'some_dir', + exec_lua(function() + return vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })[1].root_dir + end) + ) + end) end) end) end) -- cgit From b9e6fa7ec81c463d77cc919392b52f6df2d8d304 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Fri, 17 Jan 2025 15:27:50 +0100 Subject: fix(lsp): use filterText as word if textEdit/label doesn't match Problem: With language servers like lemminx, completing xml tags like ` Date: Sun, 19 Jan 2025 21:49:02 +0100 Subject: fix(lsp): don't use completion filterText if prefix is empty Follow up to https://github.com/neovim/neovim/pull/32072 If there is no prefix (e.g. at the start of word boundary or a line), it always used the `filterText` because the `match` function always returned false. --- test/functional/plugin/lsp/completion_spec.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 84c8f5864a..4e90c2fd1b 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -239,13 +239,18 @@ describe('vim.lsp.completion: item conversion', function() }, }, } - local expected = { + assert_completion_matches('