From 50b256d51528fef068e07871e18aa3e324b7e2d8 Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 31 Jan 2023 13:08:46 +0100 Subject: fix(tests): use -l mode for lsp tests This fixes "fake server" from leaking memory, which makes ASAN very upset, except on current ASAN CI for some reason. --- test/functional/plugin/lsp_spec.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5229022564..fd162961ff 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -46,16 +46,14 @@ describe('LSP', function() local test_name = "basic_init" exec_lua([=[ lsp = require('vim.lsp') - local test_name, fixture_filename, logfile = ... + local test_name, fake_lsp_code, fake_lsp_logfile = ... function test__start_client() return lsp.start_client { cmd_env = { - NVIM_LOG_FILE = logfile; + NVIM_LOG_FILE = fake_lsp_logfile; }; cmd = { - vim.v.progpath, '-Es', '-u', 'NONE', '--headless', - "-c", string.format("lua TEST_NAME = %q", test_name), - "-c", "luafile "..fixture_filename; + vim.v.progpath, '-l', fake_lsp_code, test_name; }; workspace_folders = {{ uri = 'file://' .. vim.loop.cwd(), -- cgit From f43fa301c1a2817239e046a242902af65b7cac71 Mon Sep 17 00:00:00 2001 From: Eduard Baturin Date: Sat, 18 Feb 2023 09:43:59 +0300 Subject: fix(lsp): check if the buffer is a directory before w! it (#22289) --- test/functional/plugin/lsp_spec.lua | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index fd162961ff..f1aad08140 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2067,6 +2067,8 @@ describe('LSP', function() end) describe('lsp.util.rename', function() + local pathsep = helpers.get_pathsep() + it('Can rename an existing file', function() local old = helpers.tmpname() write_file(old, 'Test content') @@ -2089,6 +2091,32 @@ describe('LSP', function() eq(true, exists) os.remove(new) end) + it('Can rename a direcory', function() + -- only reserve the name, file must not exist for the test scenario + local old_dir = helpers.tmpname() + local new_dir = helpers.tmpname() + os.remove(old_dir) + os.remove(new_dir) + + helpers.mkdir_p(old_dir) + + local file = "file" + write_file(old_dir .. pathsep .. file, 'Test content') + + exec_lua([[ + local old_dir = select(1, ...) + local new_dir = select(2, ...) + + vim.lsp.util.rename(old_dir, new_dir) + ]], old_dir, new_dir) + + eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', old_dir)) + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new_dir)) + eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new_dir .. pathsep .. file)) + eq('Test content', read_file(new_dir .. pathsep .. file)) + + os.remove(new_dir) + end) it('Does not rename file if target exists and ignoreIfExists is set or overwrite is false', function() local old = helpers.tmpname() write_file(old, 'Old File') -- cgit From 5732aa706c639b3d775573d91d1139f24624629c Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Sat, 25 Feb 2023 03:07:18 -0600 Subject: feat(lsp): implement workspace/didChangeWatchedFiles (#21293) --- test/functional/plugin/lsp_spec.lua | 421 ++++++++++++++++++++++++++++++++++++ 1 file changed, 421 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index f1aad08140..5eb367b0b8 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1,5 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) local lsp_helpers = require('test.functional.plugin.lsp.helpers') +local lfs = require('lfs') local assert_log = helpers.assert_log local buf_lines = helpers.buf_lines @@ -3589,4 +3590,424 @@ describe('LSP', function() eq(expected, result) end) end) + + describe('vim.lsp._watchfiles', function() + it('sends notifications when files change', function() + local root_dir = helpers.tmpname() + os.remove(root_dir) + lfs.mkdir(root_dir) + + exec_lua(create_server_definition) + local result = exec_lua([[ + local root_dir = ... + + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = root_dir, + }) + + local expected_messages = 2 -- initialize, initialized + + local msg_wait_timeout = require('vim.lsp._watchfiles')._watchfunc == vim._watch.poll and 2500 or 200 + local function wait_for_messages() + assert(vim.wait(msg_wait_timeout, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + end + + wait_for_messages() + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-0', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/watch', + kind = 7, + }, + }, + }, + }, + }, + }, { client_id = client_id }) + + local path = root_dir .. '/watch' + local file = io.open(path, 'w') + file:close() + + expected_messages = expected_messages + 1 + wait_for_messages() + + os.remove(path) + + expected_messages = expected_messages + 1 + wait_for_messages() + + return server.messages + ]], root_dir) + + local function watched_uri(fname) + return exec_lua([[ + local root_dir, fname = ... + return vim.uri_from_fname(root_dir .. '/' .. fname) + ]], root_dir, fname) + end + + eq(4, #result) + eq('workspace/didChangeWatchedFiles', result[3].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('watch'), + }, + }, + }, result[3].params) + eq('workspace/didChangeWatchedFiles', result[4].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + uri = watched_uri('watch'), + }, + }, + }, result[4].params) + end) + + it('correctly registers and unregisters', function() + local root_dir = 'some_dir' + exec_lua(create_server_definition) + local result = exec_lua([[ + local root_dir = ... + + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = root_dir, + }) + + local expected_messages = 2 -- initialize, initialized + local function wait_for_messages() + assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + end + + wait_for_messages() + + local send_event + require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) + local stoppped = false + send_event = function(...) + if not stoppped then + callback(...) + end + end + return function() + stoppped = true + end + end + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-0', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/*.watch0', + }, + }, + }, + }, + }, + }, { client_id = client_id }) + + send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created) + send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created) + + expected_messages = expected_messages + 1 + wait_for_messages() + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-1', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/*.watch1', + }, + }, + }, + }, + }, + }, { client_id = client_id }) + + vim.lsp.handlers['client/unregisterCapability'](nil, { + unregisterations = { + { + id = 'watchfiles-test-0', + method = 'workspace/didChangeWatchedFiles', + }, + }, + }, { client_id = client_id }) + + send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created) + send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created) + + expected_messages = expected_messages + 1 + wait_for_messages() + + return server.messages + ]], root_dir) + + local function watched_uri(fname) + return exec_lua([[ + local root_dir, fname = ... + return vim.uri_from_fname(root_dir .. '/' .. fname) + ]], root_dir, fname) + end + + eq(4, #result) + eq('workspace/didChangeWatchedFiles', result[3].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('file.watch0'), + }, + }, + }, result[3].params) + eq('workspace/didChangeWatchedFiles', result[4].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('file.watch1'), + }, + }, + }, result[4].params) + end) + + it('correctly handles the registered watch kind', function() + local root_dir = 'some_dir' + exec_lua(create_server_definition) + local result = exec_lua([[ + local root_dir = ... + + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = root_dir, + }) + + local expected_messages = 2 -- initialize, initialized + local function wait_for_messages() + assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + end + + wait_for_messages() + + local watch_callbacks = {} + local function send_event(...) + for _, cb in ipairs(watch_callbacks) do + cb(...) + end + end + require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) + table.insert(watch_callbacks, callback) + return function() + -- noop because this test never stops the watch + end + end + + local protocol = require('vim.lsp.protocol') + + local watchers = {} + local max_kind = protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete + for i = 0, max_kind do + local j = i + table.insert(watchers, { + globPattern = { + baseUri = vim.uri_from_fname('/dir'..tostring(i)), + pattern = 'watch'..tostring(i), + }, + kind = i, + }) + end + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-kind', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = watchers, + }, + }, + }, + }, { client_id = client_id }) + + for i = 0, max_kind do + local filename = 'watch'..tostring(i) + send_event(filename, vim._watch.FileChangeType.Created) + send_event(filename, vim._watch.FileChangeType.Changed) + send_event(filename, vim._watch.FileChangeType.Deleted) + end + + expected_messages = expected_messages + 1 + wait_for_messages() + + return server.messages + ]], root_dir) + + local function watched_uri(fname) + return exec_lua([[ + return vim.uri_from_fname(...) + ]], fname) + end + + eq(3, #result) + eq('workspace/didChangeWatchedFiles', result[3].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('watch1'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + uri = watched_uri('watch2'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('watch3'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + uri = watched_uri('watch3'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + uri = watched_uri('watch4'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('watch5'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + uri = watched_uri('watch5'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + uri = watched_uri('watch6'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + uri = watched_uri('watch6'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('watch7'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + uri = watched_uri('watch7'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + uri = watched_uri('watch7'), + }, + }, + }, result[3].params) + end) + + it('prunes duplicate events', function() + local root_dir = 'some_dir' + exec_lua(create_server_definition) + local result = exec_lua([[ + local root_dir = ... + + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = root_dir, + }) + + local expected_messages = 2 -- initialize, initialized + local function wait_for_messages() + assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + end + + wait_for_messages() + + local send_event + require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) + send_event = callback + return function() + -- noop because this test never stops the watch + end + end + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-kind', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/*', + }, + }, + }, + }, + }, + }, { client_id = client_id }) + + send_event('file1', vim._watch.FileChangeType.Created) + send_event('file1', vim._watch.FileChangeType.Created) -- pruned + send_event('file1', vim._watch.FileChangeType.Changed) + send_event('file2', vim._watch.FileChangeType.Created) + send_event('file1', vim._watch.FileChangeType.Changed) -- pruned + + expected_messages = expected_messages + 1 + wait_for_messages() + + return server.messages + ]], root_dir) + + local function watched_uri(fname) + return exec_lua([[ + return vim.uri_from_fname(...) + ]], fname) + end + + eq(3, #result) + eq('workspace/didChangeWatchedFiles', result[3].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('file1'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + uri = watched_uri('file1'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('file2'), + }, + }, + }, result[3].params) + end) + end) end) -- cgit From f0f27e9aef7c237dd55fbb5c2cd47c2f42d01742 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Sat, 25 Feb 2023 11:17:28 +0100 Subject: Revert "feat(lsp): implement workspace/didChangeWatchedFiles (#21293)" This reverts commit 5732aa706c639b3d775573d91d1139f24624629c. Causes editor to freeze in projects with many watcher registrations --- test/functional/plugin/lsp_spec.lua | 421 ------------------------------------ 1 file changed, 421 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5eb367b0b8..f1aad08140 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1,6 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) local lsp_helpers = require('test.functional.plugin.lsp.helpers') -local lfs = require('lfs') local assert_log = helpers.assert_log local buf_lines = helpers.buf_lines @@ -3590,424 +3589,4 @@ describe('LSP', function() eq(expected, result) end) end) - - describe('vim.lsp._watchfiles', function() - it('sends notifications when files change', function() - local root_dir = helpers.tmpname() - os.remove(root_dir) - lfs.mkdir(root_dir) - - exec_lua(create_server_definition) - local result = exec_lua([[ - local root_dir = ... - - local server = _create_server() - local client_id = vim.lsp.start({ - name = 'watchfiles-test', - cmd = server.cmd, - root_dir = root_dir, - }) - - local expected_messages = 2 -- initialize, initialized - - local msg_wait_timeout = require('vim.lsp._watchfiles')._watchfunc == vim._watch.poll and 2500 or 200 - local function wait_for_messages() - assert(vim.wait(msg_wait_timeout, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) - end - - wait_for_messages() - - vim.lsp.handlers['client/registerCapability'](nil, { - registrations = { - { - id = 'watchfiles-test-0', - method = 'workspace/didChangeWatchedFiles', - registerOptions = { - watchers = { - { - globPattern = '**/watch', - kind = 7, - }, - }, - }, - }, - }, - }, { client_id = client_id }) - - local path = root_dir .. '/watch' - local file = io.open(path, 'w') - file:close() - - expected_messages = expected_messages + 1 - wait_for_messages() - - os.remove(path) - - expected_messages = expected_messages + 1 - wait_for_messages() - - return server.messages - ]], root_dir) - - local function watched_uri(fname) - return exec_lua([[ - local root_dir, fname = ... - return vim.uri_from_fname(root_dir .. '/' .. fname) - ]], root_dir, fname) - end - - eq(4, #result) - eq('workspace/didChangeWatchedFiles', result[3].method) - eq({ - changes = { - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), - uri = watched_uri('watch'), - }, - }, - }, result[3].params) - eq('workspace/didChangeWatchedFiles', result[4].method) - eq({ - changes = { - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), - uri = watched_uri('watch'), - }, - }, - }, result[4].params) - end) - - it('correctly registers and unregisters', function() - local root_dir = 'some_dir' - exec_lua(create_server_definition) - local result = exec_lua([[ - local root_dir = ... - - local server = _create_server() - local client_id = vim.lsp.start({ - name = 'watchfiles-test', - cmd = server.cmd, - root_dir = root_dir, - }) - - local expected_messages = 2 -- initialize, initialized - local function wait_for_messages() - assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) - end - - wait_for_messages() - - local send_event - require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) - local stoppped = false - send_event = function(...) - if not stoppped then - callback(...) - end - end - return function() - stoppped = true - end - end - - vim.lsp.handlers['client/registerCapability'](nil, { - registrations = { - { - id = 'watchfiles-test-0', - method = 'workspace/didChangeWatchedFiles', - registerOptions = { - watchers = { - { - globPattern = '**/*.watch0', - }, - }, - }, - }, - }, - }, { client_id = client_id }) - - send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created) - send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created) - - expected_messages = expected_messages + 1 - wait_for_messages() - - vim.lsp.handlers['client/registerCapability'](nil, { - registrations = { - { - id = 'watchfiles-test-1', - method = 'workspace/didChangeWatchedFiles', - registerOptions = { - watchers = { - { - globPattern = '**/*.watch1', - }, - }, - }, - }, - }, - }, { client_id = client_id }) - - vim.lsp.handlers['client/unregisterCapability'](nil, { - unregisterations = { - { - id = 'watchfiles-test-0', - method = 'workspace/didChangeWatchedFiles', - }, - }, - }, { client_id = client_id }) - - send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created) - send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created) - - expected_messages = expected_messages + 1 - wait_for_messages() - - return server.messages - ]], root_dir) - - local function watched_uri(fname) - return exec_lua([[ - local root_dir, fname = ... - return vim.uri_from_fname(root_dir .. '/' .. fname) - ]], root_dir, fname) - end - - eq(4, #result) - eq('workspace/didChangeWatchedFiles', result[3].method) - eq({ - changes = { - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), - uri = watched_uri('file.watch0'), - }, - }, - }, result[3].params) - eq('workspace/didChangeWatchedFiles', result[4].method) - eq({ - changes = { - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), - uri = watched_uri('file.watch1'), - }, - }, - }, result[4].params) - end) - - it('correctly handles the registered watch kind', function() - local root_dir = 'some_dir' - exec_lua(create_server_definition) - local result = exec_lua([[ - local root_dir = ... - - local server = _create_server() - local client_id = vim.lsp.start({ - name = 'watchfiles-test', - cmd = server.cmd, - root_dir = root_dir, - }) - - local expected_messages = 2 -- initialize, initialized - local function wait_for_messages() - assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) - end - - wait_for_messages() - - local watch_callbacks = {} - local function send_event(...) - for _, cb in ipairs(watch_callbacks) do - cb(...) - end - end - require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) - table.insert(watch_callbacks, callback) - return function() - -- noop because this test never stops the watch - end - end - - local protocol = require('vim.lsp.protocol') - - local watchers = {} - local max_kind = protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete - for i = 0, max_kind do - local j = i - table.insert(watchers, { - globPattern = { - baseUri = vim.uri_from_fname('/dir'..tostring(i)), - pattern = 'watch'..tostring(i), - }, - kind = i, - }) - end - vim.lsp.handlers['client/registerCapability'](nil, { - registrations = { - { - id = 'watchfiles-test-kind', - method = 'workspace/didChangeWatchedFiles', - registerOptions = { - watchers = watchers, - }, - }, - }, - }, { client_id = client_id }) - - for i = 0, max_kind do - local filename = 'watch'..tostring(i) - send_event(filename, vim._watch.FileChangeType.Created) - send_event(filename, vim._watch.FileChangeType.Changed) - send_event(filename, vim._watch.FileChangeType.Deleted) - end - - expected_messages = expected_messages + 1 - wait_for_messages() - - return server.messages - ]], root_dir) - - local function watched_uri(fname) - return exec_lua([[ - return vim.uri_from_fname(...) - ]], fname) - end - - eq(3, #result) - eq('workspace/didChangeWatchedFiles', result[3].method) - eq({ - changes = { - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), - uri = watched_uri('watch1'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), - uri = watched_uri('watch2'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), - uri = watched_uri('watch3'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), - uri = watched_uri('watch3'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), - uri = watched_uri('watch4'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), - uri = watched_uri('watch5'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), - uri = watched_uri('watch5'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), - uri = watched_uri('watch6'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), - uri = watched_uri('watch6'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), - uri = watched_uri('watch7'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), - uri = watched_uri('watch7'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), - uri = watched_uri('watch7'), - }, - }, - }, result[3].params) - end) - - it('prunes duplicate events', function() - local root_dir = 'some_dir' - exec_lua(create_server_definition) - local result = exec_lua([[ - local root_dir = ... - - local server = _create_server() - local client_id = vim.lsp.start({ - name = 'watchfiles-test', - cmd = server.cmd, - root_dir = root_dir, - }) - - local expected_messages = 2 -- initialize, initialized - local function wait_for_messages() - assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) - end - - wait_for_messages() - - local send_event - require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) - send_event = callback - return function() - -- noop because this test never stops the watch - end - end - - vim.lsp.handlers['client/registerCapability'](nil, { - registrations = { - { - id = 'watchfiles-test-kind', - method = 'workspace/didChangeWatchedFiles', - registerOptions = { - watchers = { - { - globPattern = '**/*', - }, - }, - }, - }, - }, - }, { client_id = client_id }) - - send_event('file1', vim._watch.FileChangeType.Created) - send_event('file1', vim._watch.FileChangeType.Created) -- pruned - send_event('file1', vim._watch.FileChangeType.Changed) - send_event('file2', vim._watch.FileChangeType.Created) - send_event('file1', vim._watch.FileChangeType.Changed) -- pruned - - expected_messages = expected_messages + 1 - wait_for_messages() - - return server.messages - ]], root_dir) - - local function watched_uri(fname) - return exec_lua([[ - return vim.uri_from_fname(...) - ]], fname) - end - - eq(3, #result) - eq('workspace/didChangeWatchedFiles', result[3].method) - eq({ - changes = { - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), - uri = watched_uri('file1'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), - uri = watched_uri('file1'), - }, - { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), - uri = watched_uri('file2'), - }, - }, - }, result[3].params) - end) - end) end) -- cgit From ac69ba5fa0081026f2c5e6e29d5788802479b7b9 Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Sun, 5 Mar 2023 00:52:27 -0600 Subject: feat(lsp): implement workspace/didChangeWatchedFiles (#22405) --- test/functional/plugin/lsp_spec.lua | 421 ++++++++++++++++++++++++++++++++++++ 1 file changed, 421 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index f1aad08140..5eb367b0b8 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1,5 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) local lsp_helpers = require('test.functional.plugin.lsp.helpers') +local lfs = require('lfs') local assert_log = helpers.assert_log local buf_lines = helpers.buf_lines @@ -3589,4 +3590,424 @@ describe('LSP', function() eq(expected, result) end) end) + + describe('vim.lsp._watchfiles', function() + it('sends notifications when files change', function() + local root_dir = helpers.tmpname() + os.remove(root_dir) + lfs.mkdir(root_dir) + + exec_lua(create_server_definition) + local result = exec_lua([[ + local root_dir = ... + + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = root_dir, + }) + + local expected_messages = 2 -- initialize, initialized + + local msg_wait_timeout = require('vim.lsp._watchfiles')._watchfunc == vim._watch.poll and 2500 or 200 + local function wait_for_messages() + assert(vim.wait(msg_wait_timeout, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + end + + wait_for_messages() + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-0', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/watch', + kind = 7, + }, + }, + }, + }, + }, + }, { client_id = client_id }) + + local path = root_dir .. '/watch' + local file = io.open(path, 'w') + file:close() + + expected_messages = expected_messages + 1 + wait_for_messages() + + os.remove(path) + + expected_messages = expected_messages + 1 + wait_for_messages() + + return server.messages + ]], root_dir) + + local function watched_uri(fname) + return exec_lua([[ + local root_dir, fname = ... + return vim.uri_from_fname(root_dir .. '/' .. fname) + ]], root_dir, fname) + end + + eq(4, #result) + eq('workspace/didChangeWatchedFiles', result[3].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('watch'), + }, + }, + }, result[3].params) + eq('workspace/didChangeWatchedFiles', result[4].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + uri = watched_uri('watch'), + }, + }, + }, result[4].params) + end) + + it('correctly registers and unregisters', function() + local root_dir = 'some_dir' + exec_lua(create_server_definition) + local result = exec_lua([[ + local root_dir = ... + + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = root_dir, + }) + + local expected_messages = 2 -- initialize, initialized + local function wait_for_messages() + assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + end + + wait_for_messages() + + local send_event + require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) + local stoppped = false + send_event = function(...) + if not stoppped then + callback(...) + end + end + return function() + stoppped = true + end + end + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-0', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/*.watch0', + }, + }, + }, + }, + }, + }, { client_id = client_id }) + + send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created) + send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created) + + expected_messages = expected_messages + 1 + wait_for_messages() + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-1', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/*.watch1', + }, + }, + }, + }, + }, + }, { client_id = client_id }) + + vim.lsp.handlers['client/unregisterCapability'](nil, { + unregisterations = { + { + id = 'watchfiles-test-0', + method = 'workspace/didChangeWatchedFiles', + }, + }, + }, { client_id = client_id }) + + send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created) + send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created) + + expected_messages = expected_messages + 1 + wait_for_messages() + + return server.messages + ]], root_dir) + + local function watched_uri(fname) + return exec_lua([[ + local root_dir, fname = ... + return vim.uri_from_fname(root_dir .. '/' .. fname) + ]], root_dir, fname) + end + + eq(4, #result) + eq('workspace/didChangeWatchedFiles', result[3].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('file.watch0'), + }, + }, + }, result[3].params) + eq('workspace/didChangeWatchedFiles', result[4].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('file.watch1'), + }, + }, + }, result[4].params) + end) + + it('correctly handles the registered watch kind', function() + local root_dir = 'some_dir' + exec_lua(create_server_definition) + local result = exec_lua([[ + local root_dir = ... + + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = root_dir, + }) + + local expected_messages = 2 -- initialize, initialized + local function wait_for_messages() + assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + end + + wait_for_messages() + + local watch_callbacks = {} + local function send_event(...) + for _, cb in ipairs(watch_callbacks) do + cb(...) + end + end + require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) + table.insert(watch_callbacks, callback) + return function() + -- noop because this test never stops the watch + end + end + + local protocol = require('vim.lsp.protocol') + + local watchers = {} + local max_kind = protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete + for i = 0, max_kind do + local j = i + table.insert(watchers, { + globPattern = { + baseUri = vim.uri_from_fname('/dir'..tostring(i)), + pattern = 'watch'..tostring(i), + }, + kind = i, + }) + end + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-kind', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = watchers, + }, + }, + }, + }, { client_id = client_id }) + + for i = 0, max_kind do + local filename = 'watch'..tostring(i) + send_event(filename, vim._watch.FileChangeType.Created) + send_event(filename, vim._watch.FileChangeType.Changed) + send_event(filename, vim._watch.FileChangeType.Deleted) + end + + expected_messages = expected_messages + 1 + wait_for_messages() + + return server.messages + ]], root_dir) + + local function watched_uri(fname) + return exec_lua([[ + return vim.uri_from_fname(...) + ]], fname) + end + + eq(3, #result) + eq('workspace/didChangeWatchedFiles', result[3].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('watch1'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + uri = watched_uri('watch2'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('watch3'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + uri = watched_uri('watch3'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + uri = watched_uri('watch4'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('watch5'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + uri = watched_uri('watch5'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + uri = watched_uri('watch6'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + uri = watched_uri('watch6'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('watch7'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + uri = watched_uri('watch7'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + uri = watched_uri('watch7'), + }, + }, + }, result[3].params) + end) + + it('prunes duplicate events', function() + local root_dir = 'some_dir' + exec_lua(create_server_definition) + local result = exec_lua([[ + local root_dir = ... + + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = root_dir, + }) + + local expected_messages = 2 -- initialize, initialized + local function wait_for_messages() + assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + end + + wait_for_messages() + + local send_event + require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) + send_event = callback + return function() + -- noop because this test never stops the watch + end + end + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-kind', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/*', + }, + }, + }, + }, + }, + }, { client_id = client_id }) + + send_event('file1', vim._watch.FileChangeType.Created) + send_event('file1', vim._watch.FileChangeType.Created) -- pruned + send_event('file1', vim._watch.FileChangeType.Changed) + send_event('file2', vim._watch.FileChangeType.Created) + send_event('file1', vim._watch.FileChangeType.Changed) -- pruned + + expected_messages = expected_messages + 1 + wait_for_messages() + + return server.messages + ]], root_dir) + + local function watched_uri(fname) + return exec_lua([[ + return vim.uri_from_fname(...) + ]], fname) + end + + eq(3, #result) + eq('workspace/didChangeWatchedFiles', result[3].method) + eq({ + changes = { + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('file1'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + uri = watched_uri('file1'), + }, + { + type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + uri = watched_uri('file2'), + }, + }, + }, result[3].params) + end) + end) end) -- cgit From 9ef7297ef142354ace8b1f3f277d0eee3cfdc6d4 Mon Sep 17 00:00:00 2001 From: Michal Liszcz Date: Thu, 9 Mar 2023 15:12:56 +0100 Subject: feat(lsp): overwrite omnifunc/tagfunc set by ftplugin #22267 Problem: Some built-in ftplugins set omnifunc/tagfunc/formatexpr which causes lsp.lua:set_defaults() to skip setup of defaults for those filetypes. For example the C++ ftplugin has: omnifunc=ccomplete#Complete Last set from /usr/share/nvim/runtime/ftplugin/c.vim line 30 so the changes done in #95c65a6b221fe6e1cf91e8322e7d7571dc511a71 will always be skipped for C++ files. Solution: Overwrite omnifunc/tagfunc/formatexpr options that were set by stock ftplugin. Fixes #21001 --- test/functional/plugin/lsp_spec.lua | 103 ++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5eb367b0b8..56c53a6fed 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -31,6 +31,11 @@ local fake_lsp_code = lsp_helpers.fake_lsp_code local fake_lsp_logfile = lsp_helpers.fake_lsp_logfile local test_rpc_server = lsp_helpers.test_rpc_server +local function get_buf_option(name, bufnr) + bufnr = bufnr or "BUFFER" + return exec_lua(string.format("return vim.api.nvim_buf_get_option(%s, '%s')", bufnr, name)) +end + -- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837 if skip(is_os('win')) then return end @@ -313,6 +318,104 @@ describe('LSP', function() } end) + it('should set default options on attach', function() + local client + test_rpc_server { + test_name = "set_defaults_all_capabilities"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + ]] + end; + on_init = function(_client) + client = _client + exec_lua("lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)") + end; + on_handler = function(_, _, ctx) + if ctx.method == 'test' then + eq('v:lua.vim.lsp.tagfunc', get_buf_option("tagfunc")) + eq('v:lua.vim.lsp.omnifunc', get_buf_option("omnifunc")) + eq('v:lua.vim.lsp.formatexpr()', get_buf_option("formatexpr")) + client.stop() + end + end; + on_exit = function(_, _) + eq('', get_buf_option("tagfunc")) + eq('', get_buf_option("omnifunc")) + eq('', get_buf_option("formatexpr")) + end; + } + end) + + it('should overwrite options set by ftplugins', function() + local client + test_rpc_server { + test_name = "set_defaults_all_capabilities"; + on_setup = function() + exec_lua [[ + vim.api.nvim_command('filetype plugin on') + BUFFER_1 = vim.api.nvim_create_buf(false, true) + BUFFER_2 = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_option(BUFFER_1, 'filetype', 'man') + vim.api.nvim_buf_set_option(BUFFER_2, 'filetype', 'xml') + ]] + eq('v:lua.require\'man\'.goto_tag', get_buf_option("tagfunc", "BUFFER_1")) + eq('xmlcomplete#CompleteTags', get_buf_option("omnifunc", "BUFFER_2")) + eq('xmlformat#Format()', get_buf_option("formatexpr", "BUFFER_2")) + end; + on_init = function(_client) + client = _client + exec_lua("lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID)") + exec_lua("lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID)") + end; + on_handler = function(_, _, ctx) + if ctx.method == 'test' then + eq('v:lua.vim.lsp.tagfunc', get_buf_option("tagfunc", "BUFFER_1")) + eq('v:lua.vim.lsp.omnifunc', get_buf_option("omnifunc", "BUFFER_2")) + eq('v:lua.vim.lsp.formatexpr()', get_buf_option("formatexpr", "BUFFER_2")) + client.stop() + end + end; + on_exit = function(_, _) + eq('', get_buf_option("tagfunc", "BUFFER_1")) + eq('', get_buf_option("omnifunc", "BUFFER_2")) + eq('', get_buf_option("formatexpr", "BUFFER_2")) + end; + } + end) + + it('should not overwrite user-defined options', function() + local client + test_rpc_server { + test_name = "set_defaults_all_capabilities"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_option(BUFFER, 'tagfunc', 'tfu') + vim.api.nvim_buf_set_option(BUFFER, 'omnifunc', 'ofu') + vim.api.nvim_buf_set_option(BUFFER, 'formatexpr', 'fex') + ]] + end; + on_init = function(_client) + client = _client + exec_lua("lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)") + end; + on_handler = function(_, _, ctx) + if ctx.method == 'test' then + eq('tfu', get_buf_option("tagfunc")) + eq('ofu', get_buf_option("omnifunc")) + eq('fex', get_buf_option("formatexpr")) + client.stop() + end + end; + on_exit = function(_, _) + eq('tfu', get_buf_option("tagfunc")) + eq('ofu', get_buf_option("omnifunc")) + eq('fex', get_buf_option("formatexpr")) + end; + } + end) + it('should detach buffer on bufwipe', function() clear() exec_lua(create_server_definition) -- cgit From d3c8d104bc921a27d56d2438f98bf7e80b623e47 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 10 Mar 2023 17:30:40 +0100 Subject: fix(lsp): prevent lsp tests from picking up local user config (#22606) Sets `NVIM_APPNAME` for the lsp server instances and also for the `exec_lua` environment to ensure local user config doesn't interfere with the test cases. My local `ftplugin/xml.lua` broke the LSP test cases about setting `omnifunc` defaults. --- test/functional/plugin/lsp_spec.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 56c53a6fed..c621a5eae2 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -57,6 +57,7 @@ describe('LSP', function() return lsp.start_client { cmd_env = { NVIM_LOG_FILE = fake_lsp_logfile; + NVIM_APPNAME = "nvim_lsp_test"; }; cmd = { vim.v.progpath, '-l', fake_lsp_code, test_name; -- cgit From d15abd1be4ae85b10174e3ee139d3b7605e87577 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sun, 12 Mar 2023 09:45:28 +0100 Subject: fix(lsp): use line start/end for visual line selection (#22632) Fixes https://github.com/neovim/neovim/issues/22629 --- test/functional/plugin/lsp_spec.lua | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index c621a5eae2..a6e50ac82c 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3554,6 +3554,7 @@ describe('LSP', function() vim.cmd.normal('v') vim.api.nvim_win_set_cursor(0, { 2, 3 }) vim.lsp.buf.format({ bufnr = bufnr, false }) + vim.lsp.stop_client(client_id) return server.messages ]]) eq("textDocument/rangeFormatting", result[3].method) @@ -3563,6 +3564,52 @@ describe('LSP', function() } eq(expected_range, result[3].params.range) end) + it('format formats range in visual line mode', function() + exec_lua(create_server_definition) + local result = exec_lua([[ + local server = _create_server({ capabilities = { + documentFormattingProvider = true, + documentRangeFormattingProvider = true, + }}) + local bufnr = vim.api.nvim_get_current_buf() + local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + vim.api.nvim_win_set_buf(0, bufnr) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar baz'}) + vim.api.nvim_win_set_cursor(0, { 1, 2 }) + vim.cmd.normal('V') + vim.api.nvim_win_set_cursor(0, { 2, 1 }) + vim.lsp.buf.format({ bufnr = bufnr, false }) + + -- Format again with visual lines going from bottom to top + -- Must result in same formatting + vim.cmd.normal("") + vim.api.nvim_win_set_cursor(0, { 2, 1 }) + vim.cmd.normal('V') + vim.api.nvim_win_set_cursor(0, { 1, 2 }) + vim.lsp.buf.format({ bufnr = bufnr, false }) + + vim.lsp.stop_client(client_id) + return server.messages + ]]) + local expected_methods = { + "initialize", + "initialized", + "textDocument/rangeFormatting", + "$/cancelRequest", + "textDocument/rangeFormatting", + "$/cancelRequest", + "shutdown", + "exit", + } + eq(expected_methods, vim.tbl_map(function(x) return x.method end, result)) + -- uses first column of start line and last column of end line + local expected_range = { + start = { line = 0, character = 0 }, + ['end'] = { line = 1, character = 7 }, + } + eq(expected_range, result[3].params.range) + eq(expected_range, result[5].params.range) + end) it('Aborts with notify if no clients support requested method', function() exec_lua(create_server_definition) exec_lua([[ -- cgit From 8dde7c907ca9ad365895bded2c2f59e08f65d3ed Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Tue, 14 Mar 2023 20:59:43 +0900 Subject: fix(lsp): vim.lsp.util.apply_text_edits cursor validation #22636 Problem Using wrong variable when checking the cursor position is valid or not in vim.lsp.util.apply_text_edits. Solution Use the correct variable. --- test/functional/plugin/lsp_spec.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index a6e50ac82c..ddbeef6d84 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1714,6 +1714,9 @@ describe('LSP', function() end) it('fix the cursor col', function() + -- append empty last line. See #22636 + exec_lua('vim.api.nvim_buf_set_lines(...)', 1, -1, -1, true, {''}) + funcs.nvim_win_set_cursor(0, { 2, 11 }) local edits = { make_edit(1, 7, 1, 11, '') @@ -1725,6 +1728,7 @@ describe('LSP', function() 'Third line of text'; 'Fourth line of text'; 'å å ɧ 汉语 ↥ 🤦 🦄'; + ''; }, buf_lines(1)) eq({ 2, 7 }, funcs.nvim_win_get_cursor(0)) end) -- cgit From 4f7879dff0f0dc22ddf4cb2a2095b88605a3bab0 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 14 Mar 2023 13:08:37 +0100 Subject: fix(lsp): kill buffers after renaming a directory #22618 Problem: When LSP client renames a directory, opened buffers in the edfitor are not renamed or closed. Then `:wall` shows errors. https://github.com/neovim/neovim/blob/master/runtime/lua/vim/lsp/util.lua#L776 works correctly if you try to rename a single file, but doesn't delete old buffers with `old_fname` is a dir. Solution: Update the logic in runtime/lua/vim/lsp/util.lua:rename() Fixes #22617 --- test/functional/plugin/lsp_spec.lua | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index ddbeef6d84..8b5e7d56ac 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2200,7 +2200,22 @@ describe('LSP', function() eq(true, exists) os.remove(new) end) - it('Can rename a direcory', function() + it("Kills old buffer after renaming an existing file", function() + local old = helpers.tmpname() + write_file(old, 'Test content') + local new = helpers.tmpname() + os.remove(new) -- only reserve the name, file must not exist for the test scenario + local lines = exec_lua([[ + local old = select(1, ...) + local oldbufnr = vim.fn.bufadd(old) + local new = select(2, ...) + vim.lsp.util.rename(old, new) + return vim.fn.bufloaded(oldbufnr) + ]], old, new) + eq(0, lines) + os.remove(new) + end) + it('Can rename a directory', function() -- only reserve the name, file must not exist for the test scenario local old_dir = helpers.tmpname() local new_dir = helpers.tmpname() @@ -2209,16 +2224,19 @@ describe('LSP', function() helpers.mkdir_p(old_dir) - local file = "file" + local file = 'file.txt' write_file(old_dir .. pathsep .. file, 'Test content') - exec_lua([[ + local lines = exec_lua([[ local old_dir = select(1, ...) local new_dir = select(2, ...) + local pathsep = select(3, ...) + local oldbufnr = vim.fn.bufadd(old_dir .. pathsep .. 'file') vim.lsp.util.rename(old_dir, new_dir) - ]], old_dir, new_dir) - + return vim.fn.bufloaded(oldbufnr) + ]], old_dir, new_dir, pathsep) + eq(0, lines) eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', old_dir)) eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new_dir)) eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new_dir .. pathsep .. file)) -- cgit From 6a6191174afd7604b82fcc04b9a27b8d51133c9f Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Sun, 26 Mar 2023 04:41:27 -0500 Subject: test: fix flaky watchfiles tests (#22637) --- test/functional/plugin/lsp_spec.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 8b5e7d56ac..3de4c6275e 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3783,7 +3783,8 @@ describe('LSP', function() local expected_messages = 2 -- initialize, initialized - local msg_wait_timeout = require('vim.lsp._watchfiles')._watchfunc == vim._watch.poll and 2500 or 200 + local watchfunc = require('vim.lsp._watchfiles')._watchfunc + local msg_wait_timeout = watchfunc == vim._watch.poll and 2500 or 200 local function wait_for_messages() assert(vim.wait(msg_wait_timeout, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) end @@ -3807,6 +3808,10 @@ describe('LSP', function() }, }, { client_id = client_id }) + if watchfunc == vim._watch.poll then + vim.wait(100) + end + local path = root_dir .. '/watch' local file = io.open(path, 'w') file:close() -- cgit From a5c572bd446a89be2dccb2f7479ff1b017074640 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:07:33 +0200 Subject: docs: fix typos Co-authored-by: Gregory Anders Co-authored-by: Raphael Co-authored-by: C.D. MacEachern Co-authored-by: himanoa --- test/functional/plugin/lsp_spec.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 3de4c6275e..7d287d38fa 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3877,14 +3877,14 @@ describe('LSP', function() local send_event require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) - local stoppped = false + local stopped = false send_event = function(...) - if not stoppped then + if not stopped then callback(...) end end return function() - stoppped = true + stopped = true end end -- cgit From 743860de40502227b3f0ed64317eb937d24d4a36 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 4 Apr 2023 21:59:06 +0200 Subject: test: replace lfs with luv and vim.fs test: replace lfs with luv luv already pretty much does everything lfs does, so this duplication of dependencies isn't needed. --- test/functional/plugin/lsp_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 7d287d38fa..da05b09593 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1,6 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) local lsp_helpers = require('test.functional.plugin.lsp.helpers') -local lfs = require('lfs') local assert_log = helpers.assert_log local buf_lines = helpers.buf_lines @@ -24,6 +23,7 @@ local is_ci = helpers.is_ci local meths = helpers.meths local is_os = helpers.is_os local skip = helpers.skip +local mkdir = helpers.mkdir local clear_notrace = lsp_helpers.clear_notrace local create_server_definition = lsp_helpers.create_server_definition @@ -3768,7 +3768,7 @@ describe('LSP', function() it('sends notifications when files change', function() local root_dir = helpers.tmpname() os.remove(root_dir) - lfs.mkdir(root_dir) + mkdir(root_dir) exec_lua(create_server_definition) local result = exec_lua([[ -- cgit From 3c697f62fa0d941a8cfd287f38a159293905bac4 Mon Sep 17 00:00:00 2001 From: Michal Liszcz Date: Tue, 11 Apr 2023 18:37:27 +0200 Subject: test(lsp): fix unstable tests for set_defaults (#23002) In the `test_rpc_server` procedure, both `on_setup` and `on_init` callbacks can run concurrently in some scenarios. This caused some CI failures in tests for the LSP set_defaults feature. This commit attempts to fix this by merging those two callbacks in the impacted tests. See: https://github.com/neovim/neovim/actions/runs/4553550710/attempts/1 --- test/functional/plugin/lsp_spec.lua | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index da05b09593..5ba0706208 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -323,15 +323,13 @@ describe('LSP', function() local client test_rpc_server { test_name = "set_defaults_all_capabilities"; - on_setup = function() + on_init = function(_client) + client = _client exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) + lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) ]] end; - on_init = function(_client) - client = _client - exec_lua("lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)") - end; on_handler = function(_, _, ctx) if ctx.method == 'test' then eq('v:lua.vim.lsp.tagfunc', get_buf_option("tagfunc")) @@ -352,7 +350,8 @@ describe('LSP', function() local client test_rpc_server { test_name = "set_defaults_all_capabilities"; - on_setup = function() + on_init = function(_client) + client = _client exec_lua [[ vim.api.nvim_command('filetype plugin on') BUFFER_1 = vim.api.nvim_create_buf(false, true) @@ -360,14 +359,16 @@ describe('LSP', function() vim.api.nvim_buf_set_option(BUFFER_1, 'filetype', 'man') vim.api.nvim_buf_set_option(BUFFER_2, 'filetype', 'xml') ]] + + -- Sanity check to ensure that some values are set after setting filetype. eq('v:lua.require\'man\'.goto_tag', get_buf_option("tagfunc", "BUFFER_1")) eq('xmlcomplete#CompleteTags', get_buf_option("omnifunc", "BUFFER_2")) eq('xmlformat#Format()', get_buf_option("formatexpr", "BUFFER_2")) - end; - on_init = function(_client) - client = _client - exec_lua("lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID)") - exec_lua("lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID)") + + exec_lua [[ + lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID) + lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID) + ]] end; on_handler = function(_, _, ctx) if ctx.method == 'test' then @@ -389,18 +390,16 @@ describe('LSP', function() local client test_rpc_server { test_name = "set_defaults_all_capabilities"; - on_setup = function() + on_init = function(_client) + client = _client exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_option(BUFFER, 'tagfunc', 'tfu') vim.api.nvim_buf_set_option(BUFFER, 'omnifunc', 'ofu') vim.api.nvim_buf_set_option(BUFFER, 'formatexpr', 'fex') + lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) ]] end; - on_init = function(_client) - client = _client - exec_lua("lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)") - end; on_handler = function(_, _, ctx) if ctx.method == 'test' then eq('tfu', get_buf_option("tagfunc")) -- cgit From 075a72d5ff9d49b1e93c0253b54931ecdcf673f3 Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Tue, 9 May 2023 11:12:54 -0500 Subject: fix(lsp): fix relative patterns for `workspace/didChangeWatchedFiles` (#23548) --- test/functional/plugin/lsp_spec.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5ba0706208..fc7b2dafb8 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3855,7 +3855,7 @@ describe('LSP', function() end) it('correctly registers and unregisters', function() - local root_dir = 'some_dir' + local root_dir = '/some_dir' exec_lua(create_server_definition) local result = exec_lua([[ local root_dir = ... @@ -4009,10 +4009,9 @@ describe('LSP', function() local watchers = {} local max_kind = protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete for i = 0, max_kind do - local j = i table.insert(watchers, { globPattern = { - baseUri = vim.uri_from_fname('/dir'..tostring(i)), + baseUri = vim.uri_from_fname('/dir'), pattern = 'watch'..tostring(i), }, kind = i, @@ -4031,7 +4030,7 @@ describe('LSP', function() }, { client_id = client_id }) for i = 0, max_kind do - local filename = 'watch'..tostring(i) + local filename = '/dir/watch' .. tostring(i) send_event(filename, vim._watch.FileChangeType.Created) send_event(filename, vim._watch.FileChangeType.Changed) send_event(filename, vim._watch.FileChangeType.Deleted) @@ -4045,7 +4044,8 @@ describe('LSP', function() local function watched_uri(fname) return exec_lua([[ - return vim.uri_from_fname(...) + local fname = ... + return vim.uri_from_fname('/dir/' .. fname) ]], fname) end -- cgit From 073035a030f5ef8ae9b9ca51552f887944c52eaa Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Sat, 20 May 2023 00:45:39 -0500 Subject: fix(lsp): don't register didChangeWatchedFiles when capability not set (#23689) Some LSP servers (tailwindcss, rome) are known to request registration for `workspace/didChangeWatchedFiles` even when the corresponding client capability does not advertise support. This change adds an extra check in the `client/registerCapability` handler not to start a watch unless the client capability is set appropriately. --- test/functional/plugin/lsp_spec.lua | 84 +++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index fc7b2dafb8..506bc1333b 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3778,6 +3778,13 @@ describe('LSP', function() name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, + }, + }, }) local expected_messages = 2 -- initialize, initialized @@ -3865,6 +3872,13 @@ describe('LSP', function() name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, + }, + }, }) local expected_messages = 2 -- initialize, initialized @@ -3982,6 +3996,13 @@ describe('LSP', function() name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, + }, + }, }) local expected_messages = 2 -- initialize, initialized @@ -4116,6 +4137,13 @@ describe('LSP', function() name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, + }, + }, }) local expected_messages = 2 -- initialize, initialized @@ -4186,5 +4214,61 @@ describe('LSP', function() }, }, result[3].params) end) + + it("ignores registrations by servers when the client doesn't advertise support", function() + exec_lua(create_server_definition) + local result = exec_lua([[ + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = 'some_dir', + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = false, + }, + }, + }, + }) + + local watching = false + require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) + -- Since the registration is ignored, this should not execute and `watching` should stay false + watching = true + return function() end + end + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-kind', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/*', + }, + }, + }, + }, + }, + }, { client_id = client_id }) + + -- Ensure no errors occur when unregistering something that was never really registered. + vim.lsp.handlers['client/unregisterCapability'](nil, { + unregisterations = { + { + id = 'watchfiles-test-kind', + method = 'workspace/didChangeWatchedFiles', + }, + }, + }, { client_id = client_id }) + + return watching + ]]) + + eq(false, result) + end) end) end) -- cgit From 1fe1bb084d0099fc4f9bfdc11189485d0f74b75a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 19 Dec 2022 16:37:45 +0000 Subject: refactor(options): deprecate nvim[_buf|_win]_[gs]et_option Co-authored-by: zeertzjq Co-authored-by: famiu --- test/functional/plugin/lsp_spec.lua | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 506bc1333b..b906ae265f 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -33,7 +33,9 @@ local test_rpc_server = lsp_helpers.test_rpc_server local function get_buf_option(name, bufnr) bufnr = bufnr or "BUFFER" - return exec_lua(string.format("return vim.api.nvim_buf_get_option(%s, '%s')", bufnr, name)) + return exec_lua( + string.format("return vim.api.nvim_get_option_value('%s', { buf = %s })", name, bufnr) + ) end -- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837 @@ -356,8 +358,8 @@ describe('LSP', function() vim.api.nvim_command('filetype plugin on') BUFFER_1 = vim.api.nvim_create_buf(false, true) BUFFER_2 = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_option(BUFFER_1, 'filetype', 'man') - vim.api.nvim_buf_set_option(BUFFER_2, 'filetype', 'xml') + vim.api.nvim_set_option_value('filetype', 'man', { buf = BUFFER_1 }) + vim.api.nvim_set_option_value('filetype', 'xml', { buf = BUFFER_2 }) ]] -- Sanity check to ensure that some values are set after setting filetype. @@ -394,9 +396,9 @@ describe('LSP', function() client = _client exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_option(BUFFER, 'tagfunc', 'tfu') - vim.api.nvim_buf_set_option(BUFFER, 'omnifunc', 'ofu') - vim.api.nvim_buf_set_option(BUFFER, 'formatexpr', 'fex') + vim.api.nvim_set_option_value('tagfunc', 'tfu', { buf = BUFFER }) + vim.api.nvim_set_option_value('omnifunc', 'ofu', { buf = BUFFER }) + vim.api.nvim_set_option_value('formatexpr', 'fex', { buf = BUFFER }) lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) ]] end; @@ -1166,7 +1168,7 @@ describe('LSP', function() "testing"; "123"; }) - vim.api.nvim_buf_set_option(BUFFER, 'eol', false) + vim.bo[BUFFER].eol = false ]] end; on_init = function(_client) @@ -2929,8 +2931,8 @@ describe('LSP', function() describe('lsp.util.get_effective_tabstop', function() local function test_tabstop(tabsize, shiftwidth) exec_lua(string.format([[ - vim.api.nvim_buf_set_option(0, 'shiftwidth', %d) - vim.api.nvim_buf_set_option(0, 'tabstop', 2) + vim.bo.shiftwidth = %d + vim.bo.tabstop = 2 ]], shiftwidth)) eq(tabsize, exec_lua('return vim.lsp.util.get_effective_tabstop()')) end -- cgit From ddd92a70d2aab5247895e89abaaa79c62ba7dbb4 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Sun, 28 May 2023 07:51:28 +0200 Subject: feat(lsp): initial support for dynamic capabilities (#23681) - `client.dynamic_capabilities` is an object that tracks client register/unregister - `client.supports_method` will additionally check if a dynamic capability supports the method, taking document filters into account. But only if the client enabled `dynamicRegistration` for the capability - updated the default client capabilities to include dynamicRegistration for: - formatting - rangeFormatting - hover - codeAction - hover - rename --- test/functional/plugin/lsp_spec.lua | 90 +++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index b906ae265f..1a7a656d1d 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3765,6 +3765,96 @@ describe('LSP', function() end) end) + describe('#dynamic vim.lsp._dynamic', function() + it('supports dynamic registration', function() + local root_dir = helpers.tmpname() + os.remove(root_dir) + mkdir(root_dir) + local tmpfile = root_dir .. '/dynamic.foo' + local file = io.open(tmpfile, 'w') + file:close() + + exec_lua(create_server_definition) + local result = exec_lua([[ + local root_dir, tmpfile = ... + + local server = _create_server() + local client_id = vim.lsp.start({ + name = 'dynamic-test', + cmd = server.cmd, + root_dir = root_dir, + capabilities = { + textDocument = { + formatting = { + dynamicRegistration = true, + }, + rangeFormatting = { + dynamicRegistration = true, + }, + }, + }, + }) + + local expected_messages = 2 -- initialize, initialized + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'formatting', + method = 'textDocument/formatting', + registerOptions = { + documentSelector = {{ + pattern = root_dir .. '/*.foo', + }}, + }, + }, + }, + }, { client_id = client_id }) + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'range-formatting', + method = 'textDocument/rangeFormatting', + }, + }, + }, { client_id = client_id }) + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'completion', + method = 'textDocument/completion', + }, + }, + }, { client_id = client_id }) + + local result = {} + local function check(method, fname) + local bufnr = fname and vim.fn.bufadd(fname) or nil + local client = vim.lsp.get_client_by_id(client_id) + result[#result + 1] = {method = method, fname = fname, supported = client.supports_method(method, {bufnr = bufnr})} + end + + + check("textDocument/formatting") + check("textDocument/formatting", tmpfile) + check("textDocument/rangeFormatting") + check("textDocument/rangeFormatting", tmpfile) + check("textDocument/completion") + + return result + ]], root_dir, tmpfile) + + eq(5, #result) + eq({method = 'textDocument/formatting', supported = false}, result[1]) + eq({method = 'textDocument/formatting', supported = true, fname = tmpfile}, result[2]) + eq({method = 'textDocument/rangeFormatting', supported = true}, result[3]) + eq({method = 'textDocument/rangeFormatting', supported = true, fname = tmpfile}, result[4]) + eq({method = 'textDocument/completion', supported = false}, result[5]) + end) + end) + describe('vim.lsp._watchfiles', function() it('sends notifications when files change', function() local root_dir = helpers.tmpname() -- cgit From 58618d208acd3827c4e86668529edb619bb9b8dd Mon Sep 17 00:00:00 2001 From: jdrouhard Date: Tue, 30 May 2023 13:56:29 -0500 Subject: feat(lsp)!: promote LspRequest to a full autocmd and enrich with additional data (#23694) BREAKING CHANGE: LspRequest is no longer a User autocmd but is now a first class citizen. LspRequest as a User autocmd had limited functionality. Namely, the only thing you could do was use the notification to do a lookup on all the clients' requests tables to figure out what changed. Promoting the autocmd to a full autocmd lets us set the buffer the request was initiated on (so people can set buffer-local autocmds for listening to these events). Additionally, when used from Lua, we can pass additional metadata about the request along with the notification, including the client ID, the request ID, and the actual request object stored on the client's requests table. Users can now listen for these events and act on them proactively instead of polling all of the requests tables and looking for changes. --- test/functional/plugin/lsp_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 1a7a656d1d..e0ce62c0db 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -948,7 +948,7 @@ describe('LSP', function() test_name = "check_tracked_requests_cleared"; on_init = function(_client) command('let g:requests = 0') - command('autocmd User LspRequest let g:requests+=1') + command('autocmd LspRequest * let g:requests+=1') client = _client client.request("slow_request") eq(1, eval('g:requests')) -- cgit From fb54e6980ea6fec218a11f118e97ef65f250395a Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Thu, 1 Jun 2023 11:15:33 -0500 Subject: feat(lsp): set client offset_encoding if server supports positionEncoding If the server sends the positionEncoding capability in its initialization response, automatically set the client's offset_encoding to use the value provided. --- test/functional/plugin/lsp_spec.lua | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index e0ce62c0db..dac49345d0 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -218,6 +218,34 @@ describe('LSP', function() }) end) + it("should set the client's offset_encoding when positionEncoding capability is supported", function() + clear() + exec_lua(create_server_definition) + local result = exec_lua([[ + local server = _create_server({ + capabilities = { + positionEncoding = "utf-8" + }, + }) + + local client_id = vim.lsp.start({ + name = 'dummy', + cmd = server.cmd, + }) + + if not client_id then + return 'vim.lsp.start did not return client_id' + end + + local client = vim.lsp.get_client_by_id(client_id) + if not client then + return 'No client found with id ' .. client_id + end + return client.offset_encoding + ]]) + eq('utf-8', result) + end) + it('should succeed with manual shutdown', function() if is_ci() then pending('hangs the build on CI #14028, re-enable with freeze timeout #14204') -- cgit From 2db719f6c2b677fcbc197b02fe52764a851523b2 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 3 Jun 2023 11:06:00 +0100 Subject: feat(lua): rename vim.loop -> vim.uv (#22846) --- test/functional/plugin/lsp_spec.lua | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index dac49345d0..26fd2276fd 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -65,7 +65,7 @@ describe('LSP', function() vim.v.progpath, '-l', fake_lsp_code, test_name; }; workspace_folders = {{ - uri = 'file://' .. vim.loop.cwd(), + uri = 'file://' .. vim.uv.cwd(), name = 'test_folder', }}; } @@ -2059,7 +2059,7 @@ describe('LSP', function() } } exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') - eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile)) end) it('Supports file creation in folder that needs to be created with CreateFile payload', function() local tmpfile = helpers.tmpname() @@ -2075,7 +2075,7 @@ describe('LSP', function() } } exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') - eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile)) end) it('createFile does not touch file if it exists and ignoreIfExists is set', function() local tmpfile = helpers.tmpname() @@ -2093,7 +2093,7 @@ describe('LSP', function() } } exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') - eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile)) eq('Dummy content', read_file(tmpfile)) end) it('createFile overrides file if overwrite is set', function() @@ -2113,7 +2113,7 @@ describe('LSP', function() } } exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') - eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile)) eq('', read_file(tmpfile)) end) it('DeleteFile delete file and buffer', function() @@ -2134,7 +2134,7 @@ describe('LSP', function() } } eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')) - eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq(false, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile)) eq(false, exec_lua('return vim.api.nvim_buf_is_loaded(vim.fn.bufadd(...))', tmpfile)) end) it('DeleteFile fails if file does not exist and ignoreIfNotExists is false', function() @@ -2153,7 +2153,7 @@ describe('LSP', function() } } eq(false, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit)) - eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) + eq(false, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile)) end) end) @@ -2223,9 +2223,9 @@ describe('LSP', function() return vim.api.nvim_buf_get_lines(bufnr, 0, -1, true) ]], old, new) eq({'Test content'}, lines) - local exists = exec_lua('return vim.loop.fs_stat(...) ~= nil', old) + local exists = exec_lua('return vim.uv.fs_stat(...) ~= nil', old) eq(false, exists) - exists = exec_lua('return vim.loop.fs_stat(...) ~= nil', new) + exists = exec_lua('return vim.uv.fs_stat(...) ~= nil', new) eq(true, exists) os.remove(new) end) @@ -2266,9 +2266,9 @@ describe('LSP', function() return vim.fn.bufloaded(oldbufnr) ]], old_dir, new_dir, pathsep) eq(0, lines) - eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', old_dir)) - eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new_dir)) - eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new_dir .. pathsep .. file)) + eq(false, exec_lua('return vim.uv.fs_stat(...) ~= nil', old_dir)) + eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', new_dir)) + eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', new_dir .. pathsep .. file)) eq('Test content', read_file(new_dir .. pathsep .. file)) os.remove(new_dir) @@ -2286,7 +2286,7 @@ describe('LSP', function() vim.lsp.util.rename(old, new, { ignoreIfExists = true }) ]], old, new) - eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', old)) + eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', old)) eq('New file', read_file(new)) exec_lua([[ @@ -2296,7 +2296,7 @@ describe('LSP', function() vim.lsp.util.rename(old, new, { overwrite = false }) ]], old, new) - eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', old)) + eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', old)) eq('New file', read_file(new)) end) it('Does override target if overwrite is true', function() @@ -2311,8 +2311,8 @@ describe('LSP', function() vim.lsp.util.rename(old, new, { overwrite = true }) ]], old, new) - eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', old)) - eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new)) + eq(false, exec_lua('return vim.uv.fs_stat(...) ~= nil', old)) + eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', new)) eq('Old file\n', read_file(new)) end) end) @@ -3697,7 +3697,7 @@ describe('LSP', function() describe('cmd', function() it('can connect to lsp server via rpc.connect', function() local result = exec_lua [[ - local uv = vim.loop + local uv = vim.uv local server = uv.new_tcp() local init = nil server:bind('127.0.0.1', 0) @@ -3725,7 +3725,7 @@ describe('LSP', function() describe('handlers', function() it('handler can return false as response', function() local result = exec_lua [[ - local uv = vim.loop + local uv = vim.uv local server = uv.new_tcp() local messages = {} local responses = {} -- cgit From 5282d3299c9b1b07f3e02a9014bc2632cf3b4fed Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Mon, 5 Jun 2023 01:45:01 +0200 Subject: fix(lsp): restore marks after apply_text_edits() #14630 PROBLEM: Whenever any text edits are applied to the buffer, the `marks` part of those lines will be lost. This is mostly problematic for code formatters that format the whole buffer like `prettier`, `luafmt`, ... When doing atomic changes inside a vim doc, vim keeps track of those changes and can update the positions of marks accordingly, but in this case we have a whole doc that changed. There's no simple way to update the positions of all marks from the previous document state to the new document state. SOLUTION: * save marks right before `nvim_buf_set_lines` is called inside `apply_text_edits` * check if any marks were lost after doing `nvim_buf_set_lines` * restore those marks to the previous positions TEST CASE: * have a formatter enabled * open any file * create a couple of marks * indent the whole file to the right * save the file Before this change: all marks will be removed. After this change: they will be preserved. Fixes #14307 --- test/functional/plugin/lsp_spec.lua | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 26fd2276fd..72a9bc45a2 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1679,6 +1679,54 @@ describe('LSP', function() 'foobar'; }, buf_lines(1)) end) + it('it restores marks', function() + local edits = { + make_edit(1, 0, 2, 5, "foobar"); + make_edit(4, 0, 5, 0, "barfoo"); + } + eq(true, exec_lua('return vim.api.nvim_buf_set_mark(1, "a", 2, 1, {})')) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") + eq({ + 'First line of text'; + 'foobar line of text'; + 'Fourth line of text'; + 'barfoo'; + }, buf_lines(1)) + local mark = exec_lua('return vim.api.nvim_buf_get_mark(1, "a")') + eq({ 2, 1 }, mark) + end) + + it('it restores marks to last valid col', function() + local edits = { + make_edit(1, 0, 2, 15, "foobar"); + make_edit(4, 0, 5, 0, "barfoo"); + } + eq(true, exec_lua('return vim.api.nvim_buf_set_mark(1, "a", 2, 10, {})')) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") + eq({ + 'First line of text'; + 'foobarext'; + 'Fourth line of text'; + 'barfoo'; + }, buf_lines(1)) + local mark = exec_lua('return vim.api.nvim_buf_get_mark(1, "a")') + eq({ 2, 9 }, mark) + end) + + it('it restores marks to last valid line', function() + local edits = { + make_edit(1, 0, 4, 5, "foobar"); + make_edit(4, 0, 5, 0, "barfoo"); + } + eq(true, exec_lua('return vim.api.nvim_buf_set_mark(1, "a", 4, 1, {})')) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") + eq({ + 'First line of text'; + 'foobaro'; + }, buf_lines(1)) + local mark = exec_lua('return vim.api.nvim_buf_get_mark(1, "a")') + eq({ 2, 1 }, mark) + end) describe('cursor position', function() it('don\'t fix the cursor if the range contains the cursor', function() -- cgit From 3c6d971e5488dc75b7db07c14d01f87827f28a67 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 5 Jun 2023 13:17:38 +0800 Subject: fix(lsp): set extra info only when it has a value (#23868) --- test/functional/plugin/lsp_spec.lua | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 72a9bc45a2..85138417ff 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2213,8 +2213,8 @@ describe('LSP', function() local prefix = 'foo' local completion_list = { -- resolves into label - { label='foobar', sortText="a" }, - { label='foobar', sortText="b", textEdit={} }, + { label = 'foobar', sortText = 'a', documentation = 'documentation' }, + { label = 'foobar', sortText = 'b', documentation = { value = 'documentation' }, textEdit = {} }, -- resolves into insertText { label='foocar', sortText="c", insertText='foobar' }, { label='foocar', sortText="d", insertText='foobar', textEdit={} }, @@ -2233,17 +2233,17 @@ describe('LSP', function() } local completion_list_items = {items=completion_list} local expected = { - { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar', sortText="a" } } } } }, - { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', sortText="b", textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="c", insertText='foobar' } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="d", insertText='foobar', textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="f", textEdit={newText='foobar'} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar()', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } }, + { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = 'documentation', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar', sortText="a", documentation = 'documentation' } } } } }, + { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = 'documentation', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', sortText="b", textEdit={},documentation = { value = 'documentation' } } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="c", insertText='foobar' } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="d", insertText='foobar', textEdit={} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} } } } } }, + { abbr = 'foocar', dup = 1, empty = 1, icase = 1, 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, 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, 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, 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, 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, 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)) @@ -4440,3 +4440,4 @@ describe('LSP', function() end) end) end) + -- cgit From 643546b82b4bc0c29ca869f81af868a019723d83 Mon Sep 17 00:00:00 2001 From: Chinmay Dalal Date: Sun, 11 Jun 2023 15:23:37 +0530 Subject: feat(lsp): add handlers for inlay hints (#23736) initial support; public API left for a follow-up PR --- test/functional/plugin/lsp_spec.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 85138417ff..2f4a703c74 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3230,9 +3230,10 @@ describe('LSP', function() eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) - -- Don't compare & assert params, they're not relevant for the testcase + -- Don't compare & assert params and version, they're not relevant for the testcase -- This allows us to be lazy and avoid declaring them ctx.params = nil + ctx.version = nil eq(table.remove(test.expected_handlers), {err, result, ctx}, "expected handler") if ctx.method == 'start' then @@ -3314,6 +3315,7 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) ctx.params = nil -- don't compare in assert + ctx.version = nil eq(table.remove(expected_handlers), { err, result, ctx }) if ctx.method == 'start' then exec_lua([[ -- cgit From c07dceba335c56c9a356395ad0d1e5a14d416752 Mon Sep 17 00:00:00 2001 From: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> Date: Sat, 17 Jun 2023 08:01:31 +0200 Subject: fix(lsp): allow Lua pattern chars in code action filter (#24041) Previously, filtering code actions with the "only" option failed if the code action kind contained special Lua pattern chars such as "-" (e.g. the ocaml language server supports a "type-annotate" code action). Solution: use string comparison instead of string.find --- test/functional/plugin/lsp_spec.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 2f4a703c74..e9993eee2a 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3357,22 +3357,22 @@ describe('LSP', function() vim.lsp.commands['executed_preferred'] = function() end end - vim.lsp.commands['quickfix_command'] = function(cmd) - vim.lsp.commands['executed_quickfix'] = function() + vim.lsp.commands['type_annotate_command'] = function(cmd) + vim.lsp.commands['executed_type_annotate'] = function() end end local bufnr = vim.api.nvim_get_current_buf() vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) vim.lsp.buf.code_action({ filter = function(a) return a.isPreferred end, apply = true, }) vim.lsp.buf.code_action({ - -- expect to be returned actions 'quickfix' and 'quickfix.foo' - context = { only = {'quickfix'}, }, + -- expect to be returned actions 'type-annotate' and 'type-annotate.foo' + context = { only = { 'type-annotate' }, }, apply = true, filter = function(a) - if a.kind == 'quickfix.foo' then - vim.lsp.commands['filtered_quickfix_foo'] = function() end + if a.kind == 'type-annotate.foo' then + vim.lsp.commands['filtered_type_annotate_foo'] = function() end return false - elseif a.kind == 'quickfix' then + elseif a.kind == 'type-annotate' then return true else assert(nil, 'unreachable') @@ -3382,8 +3382,8 @@ describe('LSP', function() ]]) elseif ctx.method == 'shutdown' then eq('function', exec_lua[[return type(vim.lsp.commands['executed_preferred'])]]) - eq('function', exec_lua[[return type(vim.lsp.commands['filtered_quickfix_foo'])]]) - eq('function', exec_lua[[return type(vim.lsp.commands['executed_quickfix'])]]) + eq('function', exec_lua[[return type(vim.lsp.commands['filtered_type_annotate_foo'])]]) + eq('function', exec_lua[[return type(vim.lsp.commands['executed_type_annotate'])]]) client.stop() end end -- cgit From 3bf887f6e08fa272679187340ca483809275b20a Mon Sep 17 00:00:00 2001 From: Sooryakiran Ponnath Date: Tue, 20 Jun 2023 15:17:13 -0400 Subject: fix(lsp): always return boolean in lsp.buf_client_attach (#24077) Co-authored-by: Mathias Fussenegger --- test/functional/plugin/lsp_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index e9993eee2a..8eced4bfb5 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1070,7 +1070,7 @@ describe('LSP', function() eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) exec_lua [[ - assert(not lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Shouldn't attach twice") + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Already attached, returns true") ]] end; on_exit = function(code, signal) -- cgit From 33e1a8cd7042816a064c0d2bf32b6570d7e88b79 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 14 Jul 2023 18:47:18 +0200 Subject: feat(lsp): map K to hover by default #24331 Related: https://github.com/neovim/neovim/issues/24252 --- test/functional/plugin/lsp_spec.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 8eced4bfb5..38176c8749 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -365,6 +365,14 @@ describe('LSP', function() eq('v:lua.vim.lsp.tagfunc', get_buf_option("tagfunc")) eq('v:lua.vim.lsp.omnifunc', get_buf_option("omnifunc")) eq('v:lua.vim.lsp.formatexpr()', get_buf_option("formatexpr")) + eq('', get_buf_option("keywordprg")) + eq(true, exec_lua[[ + local keymap + vim.api.nvim_buf_call(BUFFER, function() + keymap = vim.fn.maparg("K", "n", false, true) + end) + return keymap.callback == vim.lsp.buf.hover + ]]) client.stop() end end; @@ -372,6 +380,13 @@ describe('LSP', function() eq('', get_buf_option("tagfunc")) eq('', get_buf_option("omnifunc")) eq('', get_buf_option("formatexpr")) + eq('', exec_lua[[ + local keymap + vim.api.nvim_buf_call(BUFFER, function() + keymap = vim.fn.maparg("K", "n", false, false) + end) + return keymap + ]]) end; } end) -- cgit From 1b9ccd38a12f8fdbdff51ef0b3ff363540f745ec Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Mon, 17 Jul 2023 18:27:16 +0200 Subject: feat(lsp)!: rename vim.lsp.get_active_clients to get_clients (#24113) --- test/functional/plugin/lsp_spec.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 38176c8749..9a777e2742 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -82,7 +82,7 @@ describe('LSP', function() describe('server_name specified', function() it('start_client(), stop_client()', function() retry(nil, 4000, function() - eq(1, exec_lua('return #lsp.get_active_clients()')) + eq(1, exec_lua('return #lsp.get_clients()')) end) eq(2, exec_lua([[ TEST_CLIENT2 = test__start_client() @@ -93,20 +93,20 @@ describe('LSP', function() return TEST_CLIENT3 ]])) retry(nil, 4000, function() - eq(3, exec_lua('return #lsp.get_active_clients()')) + eq(3, exec_lua('return #lsp.get_clients()')) end) eq(false, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil')) eq(false, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1).is_stopped()')) exec_lua('return lsp.get_client_by_id(TEST_CLIENT1).stop()') retry(nil, 4000, function() - eq(2, exec_lua('return #lsp.get_active_clients()')) + eq(2, exec_lua('return #lsp.get_clients()')) end) eq(true, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil')) exec_lua('lsp.stop_client({TEST_CLIENT2, TEST_CLIENT3})') retry(nil, 4000, function() - eq(0, exec_lua('return #lsp.get_active_clients()')) + eq(0, exec_lua('return #lsp.get_clients()')) end) end) @@ -116,12 +116,12 @@ describe('LSP', function() TEST_CLIENT3 = test__start_client() ]]) retry(nil, 4000, function() - eq(3, exec_lua('return #lsp.get_active_clients()')) + eq(3, exec_lua('return #lsp.get_clients()')) end) -- Stop all clients. - exec_lua('lsp.stop_client(lsp.get_active_clients())') + exec_lua('lsp.stop_client(lsp.get_clients())') retry(nil, 4000, function() - eq(0, exec_lua('return #lsp.get_active_clients()')) + eq(0, exec_lua('return #lsp.get_clients()')) end) end) end) @@ -151,7 +151,7 @@ describe('LSP', function() describe('basic_init test', function() after_each(function() stop() - exec_lua("lsp.stop_client(lsp.get_active_clients(), true)") + exec_lua("lsp.stop_client(lsp.get_clients(), true)") exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") end) -- cgit From 20c331915f4e317c615c7cfea469a9baedd2e4f7 Mon Sep 17 00:00:00 2001 From: Christoph Hasse Date: Tue, 25 Jul 2023 08:40:13 -0400 Subject: fix(lsp): SignatureHelp docstring is not escaped #16702 Problem: Nvim LSP client always treats signature.documentation as markdown, even if the server returns a plain string. Per https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#signatureInformation in a SignatureInformation response, the documentation field can be either "string" or "MarkupContent". Solution: If signature.documentation is a string, treat it as "plaintext". Closes #16563 --- test/functional/plugin/lsp_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 9a777e2742..7e34946d95 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3006,7 +3006,7 @@ describe('LSP', function() activeSignature = -1, signatures = { { - documentation = "", + documentation = "some doc", label = "TestEntity.TestEntity()", parameters = {} }, @@ -3014,7 +3014,7 @@ describe('LSP', function() } return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','}) ]] - local expected = {'```cs', 'TestEntity.TestEntity()', '```', ''} + local expected = {'```cs', 'TestEntity.TestEntity()', '```', '', 'some doc', ''} eq(expected, result) end) end) -- cgit From cc87dda31a5b5637ade7ddcfe5199f2df5fd47df Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Fri, 4 Aug 2023 07:10:54 +0100 Subject: fix(lsp): do not assume client capability exists in watchfiles check (#24550) PR #23689 assumes `client.config.capabilities.workspace.didChangeWatchedFiles` exists when checking `dynamicRegistration`, but thats's true only if it was passed to `vim.lsp.start{_client}`. This caused #23806 (still an issue in v0.9.1; needs manual backport), but #23681 fixed it by defaulting `config.capabilities` to `make_client_capabilities` if not passed to `vim.lsp.start{_client}`. However, the bug resurfaces on HEAD if you provide a non-nil `capabilities` to `vim.lsp.start{_client}` with missing fields (e.g: not made via `make_client_capabilities`). From what I see, the spec says such missing fields should be interpreted as an absence of the capability (including those indicated by missing sub-fields): https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#clientCapabilities Also, suggest `vim.empty_dict()` for an empty dict in `:h vim.lsp.start_client()` (`{[vim.type_idx]=vim.types.dictionary}` no longer works anyway, probably since the cjson switch). --- test/functional/plugin/lsp_spec.lua | 99 ++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 39 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 7e34946d95..6223c6b8d8 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -4402,58 +4402,79 @@ describe('LSP', function() it("ignores registrations by servers when the client doesn't advertise support", function() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server() - local client_id = vim.lsp.start({ - name = 'watchfiles-test', - cmd = server.cmd, - root_dir = 'some_dir', - capabilities = { - workspace = { - didChangeWatchedFiles = { - dynamicRegistration = false, - }, - }, - }, - }) - - local watching = false + exec_lua([[ + server = _create_server() require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) -- Since the registration is ignored, this should not execute and `watching` should stay false watching = true return function() end end + ]]) - vim.lsp.handlers['client/registerCapability'](nil, { - registrations = { - { - id = 'watchfiles-test-kind', - method = 'workspace/didChangeWatchedFiles', - registerOptions = { - watchers = { - { - globPattern = '**/*', + local function check_registered(capabilities) + return exec_lua([[ + watching = false + local client_id = vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = 'some_dir', + capabilities = ..., + }, { + reuse_client = function() return false end, + }) + + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-kind', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/*', + }, }, }, }, }, - }, - }, { client_id = client_id }) - - -- Ensure no errors occur when unregistering something that was never really registered. - vim.lsp.handlers['client/unregisterCapability'](nil, { - unregisterations = { - { - id = 'watchfiles-test-kind', - method = 'workspace/didChangeWatchedFiles', + }, { client_id = client_id }) + + -- Ensure no errors occur when unregistering something that was never really registered. + vim.lsp.handlers['client/unregisterCapability'](nil, { + unregisterations = { + { + id = 'watchfiles-test-kind', + method = 'workspace/didChangeWatchedFiles', + }, }, - }, - }, { client_id = client_id }) + }, { client_id = client_id }) - return watching - ]]) + vim.lsp.stop_client(client_id, true) + return watching + ]], capabilities) + end - eq(false, result) + eq(true, 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({ + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = false, + }, + }, + })) + eq(true, check_registered({ + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, + }, + })) end) end) end) -- cgit From 131a1ee82d15ce9d1356a46117c9a1651947d4b8 Mon Sep 17 00:00:00 2001 From: Tom Praschan <13141438+tom-anders@users.noreply.github.com> Date: Thu, 7 Sep 2023 10:12:02 +0200 Subject: feat(lsp): add original LSP Location as item's user_data in locations_to_items (#23743) --- test/functional/plugin/lsp_spec.lua | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 6223c6b8d8..3eb89b4556 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2387,7 +2387,14 @@ describe('LSP', function() filename = '/fake/uri', lnum = 1, col = 3, - text = 'testing' + text = 'testing', + user_data = { + uri = 'file:///fake/uri', + range = { + start = { line = 0, character = 2 }, + ['end'] = { line = 0, character = 3 }, + } + } }, } local actual = exec_lua [[ @@ -2413,7 +2420,18 @@ describe('LSP', function() filename = '/fake/uri', lnum = 1, col = 3, - text = 'testing' + text = 'testing', + user_data = { + targetUri = "file:///fake/uri", + targetRange = { + start = { line = 0, character = 2 }, + ['end'] = { line = 0, character = 3 }, + }, + targetSelectionRange = { + start = { line = 0, character = 2 }, + ['end'] = { line = 0, character = 3 }, + } + } }, } local actual = exec_lua [[ -- cgit From d22172f36bbe147f3aa6b76a1c43ae445f481c2e Mon Sep 17 00:00:00 2001 From: Sergey Slipchenko Date: Mon, 11 Sep 2023 08:16:03 +0400 Subject: fix(api): more intuitive cursor updates in nvim_buf_set_text Fixes #22526 --- test/functional/plugin/lsp_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 3eb89b4556..e0a8badb67 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1787,7 +1787,7 @@ describe('LSP', function() eq({ 'First line of text'; }, buf_lines(1)) - eq({ 1, 6 }, funcs.nvim_win_get_cursor(0)) + eq({ 1, 17 }, funcs.nvim_win_get_cursor(0)) end) it('fix the cursor row', function() -- cgit From cfd4a9dfaf5fd900264a946ca33c4a4f26f66a49 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Tue, 12 Sep 2023 20:51:21 -0700 Subject: feat(lsp): use treesitter for stylize markdown --- test/functional/plugin/lsp_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index e0a8badb67..7e30af5058 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3032,7 +3032,7 @@ describe('LSP', function() } return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','}) ]] - local expected = {'```cs', 'TestEntity.TestEntity()', '```', '', 'some doc', ''} + local expected = {'```cs', 'TestEntity.TestEntity()', '```', 'some doc'} eq(expected, result) end) end) -- cgit From 345bd91db28ecfc4deb308f4971253b534f82d49 Mon Sep 17 00:00:00 2001 From: Sergey Slipchenko Date: Thu, 21 Sep 2023 14:06:40 +0400 Subject: fix(lsp): handle absence of a trailing newline #25194 Fixes #24339 rust-analyzer sends "Invalid offset" error in such cases. Some other servers handle it specially. LSP spec mentions that "A range is comparable to a selection in an editor". Most editors don't handle trailing newlines the same way Neovim/Vim does, it's clearly visible if it's present or not. With that in mind it's understandable why sending end position as simply the start of the line after the last one is considered invalid in such cases. --- test/functional/plugin/lsp_spec.lua | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 7e30af5058..155c9ad96c 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1244,6 +1244,67 @@ describe('LSP', function() } end) + it('should send correct range for inlay hints with noeol', function() + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, { + method="textDocument/inlayHint", + params = { + textDocument = { + uri = 'file://', + }, + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = 1, character = 3 }, + } + }, + bufnr=2, + client_id=1, + }}; + {NIL, {}, {method="start", client_id=1}}; + } + local client + test_rpc_server { + test_name = "inlay_hint"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + vim.bo[BUFFER].eol = false + ]] + end; + on_init = function(_client) + client = _client + eq(true, client.supports_method('textDocument/inlayHint')) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") + eq(0, signal, "exit signal") + end; + on_handler = function(err, result, ctx) + if ctx.method == 'start' then + exec_lua [[ + vim.lsp.inlay_hint(BUFFER, true) + ]] + end + if ctx.method == 'textDocument/inlayHint' then + client.notify('finish') + end + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then + client.stop() + end + end; + } + end) + it('should check the body and didChange incremental', function() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; -- cgit From 4a09c178a19097c295521892c889f1f196fff100 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Mon, 2 Oct 2023 22:14:19 +0200 Subject: feat(lsp): fallback to code-action command on resolve failure (#25464) The haskell-language-server supports resolve only for a subset of code actions. For many code actions trying to resolve the `edit` property results in an error, but the unresolved action already contains a command that can be executed without issue. The protocol specification is unfortunately a bit vague about this, and what the haskell-language-server does seems to be valid. Example: newtype Dummy = Dummy Int instance Num Dummy where Triggering code actions on "Num Dummy" and choosing "Add placeholders for all missing methods" resulted in: -32601: No plugin enabled for SMethod_CodeActionResolve, potentially available: explicit-fields, importLens, hlint, overloaded-record-dot With this change it will insert the missing methods: instance Num Dummy where (+) = _ (-) = _ (*) = _ negate = _ abs = _ signum = _ fromInteger = _ --- test/functional/plugin/lsp_spec.lua | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 155c9ad96c..ef08860f10 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3483,6 +3483,50 @@ describe('LSP', function() end } end) + it("Fallback to command execution on resolve error", function() + clear() + exec_lua(create_server_definition) + local result = exec_lua([[ + local server = _create_server({ + capabilities = { + executeCommandProvider = { + commands = {"command:1"}, + }, + codeActionProvider = { + resolveProvider = true + } + }, + handlers = { + ["textDocument/codeAction"] = function() + return { + { + title = "Code Action 1", + command = { + title = "Command 1", + command = "command:1", + } + } + } + end, + ["codeAction/resolve"] = function() + return nil, "resolve failed" + end, + } + }) + + local client_id = vim.lsp.start({ + name = "dummy", + cmd = server.cmd, + }) + + vim.lsp.buf.code_action({ apply = true }) + vim.lsp.stop_client(client_id) + return server.messages + ]]) + eq("codeAction/resolve", result[4].method) + eq("workspace/executeCommand", result[5].method) + eq("command:1", result[5].params.command) + end) end) describe('vim.lsp.commands', function() it('Accepts only string keys', function() -- cgit From eb1f0e8fcca756a00d287e23bf87554e0e7f6dfd Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sun, 1 Oct 2023 09:54:04 -0700 Subject: feat(lsp)!: replace snippet parser by lpeg grammar --- test/functional/plugin/lsp_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index ef08860f10..73e05d8d11 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2301,7 +2301,7 @@ describe('LSP', function() { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} }, { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} }, -- nested snippet tokens - { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} }, + { label='foocar', sortText="i", insertText='foodar(${1:${2|typ1,typ2|}}) {$0\\}', insertTextFormat=2, textEdit={} }, -- braced tabstop { label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} }, -- plain text @@ -2317,7 +2317,7 @@ describe('LSP', function() { abbr = 'foocar', dup = 1, empty = 1, icase = 1, 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, 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, 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, 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, kind = 'Unknown', menu = '', word = 'foodar(typ1) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:${2|typ1,typ2|}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } }, { abbr = 'foocar', dup = 1, empty = 1, icase = 1, 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, kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } }, } -- cgit From 840e1864c2de2b4b192a4df1865b69093904b139 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 12 Oct 2023 15:39:39 +0800 Subject: fix(lsp): handle NUL bytes in popup text (#25612) Fix #25610 --- test/functional/plugin/lsp_spec.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 73e05d8d11..8a4a82fa38 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3069,6 +3069,18 @@ describe('LSP', function() it('calculates size correctly with wrapping', function() eq({15,5}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents,{width = 15, wrap_at = 14})} ]]) end) + + it('handles NUL bytes in text', function() + exec_lua([[ contents = { + '\000\001\002\003\004\005\006\007\008\009', + '\010\011\012\013\014\015\016\017\018\019', + '\020\021\022\023\024\025\026\027\028\029', + } ]]) + command('set list listchars=') + eq({20,3}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents)} ]]) + command('set display+=uhex') + eq({40,3}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents)} ]]) + end) end) describe('lsp.util.trim.trim_empty_lines', function() -- cgit From 195301c60969c7ce97b1ef3a3caaf4965da1abd5 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Sat, 21 Oct 2023 09:57:50 +0200 Subject: refactor(lsp): deprecate completion util methods Relates to https://github.com/neovim/neovim/issues/25272 --- test/functional/plugin/lsp_spec.lua | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 8a4a82fa38..3bd8db815d 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2769,18 +2769,6 @@ describe('LSP', function() end) end) - describe('lsp.util._get_completion_item_kind_name', function() - it('returns the name specified by protocol', function() - eq("Text", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1)")) - eq("TypeParameter", exec_lua("return vim.lsp.util._get_completion_item_kind_name(25)")) - end) - it('returns the name not specified by protocol', function() - eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(nil)")) - eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(vim.NIL)")) - eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1000)")) - end) - end) - describe('lsp.util._get_symbol_kind_name', function() it('returns the name specified by protocol', function() eq("File", exec_lua("return vim.lsp.util._get_symbol_kind_name(1)")) -- cgit From 5e5f5174e3faa862a9bc353aa7da41487911140b Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Sat, 21 Oct 2023 13:44:53 +0200 Subject: fix(lsp): fix off-by-one error for omnifunc word boundary Fixes https://github.com/neovim/neovim/issues/25177 I initially wanted to split this into a refactor commit to make it more testable, but it appears that already accidentally fixed the issue by normalizing lnum/col to 0-indexing --- test/functional/plugin/lsp_spec.lua | 47 ------------------------------------- 1 file changed, 47 deletions(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 3bd8db815d..d56e5b4afa 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2281,53 +2281,6 @@ describe('LSP', function() end) end) - describe('completion_list_to_complete_items', function() - -- Completion option precedence: - -- textEdit.newText > insertText > label - -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion - it('should choose right completion option', function () - local prefix = 'foo' - local completion_list = { - -- resolves into label - { label = 'foobar', sortText = 'a', documentation = 'documentation' }, - { label = 'foobar', sortText = 'b', documentation = { value = 'documentation' }, textEdit = {} }, - -- resolves into insertText - { label='foocar', sortText="c", insertText='foobar' }, - { label='foocar', sortText="d", insertText='foobar', textEdit={} }, - -- resolves into textEdit.newText - { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} }, - { label='foocar', sortText="f", textEdit={newText='foobar'} }, - -- real-world snippet text - { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} }, - { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} }, - -- nested snippet tokens - { label='foocar', sortText="i", insertText='foodar(${1:${2|typ1,typ2|}}) {$0\\}', insertTextFormat=2, textEdit={} }, - -- braced tabstop - { label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} }, - -- plain text - { label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} }, - } - local completion_list_items = {items=completion_list} - local expected = { - { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = 'documentation', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar', sortText="a", documentation = 'documentation' } } } } }, - { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = 'documentation', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', sortText="b", textEdit={},documentation = { value = 'documentation' } } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="c", insertText='foobar' } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="d", insertText='foobar', textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, 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, 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, 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, kind = 'Unknown', menu = '', word = 'foodar(typ1) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:${2|typ1,typ2|}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } }, - { abbr = 'foocar', dup = 1, empty = 1, icase = 1, 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, kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } }, - } - - eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix)) - eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list_items, prefix)) - eq({}, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], {}, prefix)) - end) - end) - describe('lsp.util.rename', function() local pathsep = helpers.get_pathsep() -- cgit From 448907f65d6709fa234d8366053e33311a01bdb9 Mon Sep 17 00:00:00 2001 From: LW Date: Sun, 12 Nov 2023 04:54:27 -0800 Subject: feat(lsp)!: vim.lsp.inlay_hint.get(), enable(), is_enabled() #25512 refactor!: `vim.lsp.inlay_hint()` -> `vim.lsp.inlay_hint.enable()` Problem: The LSP specification allows inlay hints to include tooltips, clickable label parts, and code actions; but Neovim provides no API to query for these. Solution: Add minimal viable extension point from which plugins can query for inlay hints in a range, in order to build functionality on top of. Possible Next Steps --- - Add `virt_text_idx` field to `vim.fn.getmousepos()` return value, for usage in mappings of ``, ``, etc --- test/functional/plugin/lsp_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index d56e5b4afa..bb8d775838 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1291,7 +1291,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) if ctx.method == 'start' then exec_lua [[ - vim.lsp.inlay_hint(BUFFER, true) + vim.lsp.inlay_hint.enable(BUFFER) ]] end if ctx.method == 'textDocument/inlayHint' then -- cgit From 7ca2d64e8bbfb73f33cf82a2f9c03808bfea3d95 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sun, 19 Nov 2023 18:37:49 +0100 Subject: test: skip failing watch file tests on freebsd (#26110) Quick fix as follow up to https://github.com/neovim/neovim/pull/26108 kqueue only reports events on a watched folder itself, not for files created or deleted within. So the approach the PR took doesn't work on FreeBSD. We'll either need to bring back polling for it, combine watching with manual file tracking, or disable LSP file watching on FreeBSD --- test/functional/plugin/lsp_spec.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'test/functional/plugin/lsp_spec.lua') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index bb8d775838..56d31a0e44 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -4026,6 +4026,7 @@ describe('LSP', function() describe('vim.lsp._watchfiles', function() it('sends notifications when files change', function() + skip(is_os('bsd'), "bsd only reports rename on folders if file inside change") local root_dir = helpers.tmpname() os.remove(root_dir) mkdir(root_dir) -- cgit