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/helpers.lua | 9 +++------ test/functional/plugin/lsp_spec.lua | 8 +++----- 2 files changed, 6 insertions(+), 11 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/helpers.lua b/test/functional/plugin/lsp/helpers.lua index 028ccb9e2c..caab174b4d 100644 --- a/test/functional/plugin/lsp/helpers.lua +++ b/test/functional/plugin/lsp/helpers.lua @@ -80,17 +80,14 @@ M.fake_lsp_logfile = 'Xtest-fake-lsp.log' local function fake_lsp_server_setup(test_name, timeout_ms, options, settings) exec_lua([=[ lsp = require('vim.lsp') - local test_name, fixture_filename, logfile, timeout, options, settings = ... + local test_name, fake_lsp_code, fake_lsp_logfile, timeout, options, settings = ... TEST_RPC_CLIENT_ID = lsp.start_client { cmd_env = { - NVIM_LOG_FILE = logfile; + NVIM_LOG_FILE = fake_lsp_logfile; NVIM_LUA_NOTRACK = "1"; }; cmd = { - vim.v.progpath, '-Es', '-u', 'NONE', '--headless', - "-c", string.format("lua TEST_NAME = %q", test_name), - "-c", string.format("lua TIMEOUT = %d", timeout), - "-c", "luafile "..fixture_filename, + vim.v.progpath, '-l', fake_lsp_code, test_name, tostring(timeout), }; handlers = setmetatable({}, { __index = function(t, method) 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 9ce44a750c2a65082962effe6ce4d185b7698d73 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 1 Feb 2023 17:21:42 +0000 Subject: fix(man): use italics for `_` (#22086) fix(man): use italics for _ Even though underline is strictly what this should be. _ was used by nroff to indicate italics which wasn't possible on old typewriters so underline was used. Modern terminals now support italics so lets use that now. See: - https://unix.stackexchange.com/questions/274658/purpose-of-ascii-text-with-overstriking-file-format/274795#274795 - https://cmd.inp.nsk.su/old/cmd2/manuals/unix/UNIX_Unleashed/ch08.htm --- test/functional/plugin/man_spec.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index c6c7d2b03d..58da059be6 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -59,7 +59,7 @@ describe(':Man', function() screen:expect([[ ^this {b:is} {b:a} test | - with {u:overstruck} text | + with {i:overstruck} text | {eob:~ }| {eob:~ }| | @@ -98,7 +98,7 @@ describe(':Man', function() screen:expect([[ ^this {b:is} {b:あ} test | - with {u:överstrũck} te{i:xt¶} | + with {i:överstrũck} te{i:xt¶} | {eob:~ }| {eob:~ }| | @@ -115,7 +115,7 @@ describe(':Man', function() screen:expect([[ {b:^_begins} | {b:mid_dle} | - {u:mid_dle} | + {i:mid_dle} | {eob:~ }| | ]]) -- 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') 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/watchfiles_spec.lua | 173 ++++++++++ test/functional/plugin/lsp_spec.lua | 421 +++++++++++++++++++++++++ 2 files changed, 594 insertions(+) create mode 100644 test/functional/plugin/lsp/watchfiles_spec.lua (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/watchfiles_spec.lua b/test/functional/plugin/lsp/watchfiles_spec.lua new file mode 100644 index 0000000000..c5d6803a7f --- /dev/null +++ b/test/functional/plugin/lsp/watchfiles_spec.lua @@ -0,0 +1,173 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local has_err = require('luassert').has.errors + +describe('vim.lsp._watchfiles', function() + before_each(helpers.clear) + after_each(helpers.clear) + + local match = function(...) + return exec_lua('return require("vim.lsp._watchfiles")._match(...)', ...) + end + + describe('glob matching', function() + it('should match literal strings', function() + eq(true, match('', '')) + eq(false, match('', 'a')) + eq(true, match('a', 'a')) + eq(true, match('abc', 'abc')) + eq(false, match('abc', 'abcdef')) + eq(false, match('abc', 'a')) + eq(false, match('a', 'b')) + eq(false, match('.', 'a')) + eq(true, match('$', '$')) + eq(false, match('dir/subdir', 'dir/subdir/file')) + end) + + it('should match * wildcards', function() + -- eq(false, match('*', '')) -- TODO: this fails + eq(true, match('*', 'a')) + eq(false, match('*', '/a')) + eq(false, match('*', 'a/')) + eq(true, match('*', 'aaa')) + eq(true, match('*.txt', 'file.txt')) + eq(false, match('*.txt', 'file.txtxt')) + eq(false, match('*.txt', 'dir/file.txt')) + eq(false, match('*.txt', '/dir/file.txt')) + eq(false, match('*.txt', 'C:/dir/file.txt')) + eq(false, match('*.dir', 'test.dir/file')) + eq(true, match('file.*', 'file.txt')) + eq(false, match('file.*', 'not-file.txt')) + eq(false, match('dir/*.txt', 'file.txt')) + eq(true, match('dir/*.txt', 'dir/file.txt')) + eq(false, match('dir/*.txt', 'dir/subdir/file.txt')) + end) + + it('should match ? wildcards', function() + eq(false, match('?', '')) + eq(true, match('?', 'a')) + eq(false, match('??', 'a')) + eq(false, match('?', 'ab')) + eq(true, match('??', 'ab')) + eq(true, match('a?c', 'abc')) + eq(false, match('a?c', 'a/c')) + end) + + it('should match ** wildcards', function() + eq(true, match('**', '')) + eq(true, match('**', 'a')) + eq(true, match('**', 'a/')) + eq(true, match('**', '/a')) + eq(true, match('**', 'C:/a')) + eq(true, match('**', 'a/a')) + eq(true, match('**', 'a/a/a')) + eq(false, match('a**', '')) + eq(true, match('a**', 'a')) + eq(true, match('a**', 'abcd')) + eq(false, match('a**', 'ba')) + eq(false, match('a**', 'a/b')) + eq(false, match('**a', '')) + eq(true, match('**a', 'a')) + eq(true, match('**a', 'dcba')) + eq(false, match('**a', 'ab')) + eq(false, match('**a', 'b/a')) + eq(false, match('a/**', '')) + eq(true, match('a/**', 'a')) + eq(true, match('a/**', 'a/b')) + eq(false, match('a/**', 'b/a')) + eq(false, match('a/**', '/a')) + eq(false, match('**/a', '')) + eq(true, match('**/a', 'a')) + eq(false, match('**/a', 'a/b')) + eq(true, match('**/a', '/a')) + eq(false, match('a/**/c', 'a')) + eq(false, match('a/**/c', 'c')) + eq(true, match('a/**/c', 'a/c')) + eq(true, match('a/**/c', 'a/b/c')) + eq(true, match('a/**/c', 'a/b/b/c')) + eq(true, match('**/a/**', 'a')) + eq(true, match('**/a/**', '/dir/a')) + eq(true, match('**/a/**', 'a/dir')) + eq(true, match('**/a/**', 'dir/a/dir')) + eq(true, match('**/a/**', '/a/dir')) + eq(true, match('**/a/**', 'C:/a/dir')) + -- eq(false, match('**/a/**', 'a.txt')) -- TODO: this fails + end) + + it('should match {} groups', function() + eq(false, match('{}', '')) + eq(true, match('{,}', '')) + eq(false, match('{}', 'a')) + eq(true, match('{a}', 'a')) + eq(false, match('{a}', 'aa')) + eq(false, match('{a}', 'ab')) + eq(false, match('{ab}', 'a')) + eq(true, match('{ab}', 'ab')) + eq(true, match('{a,b}', 'a')) + eq(true, match('{a,b}', 'b')) + eq(false, match('{a,b}', 'ab')) + eq(true, match('{ab,cd}', 'ab')) + eq(false, match('{ab,cd}', 'a')) + eq(true, match('{ab,cd}', 'cd')) + eq(true, match('{a,b,c}', 'c')) + eq(false, match('{a,{b,c}}', 'c')) -- {} can't nest + end) + + it('should match [] groups', function() + eq(true, match('[]', '')) + eq(false, match('[a-z]', '')) + eq(true, match('[a-z]', 'a')) + eq(false, match('[a-z]', 'ab')) + eq(true, match('[a-z]', 'z')) + eq(true, match('[a-z]', 'j')) + eq(false, match('[a-f]', 'j')) + eq(false, match('[a-z]', '`')) -- 'a' - 1 + eq(false, match('[a-z]', '{')) -- 'z' + 1 + eq(false, match('[a-z]', 'A')) + eq(false, match('[a-z]', '5')) + eq(true, match('[A-Z]', 'A')) + eq(true, match('[A-Z]', 'Z')) + eq(true, match('[A-Z]', 'J')) + eq(false, match('[A-Z]', '@')) -- 'A' - 1 + eq(false, match('[A-Z]', '[')) -- 'Z' + 1 + eq(false, match('[A-Z]', 'a')) + eq(false, match('[A-Z]', '5')) + eq(true, match('[a-zA-Z0-9]', 'z')) + eq(true, match('[a-zA-Z0-9]', 'Z')) + eq(true, match('[a-zA-Z0-9]', '9')) + eq(false, match('[a-zA-Z0-9]', '&')) + end) + + it('should match [!...] groups', function() + has_err(function() match('[!]', '') end) -- not a valid pattern + eq(false, match('[!a-z]', '')) + eq(false, match('[!a-z]', 'a')) + eq(false, match('[!a-z]', 'z')) + eq(false, match('[!a-z]', 'j')) + eq(true, match('[!a-f]', 'j')) + eq(false, match('[!a-f]', 'jj')) + eq(true, match('[!a-z]', '`')) -- 'a' - 1 + eq(true, match('[!a-z]', '{')) -- 'z' + 1 + eq(false, match('[!a-zA-Z0-9]', 'a')) + eq(false, match('[!a-zA-Z0-9]', 'A')) + eq(false, match('[!a-zA-Z0-9]', '0')) + eq(true, match('[!a-zA-Z0-9]', '!')) + end) + + it('should match complex patterns', function() + eq(false, match('**/*.{c,h}', '')) + eq(false, match('**/*.{c,h}', 'c')) + eq(true, match('**/*.{c,h}', 'file.c')) + eq(true, match('**/*.{c,h}', 'file.h')) + eq(true, match('**/*.{c,h}', '/file.c')) + eq(true, match('**/*.{c,h}', 'dir/subdir/file.c')) + eq(true, match('**/*.{c,h}', 'dir/subdir/file.h')) + + eq(true, match('{[0-9],[a-z]}', '0')) + eq(true, match('{[0-9],[a-z]}', 'a')) + eq(false, match('{[0-9],[a-z]}', 'A')) + end) + end) +end) 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/watchfiles_spec.lua | 173 ---------- test/functional/plugin/lsp_spec.lua | 421 ------------------------- 2 files changed, 594 deletions(-) delete mode 100644 test/functional/plugin/lsp/watchfiles_spec.lua (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/watchfiles_spec.lua b/test/functional/plugin/lsp/watchfiles_spec.lua deleted file mode 100644 index c5d6803a7f..0000000000 --- a/test/functional/plugin/lsp/watchfiles_spec.lua +++ /dev/null @@ -1,173 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) - -local eq = helpers.eq -local exec_lua = helpers.exec_lua -local has_err = require('luassert').has.errors - -describe('vim.lsp._watchfiles', function() - before_each(helpers.clear) - after_each(helpers.clear) - - local match = function(...) - return exec_lua('return require("vim.lsp._watchfiles")._match(...)', ...) - end - - describe('glob matching', function() - it('should match literal strings', function() - eq(true, match('', '')) - eq(false, match('', 'a')) - eq(true, match('a', 'a')) - eq(true, match('abc', 'abc')) - eq(false, match('abc', 'abcdef')) - eq(false, match('abc', 'a')) - eq(false, match('a', 'b')) - eq(false, match('.', 'a')) - eq(true, match('$', '$')) - eq(false, match('dir/subdir', 'dir/subdir/file')) - end) - - it('should match * wildcards', function() - -- eq(false, match('*', '')) -- TODO: this fails - eq(true, match('*', 'a')) - eq(false, match('*', '/a')) - eq(false, match('*', 'a/')) - eq(true, match('*', 'aaa')) - eq(true, match('*.txt', 'file.txt')) - eq(false, match('*.txt', 'file.txtxt')) - eq(false, match('*.txt', 'dir/file.txt')) - eq(false, match('*.txt', '/dir/file.txt')) - eq(false, match('*.txt', 'C:/dir/file.txt')) - eq(false, match('*.dir', 'test.dir/file')) - eq(true, match('file.*', 'file.txt')) - eq(false, match('file.*', 'not-file.txt')) - eq(false, match('dir/*.txt', 'file.txt')) - eq(true, match('dir/*.txt', 'dir/file.txt')) - eq(false, match('dir/*.txt', 'dir/subdir/file.txt')) - end) - - it('should match ? wildcards', function() - eq(false, match('?', '')) - eq(true, match('?', 'a')) - eq(false, match('??', 'a')) - eq(false, match('?', 'ab')) - eq(true, match('??', 'ab')) - eq(true, match('a?c', 'abc')) - eq(false, match('a?c', 'a/c')) - end) - - it('should match ** wildcards', function() - eq(true, match('**', '')) - eq(true, match('**', 'a')) - eq(true, match('**', 'a/')) - eq(true, match('**', '/a')) - eq(true, match('**', 'C:/a')) - eq(true, match('**', 'a/a')) - eq(true, match('**', 'a/a/a')) - eq(false, match('a**', '')) - eq(true, match('a**', 'a')) - eq(true, match('a**', 'abcd')) - eq(false, match('a**', 'ba')) - eq(false, match('a**', 'a/b')) - eq(false, match('**a', '')) - eq(true, match('**a', 'a')) - eq(true, match('**a', 'dcba')) - eq(false, match('**a', 'ab')) - eq(false, match('**a', 'b/a')) - eq(false, match('a/**', '')) - eq(true, match('a/**', 'a')) - eq(true, match('a/**', 'a/b')) - eq(false, match('a/**', 'b/a')) - eq(false, match('a/**', '/a')) - eq(false, match('**/a', '')) - eq(true, match('**/a', 'a')) - eq(false, match('**/a', 'a/b')) - eq(true, match('**/a', '/a')) - eq(false, match('a/**/c', 'a')) - eq(false, match('a/**/c', 'c')) - eq(true, match('a/**/c', 'a/c')) - eq(true, match('a/**/c', 'a/b/c')) - eq(true, match('a/**/c', 'a/b/b/c')) - eq(true, match('**/a/**', 'a')) - eq(true, match('**/a/**', '/dir/a')) - eq(true, match('**/a/**', 'a/dir')) - eq(true, match('**/a/**', 'dir/a/dir')) - eq(true, match('**/a/**', '/a/dir')) - eq(true, match('**/a/**', 'C:/a/dir')) - -- eq(false, match('**/a/**', 'a.txt')) -- TODO: this fails - end) - - it('should match {} groups', function() - eq(false, match('{}', '')) - eq(true, match('{,}', '')) - eq(false, match('{}', 'a')) - eq(true, match('{a}', 'a')) - eq(false, match('{a}', 'aa')) - eq(false, match('{a}', 'ab')) - eq(false, match('{ab}', 'a')) - eq(true, match('{ab}', 'ab')) - eq(true, match('{a,b}', 'a')) - eq(true, match('{a,b}', 'b')) - eq(false, match('{a,b}', 'ab')) - eq(true, match('{ab,cd}', 'ab')) - eq(false, match('{ab,cd}', 'a')) - eq(true, match('{ab,cd}', 'cd')) - eq(true, match('{a,b,c}', 'c')) - eq(false, match('{a,{b,c}}', 'c')) -- {} can't nest - end) - - it('should match [] groups', function() - eq(true, match('[]', '')) - eq(false, match('[a-z]', '')) - eq(true, match('[a-z]', 'a')) - eq(false, match('[a-z]', 'ab')) - eq(true, match('[a-z]', 'z')) - eq(true, match('[a-z]', 'j')) - eq(false, match('[a-f]', 'j')) - eq(false, match('[a-z]', '`')) -- 'a' - 1 - eq(false, match('[a-z]', '{')) -- 'z' + 1 - eq(false, match('[a-z]', 'A')) - eq(false, match('[a-z]', '5')) - eq(true, match('[A-Z]', 'A')) - eq(true, match('[A-Z]', 'Z')) - eq(true, match('[A-Z]', 'J')) - eq(false, match('[A-Z]', '@')) -- 'A' - 1 - eq(false, match('[A-Z]', '[')) -- 'Z' + 1 - eq(false, match('[A-Z]', 'a')) - eq(false, match('[A-Z]', '5')) - eq(true, match('[a-zA-Z0-9]', 'z')) - eq(true, match('[a-zA-Z0-9]', 'Z')) - eq(true, match('[a-zA-Z0-9]', '9')) - eq(false, match('[a-zA-Z0-9]', '&')) - end) - - it('should match [!...] groups', function() - has_err(function() match('[!]', '') end) -- not a valid pattern - eq(false, match('[!a-z]', '')) - eq(false, match('[!a-z]', 'a')) - eq(false, match('[!a-z]', 'z')) - eq(false, match('[!a-z]', 'j')) - eq(true, match('[!a-f]', 'j')) - eq(false, match('[!a-f]', 'jj')) - eq(true, match('[!a-z]', '`')) -- 'a' - 1 - eq(true, match('[!a-z]', '{')) -- 'z' + 1 - eq(false, match('[!a-zA-Z0-9]', 'a')) - eq(false, match('[!a-zA-Z0-9]', 'A')) - eq(false, match('[!a-zA-Z0-9]', '0')) - eq(true, match('[!a-zA-Z0-9]', '!')) - end) - - it('should match complex patterns', function() - eq(false, match('**/*.{c,h}', '')) - eq(false, match('**/*.{c,h}', 'c')) - eq(true, match('**/*.{c,h}', 'file.c')) - eq(true, match('**/*.{c,h}', 'file.h')) - eq(true, match('**/*.{c,h}', '/file.c')) - eq(true, match('**/*.{c,h}', 'dir/subdir/file.c')) - eq(true, match('**/*.{c,h}', 'dir/subdir/file.h')) - - eq(true, match('{[0-9],[a-z]}', '0')) - eq(true, match('{[0-9],[a-z]}', 'a')) - eq(false, match('{[0-9],[a-z]}', 'A')) - end) - end) -end) 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 7e19cabeb192d2e7f20d7bb965a3f62e1543d2ac Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 28 Feb 2023 12:38:33 +0100 Subject: perf(lsp): only redraw the windows containing LSP tokens redraw! redraws the entire screen instead of just the windows with the buffer which were actually changed. I considered trying to calculating the range for the delta but it looks tricky. Could a follow-up. --- .../functional/plugin/lsp/semantic_tokens_spec.lua | 160 ++++++++++++++++++--- 1 file changed, 143 insertions(+), 17 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 9c1ba86fe1..004fce4983 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -24,6 +24,25 @@ end) describe('semantic token highlighting', function() + local screen + before_each(function() + screen = Screen.new(40, 16) + screen:attach() + screen:set_default_attr_ids { + [1] = { bold = true, foreground = Screen.colors.Blue1 }; + [2] = { foreground = Screen.colors.DarkCyan }; + [3] = { foreground = Screen.colors.SlateBlue }; + [4] = { bold = true, foreground = Screen.colors.SeaGreen }; + [5] = { foreground = tonumber('0x6a0dad') }; + [6] = { foreground = Screen.colors.Blue1 }; + [7] = { bold = true, foreground = Screen.colors.DarkCyan }; + [8] = { bold = true, foreground = Screen.colors.SlateBlue }; + } + command([[ hi link @namespace Type ]]) + command([[ hi link @function Special ]]) + command([[ hi @declaration gui=bold ]]) + end) + describe('general', function() local text = dedent([[ #include @@ -58,24 +77,7 @@ describe('semantic token highlighting', function() "resultId":"2" }]] - local screen before_each(function() - screen = Screen.new(40, 16) - screen:attach() - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue1 }; - [2] = { foreground = Screen.colors.DarkCyan }; - [3] = { foreground = Screen.colors.SlateBlue }; - [4] = { bold = true, foreground = Screen.colors.SeaGreen }; - [5] = { foreground = tonumber('0x6a0dad') }; - [6] = { foreground = Screen.colors.Blue1 }; - [7] = { bold = true, foreground = Screen.colors.DarkCyan }; - [8] = { bold = true, foreground = Screen.colors.SlateBlue }; - } - command([[ hi link @namespace Type ]]) - command([[ hi link @function Special ]]) - command([[ hi @declaration gui=bold ]]) - exec_lua(create_server_definition) exec_lua([[ local legend, response, edit_response = ... @@ -928,6 +930,46 @@ b = "as"]], extmark_added = true, } }, + expected_screen1 = function() + screen:expect{grid=[[ + char* {7:foo} = "\n"^; | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end, + expected_screen2 = function() + screen:expect{grid=[[ + ^ | + char* {7:foo} = "\n"; | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end, }, { it = 'response with multiple delta edits', @@ -1127,6 +1169,46 @@ int main() extmark_added = true, } }, + expected_screen1 = function() + screen:expect{grid=[[ + #include | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end, + expected_screen2 = function() + screen:expect{grid=[[ + #include | + | + int {8:main}() | + { | + int {8:x}(); | + double {7:y}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {3:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:^#endif} | + } | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end, }, { it = 'optional token_edit.data on deletion', @@ -1156,6 +1238,46 @@ int main() }, expected2 = { }, + expected_screen1 = function() + screen:expect{grid=[[ + {7:string} = "test^" | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end, + expected_screen2 = function() + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end, }, }) do it(test.it, function() @@ -1192,6 +1314,8 @@ int main() insert(test.text1) + test.expected_screen1() + local highlights = exec_lua([[ return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights ]]) @@ -1208,6 +1332,8 @@ int main() ]], test.text2) end + test.expected_screen2() + highlights = exec_lua([[ return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights ]]) -- 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/watchfiles_spec.lua | 173 ++++++++++ test/functional/plugin/lsp_spec.lua | 421 +++++++++++++++++++++++++ 2 files changed, 594 insertions(+) create mode 100644 test/functional/plugin/lsp/watchfiles_spec.lua (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/watchfiles_spec.lua b/test/functional/plugin/lsp/watchfiles_spec.lua new file mode 100644 index 0000000000..c5d6803a7f --- /dev/null +++ b/test/functional/plugin/lsp/watchfiles_spec.lua @@ -0,0 +1,173 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local has_err = require('luassert').has.errors + +describe('vim.lsp._watchfiles', function() + before_each(helpers.clear) + after_each(helpers.clear) + + local match = function(...) + return exec_lua('return require("vim.lsp._watchfiles")._match(...)', ...) + end + + describe('glob matching', function() + it('should match literal strings', function() + eq(true, match('', '')) + eq(false, match('', 'a')) + eq(true, match('a', 'a')) + eq(true, match('abc', 'abc')) + eq(false, match('abc', 'abcdef')) + eq(false, match('abc', 'a')) + eq(false, match('a', 'b')) + eq(false, match('.', 'a')) + eq(true, match('$', '$')) + eq(false, match('dir/subdir', 'dir/subdir/file')) + end) + + it('should match * wildcards', function() + -- eq(false, match('*', '')) -- TODO: this fails + eq(true, match('*', 'a')) + eq(false, match('*', '/a')) + eq(false, match('*', 'a/')) + eq(true, match('*', 'aaa')) + eq(true, match('*.txt', 'file.txt')) + eq(false, match('*.txt', 'file.txtxt')) + eq(false, match('*.txt', 'dir/file.txt')) + eq(false, match('*.txt', '/dir/file.txt')) + eq(false, match('*.txt', 'C:/dir/file.txt')) + eq(false, match('*.dir', 'test.dir/file')) + eq(true, match('file.*', 'file.txt')) + eq(false, match('file.*', 'not-file.txt')) + eq(false, match('dir/*.txt', 'file.txt')) + eq(true, match('dir/*.txt', 'dir/file.txt')) + eq(false, match('dir/*.txt', 'dir/subdir/file.txt')) + end) + + it('should match ? wildcards', function() + eq(false, match('?', '')) + eq(true, match('?', 'a')) + eq(false, match('??', 'a')) + eq(false, match('?', 'ab')) + eq(true, match('??', 'ab')) + eq(true, match('a?c', 'abc')) + eq(false, match('a?c', 'a/c')) + end) + + it('should match ** wildcards', function() + eq(true, match('**', '')) + eq(true, match('**', 'a')) + eq(true, match('**', 'a/')) + eq(true, match('**', '/a')) + eq(true, match('**', 'C:/a')) + eq(true, match('**', 'a/a')) + eq(true, match('**', 'a/a/a')) + eq(false, match('a**', '')) + eq(true, match('a**', 'a')) + eq(true, match('a**', 'abcd')) + eq(false, match('a**', 'ba')) + eq(false, match('a**', 'a/b')) + eq(false, match('**a', '')) + eq(true, match('**a', 'a')) + eq(true, match('**a', 'dcba')) + eq(false, match('**a', 'ab')) + eq(false, match('**a', 'b/a')) + eq(false, match('a/**', '')) + eq(true, match('a/**', 'a')) + eq(true, match('a/**', 'a/b')) + eq(false, match('a/**', 'b/a')) + eq(false, match('a/**', '/a')) + eq(false, match('**/a', '')) + eq(true, match('**/a', 'a')) + eq(false, match('**/a', 'a/b')) + eq(true, match('**/a', '/a')) + eq(false, match('a/**/c', 'a')) + eq(false, match('a/**/c', 'c')) + eq(true, match('a/**/c', 'a/c')) + eq(true, match('a/**/c', 'a/b/c')) + eq(true, match('a/**/c', 'a/b/b/c')) + eq(true, match('**/a/**', 'a')) + eq(true, match('**/a/**', '/dir/a')) + eq(true, match('**/a/**', 'a/dir')) + eq(true, match('**/a/**', 'dir/a/dir')) + eq(true, match('**/a/**', '/a/dir')) + eq(true, match('**/a/**', 'C:/a/dir')) + -- eq(false, match('**/a/**', 'a.txt')) -- TODO: this fails + end) + + it('should match {} groups', function() + eq(false, match('{}', '')) + eq(true, match('{,}', '')) + eq(false, match('{}', 'a')) + eq(true, match('{a}', 'a')) + eq(false, match('{a}', 'aa')) + eq(false, match('{a}', 'ab')) + eq(false, match('{ab}', 'a')) + eq(true, match('{ab}', 'ab')) + eq(true, match('{a,b}', 'a')) + eq(true, match('{a,b}', 'b')) + eq(false, match('{a,b}', 'ab')) + eq(true, match('{ab,cd}', 'ab')) + eq(false, match('{ab,cd}', 'a')) + eq(true, match('{ab,cd}', 'cd')) + eq(true, match('{a,b,c}', 'c')) + eq(false, match('{a,{b,c}}', 'c')) -- {} can't nest + end) + + it('should match [] groups', function() + eq(true, match('[]', '')) + eq(false, match('[a-z]', '')) + eq(true, match('[a-z]', 'a')) + eq(false, match('[a-z]', 'ab')) + eq(true, match('[a-z]', 'z')) + eq(true, match('[a-z]', 'j')) + eq(false, match('[a-f]', 'j')) + eq(false, match('[a-z]', '`')) -- 'a' - 1 + eq(false, match('[a-z]', '{')) -- 'z' + 1 + eq(false, match('[a-z]', 'A')) + eq(false, match('[a-z]', '5')) + eq(true, match('[A-Z]', 'A')) + eq(true, match('[A-Z]', 'Z')) + eq(true, match('[A-Z]', 'J')) + eq(false, match('[A-Z]', '@')) -- 'A' - 1 + eq(false, match('[A-Z]', '[')) -- 'Z' + 1 + eq(false, match('[A-Z]', 'a')) + eq(false, match('[A-Z]', '5')) + eq(true, match('[a-zA-Z0-9]', 'z')) + eq(true, match('[a-zA-Z0-9]', 'Z')) + eq(true, match('[a-zA-Z0-9]', '9')) + eq(false, match('[a-zA-Z0-9]', '&')) + end) + + it('should match [!...] groups', function() + has_err(function() match('[!]', '') end) -- not a valid pattern + eq(false, match('[!a-z]', '')) + eq(false, match('[!a-z]', 'a')) + eq(false, match('[!a-z]', 'z')) + eq(false, match('[!a-z]', 'j')) + eq(true, match('[!a-f]', 'j')) + eq(false, match('[!a-f]', 'jj')) + eq(true, match('[!a-z]', '`')) -- 'a' - 1 + eq(true, match('[!a-z]', '{')) -- 'z' + 1 + eq(false, match('[!a-zA-Z0-9]', 'a')) + eq(false, match('[!a-zA-Z0-9]', 'A')) + eq(false, match('[!a-zA-Z0-9]', '0')) + eq(true, match('[!a-zA-Z0-9]', '!')) + end) + + it('should match complex patterns', function() + eq(false, match('**/*.{c,h}', '')) + eq(false, match('**/*.{c,h}', 'c')) + eq(true, match('**/*.{c,h}', 'file.c')) + eq(true, match('**/*.{c,h}', 'file.h')) + eq(true, match('**/*.{c,h}', '/file.c')) + eq(true, match('**/*.{c,h}', 'dir/subdir/file.c')) + eq(true, match('**/*.{c,h}', 'dir/subdir/file.h')) + + eq(true, match('{[0-9],[a-z]}', '0')) + eq(true, match('{[0-9],[a-z]}', 'a')) + eq(false, match('{[0-9],[a-z]}', 'A')) + end) + end) +end) 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 1cc23e1109ed88275df5c986c352f73b99a0301c Mon Sep 17 00:00:00 2001 From: swarn Date: Mon, 6 Mar 2023 12:03:13 -0600 Subject: feat(lsp)!: add rule-based sem token highlighting (#22022) feat(lsp)!: change semantic token highlighting Change the default highlights used, and add more highlights per token. Add an LspTokenUpdate event and a highlight_token function. :Inspect now shows any highlights applied by token highlighting rules, default or user-defined. BREAKING CHANGE: change the default highlight groups used by semantic token highlighting. --- .../functional/plugin/lsp/semantic_tokens_spec.lua | 211 ++++++++++++--------- 1 file changed, 125 insertions(+), 86 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 004fce4983..780d18fce9 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -37,10 +37,12 @@ describe('semantic token highlighting', function() [6] = { foreground = Screen.colors.Blue1 }; [7] = { bold = true, foreground = Screen.colors.DarkCyan }; [8] = { bold = true, foreground = Screen.colors.SlateBlue }; + [9] = { bold = true, foreground = tonumber('0x6a0dad') }; } - command([[ hi link @namespace Type ]]) - command([[ hi link @function Special ]]) - command([[ hi @declaration gui=bold ]]) + command([[ hi link @lsp.type.namespace Type ]]) + command([[ hi link @lsp.type.function Special ]]) + command([[ hi link @lsp.type.comment Comment ]]) + command([[ hi @lsp.mod.declaration gui=bold ]]) end) describe('general', function() @@ -129,6 +131,46 @@ describe('semantic token highlighting', function() ]] } end) + it('use LspTokenUpdate and highlight_token', function() + exec_lua([[ + vim.api.nvim_create_autocmd("LspTokenUpdate", { + callback = function(args) + local token = args.data.token + if token.type == "function" and token.modifiers.declaration then + vim.lsp.semantic_tokens.highlight_token( + token, args.buf, args.data.client_id, "Macro" + ) + end + end, + }) + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + screen:expect { grid = [[ + #include | + | + int {9:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + + end) + it('buffer is unhighlighted when client is detached', function() exec_lua([[ bufnr = vim.api.nvim_get_current_buf() @@ -580,14 +622,11 @@ describe('semantic token highlighting', function() expected = { { line = 0, - modifiers = { - 'declaration', - 'globalScope', - }, + modifiers = { declaration = true, globalScope = true }, start_col = 6, end_col = 9, type = 'variable', - extmark_added = true, + marked = true, }, }, }, @@ -615,67 +654,67 @@ int main() expected = { { -- main line = 1, - modifiers = { 'declaration', 'globalScope' }, + modifiers = { declaration = true, globalScope = true }, start_col = 4, end_col = 8, type = 'function', - extmark_added = true, + marked = true, }, { -- __cplusplus line = 3, - modifiers = { 'globalScope' }, + modifiers = { globalScope = true }, start_col = 9, end_col = 20, type = 'macro', - extmark_added = true, + marked = true, }, { -- x line = 4, - modifiers = { 'declaration', 'readonly', 'functionScope' }, + modifiers = { declaration = true, readonly = true, functionScope = true }, start_col = 12, end_col = 13, type = 'variable', - extmark_added = true, + marked = true, }, { -- std line = 5, - modifiers = { 'defaultLibrary', 'globalScope' }, + modifiers = { defaultLibrary = true, globalScope = true }, start_col = 2, end_col = 5, type = 'namespace', - extmark_added = true, + marked = true, }, { -- cout line = 5, - modifiers = { 'defaultLibrary', 'globalScope' }, + modifiers = { defaultLibrary = true, globalScope = true }, start_col = 7, end_col = 11, type = 'variable', - extmark_added = true, + marked = true, }, { -- x line = 5, - modifiers = { 'readonly', 'functionScope' }, + modifiers = { readonly = true, functionScope = true }, start_col = 15, end_col = 16, type = 'variable', - extmark_added = true, + marked = true, }, { -- std line = 5, - modifiers = { 'defaultLibrary', 'globalScope' }, + modifiers = { defaultLibrary = true, globalScope = true }, start_col = 20, end_col = 23, type = 'namespace', - extmark_added = true, + marked = true, }, { -- endl line = 5, - modifiers = { 'defaultLibrary', 'globalScope' }, + modifiers = { defaultLibrary = true, globalScope = true }, start_col = 25, end_col = 29, type = 'function', - extmark_added = true, + marked = true, }, { -- #else comment #endif line = 6, @@ -683,7 +722,7 @@ int main() start_col = 0, end_col = 7, type = 'comment', - extmark_added = true, + marked = true, }, { line = 7, @@ -691,7 +730,7 @@ int main() start_col = 0, end_col = 11, type = 'comment', - extmark_added = true, + marked = true, }, { line = 8, @@ -699,7 +738,7 @@ int main() start_col = 0, end_col = 8, type = 'comment', - extmark_added = true, + marked = true, }, }, }, @@ -724,23 +763,23 @@ b = "as"]], start_col = 0, end_col = 10, type = 'comment', -- comment - extmark_added = true, + marked = true, }, { line = 1, - modifiers = { 'declaration' }, -- a + modifiers = { declaration = true }, -- a start_col = 6, end_col = 7, type = 'variable', - extmark_added = true, + marked = true, }, { line = 2, - modifiers = { 'static' }, -- b (global) + modifiers = { static = true }, -- b (global) start_col = 0, end_col = 1, type = 'variable', - extmark_added = true, + marked = true, }, }, }, @@ -770,7 +809,7 @@ b = "as"]], start_col = 0, end_col = 3, -- pub type = 'keyword', - extmark_added = true, + marked = true, }, { line = 0, @@ -778,15 +817,15 @@ b = "as"]], start_col = 4, end_col = 6, -- fn type = 'keyword', - extmark_added = true, + marked = true, }, { line = 0, - modifiers = { 'declaration', 'public' }, + modifiers = { declaration = true, public = true }, start_col = 7, end_col = 11, -- main type = 'function', - extmark_added = true, + marked = true, }, { line = 0, @@ -794,7 +833,7 @@ b = "as"]], start_col = 11, end_col = 12, type = 'parenthesis', - extmark_added = true, + marked = true, }, { line = 0, @@ -802,7 +841,7 @@ b = "as"]], start_col = 12, end_col = 13, type = 'parenthesis', - extmark_added = true, + marked = true, }, { line = 0, @@ -810,15 +849,15 @@ b = "as"]], start_col = 14, end_col = 15, type = 'brace', - extmark_added = true, + marked = true, }, { line = 1, - modifiers = { 'controlFlow' }, + modifiers = { controlFlow = true }, start_col = 4, end_col = 9, -- break type = 'keyword', - extmark_added = true, + marked = true, }, { line = 1, @@ -826,7 +865,7 @@ b = "as"]], start_col = 10, end_col = 13, -- rust type = 'unresolvedReference', - extmark_added = true, + marked = true, }, { line = 1, @@ -834,15 +873,15 @@ b = "as"]], start_col = 13, end_col = 13, type = 'semicolon', - extmark_added = true, + marked = true, }, { line = 2, - modifiers = { 'documentation' }, + modifiers = { documentation = true }, start_col = 4, end_col = 11, type = 'comment', -- /// what? - extmark_added = true, + marked = true, }, { line = 3, @@ -850,7 +889,7 @@ b = "as"]], start_col = 0, end_col = 1, type = 'brace', - extmark_added = true, + marked = true, }, }, }, @@ -908,26 +947,26 @@ b = "as"]], { line = 0, modifiers = { - 'declaration', - 'globalScope', + declaration = true, + globalScope = true, }, start_col = 6, end_col = 9, type = 'variable', - extmark_added = true, + marked = true, } }, expected2 = { { line = 1, modifiers = { - 'declaration', - 'globalScope', + declaration = true, + globalScope = true, }, start_col = 6, end_col = 9, type = 'variable', - extmark_added = true, + marked = true, } }, expected_screen1 = function() @@ -1018,55 +1057,55 @@ int main() line = 2, start_col = 4, end_col = 8, - modifiers = { 'declaration', 'globalScope' }, + modifiers = { declaration = true, globalScope = true }, type = 'function', - extmark_added = true, + marked = true, }, { line = 4, start_col = 8, end_col = 9, - modifiers = { 'declaration', 'functionScope' }, + modifiers = { declaration = true, functionScope = true }, type = 'variable', - extmark_added = true, + marked = true, }, { line = 5, start_col = 7, end_col = 18, - modifiers = { 'globalScope' }, + modifiers = { globalScope = true }, type = 'macro', - extmark_added = true, + marked = true, }, { line = 6, start_col = 4, end_col = 7, - modifiers = { 'defaultLibrary', 'globalScope' }, + modifiers = { defaultLibrary = true, globalScope = true }, type = 'namespace', - extmark_added = true, + marked = true, }, { line = 6, start_col = 9, end_col = 13, - modifiers = { 'defaultLibrary', 'globalScope' }, + modifiers = { defaultLibrary = true, globalScope = true }, type = 'variable', - extmark_added = true, + marked = true, }, { line = 6, start_col = 17, end_col = 18, - extmark_added = true, - modifiers = { 'functionScope' }, + marked = true, + modifiers = { functionScope = true }, type = 'variable', }, { line = 7, start_col = 0, end_col = 5, - extmark_added = true, + marked = true, modifiers = {}, type = 'comment', }, @@ -1076,7 +1115,7 @@ int main() modifiers = {}, start_col = 0, type = 'comment', - extmark_added = true, + marked = true, }, { line = 9, @@ -1084,7 +1123,7 @@ int main() end_col = 6, modifiers = {}, type = 'comment', - extmark_added = true, + marked = true, } }, expected2 = { @@ -1092,63 +1131,63 @@ int main() line = 2, start_col = 4, end_col = 8, - modifiers = { 'declaration', 'globalScope' }, + modifiers = { declaration = true, globalScope = true }, type = 'function', - extmark_added = true, + marked = true, }, { line = 4, start_col = 8, end_col = 9, - modifiers = { 'declaration', 'globalScope' }, + modifiers = { declaration = true, globalScope = true }, type = 'function', - extmark_added = true, + marked = true, }, { line = 5, end_col = 12, start_col = 11, - modifiers = { 'declaration', 'functionScope' }, + modifiers = { declaration = true, functionScope = true }, type = 'variable', - extmark_added = true, + marked = true, }, { line = 6, start_col = 7, end_col = 18, - modifiers = { 'globalScope' }, + modifiers = { globalScope = true }, type = 'macro', - extmark_added = true, + marked = true, }, { line = 7, start_col = 4, end_col = 7, - modifiers = { 'defaultLibrary', 'globalScope' }, + modifiers = { defaultLibrary = true, globalScope = true }, type = 'namespace', - extmark_added = true, + marked = true, }, { line = 7, start_col = 9, end_col = 13, - modifiers = { 'defaultLibrary', 'globalScope' }, + modifiers = { defaultLibrary = true, globalScope = true }, type = 'variable', - extmark_added = true, + marked = true, }, { line = 7, start_col = 17, end_col = 18, - extmark_added = true, - modifiers = { 'globalScope' }, + marked = true, + modifiers = { globalScope = true }, type = 'function', }, { line = 8, start_col = 0, end_col = 5, - extmark_added = true, + marked = true, modifiers = {}, type = 'comment', }, @@ -1158,7 +1197,7 @@ int main() modifiers = {}, start_col = 0, type = 'comment', - extmark_added = true, + marked = true, }, { line = 10, @@ -1166,7 +1205,7 @@ int main() end_col = 6, modifiers = {}, type = 'comment', - extmark_added = true, + marked = true, } }, expected_screen1 = function() @@ -1228,12 +1267,12 @@ int main() { line = 0, modifiers = { - 'declaration', + declaration = true, }, start_col = 0, end_col = 6, type = 'variable', - extmark_added = true, + marked = true, } }, expected2 = { -- cgit From 160a019ffa104eebd65f4037729954d98aca6ad0 Mon Sep 17 00:00:00 2001 From: Eriks Muhins Date: Fri, 3 Mar 2023 21:44:13 +0200 Subject: feat(man.lua): support spaces in manpage names Problem: :Man command errors if given more than two arguments. Thus, it is impossible to open man pages that contain spaces in their names. Solution: Adjust :Man so that it tries variants with spaces and underscores, and uses the first found. --- test/functional/plugin/man_spec.lua | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 58da059be6..a1a7274e5d 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -10,6 +10,26 @@ local write_file = helpers.write_file local tmpname = helpers.tmpname local skip = helpers.skip local is_ci = helpers.is_ci +local table_contains = vim.tbl_contains + +-- Returns a table composed of all man page name arguments +-- that were passed to search_for_path after attempting to +-- open 'name'. +local function get_search_history(name) + local as_table = string.gsub(name, ' ', '\', \'') + as_table = '\'' .. as_table .. '\'' + local code = ([[ + local man = require('runtime.lua.man') + local res = {} + man.attempt_to_get_path = function(sect, name, silent) + table.insert(res, name) + return nil + end + pcall(man.open_page, 0, {tab = 0}, {%s}) + return res + ]]):format(as_table) + return exec_lua(code) +end clear() if funcs.executable('man') == 0 then @@ -173,4 +193,10 @@ describe(':Man', function() funcs.system(args, {''})) os.remove(actual_file) end) + + it('searches for manpage name with variants with spaces, underscores', function() + local tried = get_search_history('NAME WITH SPACES') + table_contains(tried, 'NAME WITH SPACES') + table_contains(tried, 'NAME_WITH_SPACES') + end) end) -- cgit From 304477ff3504373a336c83127654e65eddfa2ef9 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 7 Mar 2023 15:13:09 +0100 Subject: fix(man.lua): tests, naming --- test/functional/plugin/man_spec.lua | 39 +++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index a1a7274e5d..9730bf4bf6 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -8,27 +8,27 @@ local nvim_prog = helpers.nvim_prog local matches = helpers.matches local write_file = helpers.write_file local tmpname = helpers.tmpname +local eq = helpers.eq local skip = helpers.skip local is_ci = helpers.is_ci -local table_contains = vim.tbl_contains --- Returns a table composed of all man page name arguments --- that were passed to search_for_path after attempting to --- open 'name'. +-- Collects all names passed to find_path() after attempting ":Man foo". local function get_search_history(name) - local as_table = string.gsub(name, ' ', '\', \'') - as_table = '\'' .. as_table .. '\'' - local code = ([[ + local args = vim.split(name, ' ') + local code = [[ + local args = ... local man = require('runtime.lua.man') local res = {} - man.attempt_to_get_path = function(sect, name, silent) + man.find_path = function(sect, name) table.insert(res, name) return nil end - pcall(man.open_page, 0, {tab = 0}, {%s}) + local ok, rv = pcall(man.open_page, 0, {tab = 0}, args) + assert(not ok) + assert(rv and rv:match('no manual entry')) return res - ]]):format(as_table) - return exec_lua(code) + ]] + return exec_lua(code, args) end clear() @@ -194,9 +194,18 @@ describe(':Man', function() os.remove(actual_file) end) - it('searches for manpage name with variants with spaces, underscores', function() - local tried = get_search_history('NAME WITH SPACES') - table_contains(tried, 'NAME WITH SPACES') - table_contains(tried, 'NAME_WITH_SPACES') + it('tries variants with spaces, underscores #22503', function() + eq({ + 'NAME WITH SPACES', + 'NAME_WITH_SPACES', + }, get_search_history('NAME WITH SPACES')) + eq({ + 'some other man', + 'some_other_man', + }, get_search_history('3 some other man')) + eq({ + 'other_man', + 'other_man', + }, get_search_history('other_man(1)')) 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') 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/helpers.lua | 2 ++ test/functional/plugin/lsp_spec.lua | 1 + 2 files changed, 3 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/helpers.lua b/test/functional/plugin/lsp/helpers.lua index caab174b4d..86f7da0d2c 100644 --- a/test/functional/plugin/lsp/helpers.lua +++ b/test/functional/plugin/lsp/helpers.lua @@ -13,6 +13,7 @@ function M.clear_notrace() -- solution: don't look too closely for dragons clear {env={ NVIM_LUA_NOTRACK="1"; + NVIM_APPNAME="nvim_lsp_test"; VIMRUNTIME=os.getenv"VIMRUNTIME"; }} end @@ -85,6 +86,7 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options, settings) cmd_env = { NVIM_LOG_FILE = fake_lsp_logfile; NVIM_LUA_NOTRACK = "1"; + NVIM_APPNAME = "nvim_lsp_test"; }; cmd = { vim.v.progpath, '-l', fake_lsp_code, test_name, tostring(timeout), 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') 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') 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') 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 257d894d75bc583bb16f4dbe441907eb273d20ad Mon Sep 17 00:00:00 2001 From: Roberto Pommella Alegro Date: Sat, 25 Mar 2023 13:46:07 -0300 Subject: feat(lsp): render markdown in docs hover #22766 Problem: LSP docs hover (textDocument/hover) doesn't handle HTML escape seqs in markdown. Solution: Convert common HTML escape seqs to a nicer form, to display in the float. closees #22757 Signed-off-by: Kasama --- test/functional/plugin/lsp/utils_spec.lua | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 test/functional/plugin/lsp/utils_spec.lua (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua new file mode 100644 index 0000000000..3e53b6d574 --- /dev/null +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -0,0 +1,75 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local exec_lua = helpers.exec_lua + +describe('vim.lsp.util', function() + before_each(helpers.clear) + + describe('stylize_markdown', function() + local stylize_markdown = function(content, opts) + return exec_lua([[ + local bufnr = vim.uri_to_bufnr("file:///fake/uri") + vim.fn.bufload(bufnr) + + local args = { ... } + local content = args[1] + local opts = args[2] + local stripped_content = vim.lsp.util.stylize_markdown(bufnr, content, opts) + + return stripped_content + ]], content, opts) + end + + it('code fences', function() + local lines = { + "```lua", + "local hello = 'world'", + "```", + } + local expected = { + "local hello = 'world'", + } + local opts = {} + eq(expected, stylize_markdown(lines, opts)) + end) + + it('adds separator after code block', function() + local lines = { + "```lua", + "local hello = 'world'", + "```", + "", + "something", + } + local expected = { + "local hello = 'world'", + "─────────────────────", + "something", + } + local opts = { separator = true } + eq(expected, stylize_markdown(lines, opts)) + end) + + it('replaces supported HTML entities', function() + local lines = { + "1 < 2", + "3 > 2", + ""quoted"", + "'apos'", + "   ", + "&", + } + local expected = { + "1 < 2", + "3 > 2", + '"quoted"', + "'apos'", + " ", + "&", + } + local opts = {} + eq(expected, stylize_markdown(lines, opts)) + end) + end) +end) -- 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') 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 226a6c3eaef2a7220841d3d5e69e1baf543b3d6f Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 30 Mar 2023 14:49:58 +0100 Subject: feat(diagnostic): add support for tags The LSP spec supports two tags that can be added to diagnostics: unnecessary and deprecated. Extend vim.diagnostic to be able to handle these. --- test/functional/plugin/lsp/diagnostic_spec.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index f73ffc29b0..f58016bf01 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -97,7 +97,6 @@ describe('vim.lsp.diagnostic', function() } diagnostics[1].code = 42 - diagnostics[1].tags = {"foo", "bar"} diagnostics[1].data = "Hello world" vim.lsp.diagnostic.on_publish_diagnostics(nil, { @@ -110,10 +109,9 @@ describe('vim.lsp.diagnostic', function() vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1)[1], } ]] - eq({code = 42, tags = {"foo", "bar"}, data = "Hello world"}, result[1].user_data.lsp) + eq({code = 42, data = "Hello world"}, result[1].user_data.lsp) eq(42, result[1].code) eq(42, result[2].code) - eq({"foo", "bar"}, result[2].tags) eq("Hello world", result[2].data) end) end) -- 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/semantic_tokens_spec.lua | 2 +- test/functional/plugin/lsp_spec.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 780d18fce9..ec4d20974d 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -1367,7 +1367,7 @@ int main() exec_lua([[ local text = ... vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.fn.split(text, "\n")) - vim.wait(15) -- wait fot debounce + vim.wait(15) -- wait for debounce ]], test.text2) end 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') 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') 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 c08b03076167837cff9eb66c19440d727e6dad31 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Sat, 15 Apr 2023 23:40:48 +0200 Subject: refactor: deprecate checkhealth functions The following functions are deprecated and will be removed in Nvim v0.11: - health#report_start() - health#report_info() - health#report_ok() - health#report_warn() - health#report_error() - vim.health.report_start() - vim.health.report_info() - vim.health.report_ok() - vim.health.report_warn() - vim.health.report_error() Users should instead use these: - vim.health.start() - vim.health.info() - vim.health.ok() - vim.health.warn() - vim.health.error() --- test/functional/plugin/health_spec.lua | 109 +++------------------------------ 1 file changed, 9 insertions(+), 100 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 97d32313e5..926e12ec32 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -1,5 +1,4 @@ local helpers = require('test.functional.helpers')(after_each) -local global_helpers = require('test.helpers') local Screen = require('test.functional.ui.screen') local clear = helpers.clear @@ -43,20 +42,17 @@ end) describe('health.vim', function() before_each(function() clear{args={'-u', 'NORC'}} - -- Provides functions: - -- health#broken#check() - -- health#success1#check() - -- health#success2#check() + -- Provides healthcheck functions command("set runtimepath+=test/functional/fixtures") end) describe(":checkhealth", function() - it("functions health#report_*() render correctly", function() + it("functions report_*() render correctly", function() command("checkhealth full_render") helpers.expect([[ ============================================================================== - full_render: health#full_render#check + test_plug.full_render: require("test_plug.full_render.health").check() report 1 ~ - OK life is fine @@ -79,7 +75,7 @@ describe('health.vim', function() helpers.expect([[ ============================================================================== - success1: health#success1#check + test_plug: require("test_plug.health").check() report 1 ~ - OK everything is fine @@ -88,36 +84,19 @@ describe('health.vim', function() - OK nothing to see here ============================================================================== - success2: health#success2#check - - another 1 ~ - - OK ok - - ============================================================================== - test_plug: require("test_plug.health").check() + test_plug.success1: require("test_plug.success1.health").check() report 1 ~ - OK everything is fine report 2 ~ - OK nothing to see here - ]]) - end) - - it("lua plugins, skips vimscript healthchecks with the same name", function() - command("checkhealth test_plug") - -- Existing file in test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim - -- and the Lua healthcheck is used instead. - helpers.expect([[ ============================================================================== - test_plug: require("test_plug.health").check() - - report 1 ~ - - OK everything is fine + test_plug.success2: require("test_plug.success2.health").check() - report 2 ~ - - OK nothing to see here + another 1 ~ + - OK ok ]]) end) @@ -136,57 +115,6 @@ describe('health.vim', function() ]]) end) - it("lua plugins submodules with expression '*'", function() - command("checkhealth test_plug*") - local buf_lines = helpers.curbuf('get_lines', 0, -1, true) - -- avoid dealing with path separators - local received = table.concat(buf_lines, '\n', 1, #buf_lines - 5) - local expected = helpers.dedent([[ - - ============================================================================== - test_plug: require("test_plug.health").check() - - report 1 ~ - - OK everything is fine - - report 2 ~ - - OK nothing to see here - - ============================================================================== - test_plug.submodule: require("test_plug.submodule.health").check() - - report 1 ~ - - OK everything is fine - - report 2 ~ - - OK nothing to see here - - ============================================================================== - test_plug.submodule_empty: require("test_plug.submodule_empty.health").check() - - - ERROR The healthcheck report for "test_plug.submodule_empty" plugin is empty. - - ============================================================================== - test_plug.submodule_failed: require("test_plug.submodule_failed.health").check() - - - ERROR Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: - function health#check, line 25]]) - eq(expected, received) - end) - - it("gracefully handles broken healthcheck", function() - command("checkhealth broken") - helpers.expect([[ - - ============================================================================== - broken: health#broken#check - - - ERROR Failed to run healthcheck for "broken" plugin. Exception: - function health#check[25]..health#broken#check, line 1 - caused an error - ]]) - end) - it("... including empty reports", function() command("checkhealth test_plug.submodule_empty") helpers.expect([[ @@ -198,25 +126,6 @@ describe('health.vim', function() ]]) end) - it("gracefully handles broken lua healthcheck", function() - command("checkhealth test_plug.submodule_failed") - local buf_lines = helpers.curbuf('get_lines', 0, -1, true) - local received = table.concat(buf_lines, '\n', 1, #buf_lines - 5) - -- avoid dealing with path separators - local lua_err = "attempt to perform arithmetic on a nil value" - local last_line = buf_lines[#buf_lines - 4] - assert(string.find(last_line, lua_err) ~= nil, "Lua error not present") - - local expected = global_helpers.dedent([[ - - ============================================================================== - test_plug.submodule_failed: require("test_plug.submodule_failed.health").check() - - - ERROR Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: - function health#check, line 25]]) - eq(expected, received) - end) - it("highlights OK, ERROR", function() local screen = Screen.new(50, 12) screen:attach() @@ -236,7 +145,7 @@ describe('health.vim', function() - {Error:ERROR} No healthcheck found for "foo" plugin. | | {Bar:──────────────────────────────────────────────────}| - {Heading:success1: health#success1#check} | + {Heading:test_plug.success1: require("test_plug.success1.he}| | {Heading:report 1} | - {Ok:OK} everything is fine | -- cgit From 540d6c595bc8e1b298dce51211b5d39e25e17d03 Mon Sep 17 00:00:00 2001 From: jdrouhard Date: Sat, 22 Apr 2023 17:08:28 -0500 Subject: test(lsp): fix unstable tests for semantic tokens Add screen:expect() calls after insert() to make sure the screen has been drawn before we assert the state of the semantic tokens table --- .../functional/plugin/lsp/semantic_tokens_spec.lua | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index ec4d20974d..d1ffb72ef5 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -629,6 +629,26 @@ describe('semantic token highlighting', function() marked = true, }, }, + expected_screen = function() + screen:expect{grid=[[ + char* {7:foo} = "\n"^; | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end, }, { it = 'clangd-15 on C++', @@ -741,6 +761,26 @@ int main() marked = true, }, }, + expected_screen = function() + screen:expect{grid=[[ + #include | + int {8:main}() | + { | + #ifdef {5:__cplusplus} | + const int {7:x} = 1; | + {4:std}::{2:cout} << {2:x} << {4:std}::{3:endl}; | + {6: #else} | + {6: comment} | + {6: #endif} | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end, }, { it = 'sumneko_lua', @@ -782,6 +822,26 @@ b = "as"]], marked = true, }, }, + expected_screen = function() + screen:expect{grid=[[ + {6:-- comment} | + local {7:a} = 1 | + {2:b} = "as^" | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end, }, { it = 'rust-analyzer', @@ -892,6 +952,26 @@ b = "as"]], marked = true, }, }, + expected_screen = function() + screen:expect{grid=[[ + pub fn {8:main}() { | + break rust; | + //{6:/ what?} | + } | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end, }, }) do it(test.it, function() @@ -918,6 +998,8 @@ b = "as"]], insert(test.text) + test.expected_screen() + local highlights = exec_lua([[ local semantic_tokens = vim.lsp.semantic_tokens return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights -- cgit From d321deb4a9b05e9d81b79ac166274f4a6e7981bf Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 27 Apr 2023 15:51:44 +0800 Subject: test: fix dependencies between test cases (#23343) Discovered using --shuffle argument of busted. --- test/functional/plugin/shada_spec.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index 93cf6d2b77..f6145a5809 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -2150,6 +2150,7 @@ describe('plugin/shada.vim', function() teardown(function() os.remove(fname) + os.remove(fname .. '.tst') os.remove(fname_tmp) end) @@ -2419,6 +2420,9 @@ describe('plugin/shada.vim', function() it('event SourceCmd', function() reset(fname) + finally(function() + nvim_command('set shadafile=NONE') -- Avoid writing shada file on exit + end) wshada('\004\000\006\146\000\196\002ab') wshada_tmp('\004\001\006\146\000\196\002bc') eq(0, exc_exec('source ' .. fname)) -- cgit From c8ebb04e92c9646d83110f4b45f1d1dfd5316561 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 5 May 2023 18:15:44 +0200 Subject: fix(health): replace healthFoo with DiagnosticFoo (#23475) This replaces the custom `health{Error,Warning,Success}` highlight groups with `Diagnostic{Error,Warning,Ok}`, which are defined by default. Removes the link for `healthHelp`, which was no longer actually used after #20879. --- test/functional/plugin/health_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 926e12ec32..488a213a9e 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -130,8 +130,8 @@ describe('health.vim', function() local screen = Screen.new(50, 12) screen:attach() screen:set_default_attr_ids({ - Ok = { foreground = Screen.colors.Grey3, background = 6291200 }, - Error = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, + Ok = { foreground = Screen.colors.LightGreen }, + Error = { foreground = Screen.colors.Red }, Heading = { foreground = tonumber('0x6a0dad') }, Bar = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGrey }, }) -- cgit From 3001d86aea184f8b015c1bfffd2db42c946f8a84 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 8 May 2023 15:22:58 +0800 Subject: test: add more tests for :Man section extraction --- test/functional/plugin/man_spec.lua | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 9730bf4bf6..d5c1a78fc8 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -20,10 +20,10 @@ local function get_search_history(name) local man = require('runtime.lua.man') local res = {} man.find_path = function(sect, name) - table.insert(res, name) + table.insert(res, {sect, name}) return nil end - local ok, rv = pcall(man.open_page, 0, {tab = 0}, args) + local ok, rv = pcall(man.open_page, -1, {tab = 0}, args) assert(not ok) assert(rv and rv:match('no manual entry')) return res @@ -196,16 +196,32 @@ describe(':Man', function() it('tries variants with spaces, underscores #22503', function() eq({ - 'NAME WITH SPACES', - 'NAME_WITH_SPACES', + {'', 'NAME WITH SPACES'}, + {'', 'NAME_WITH_SPACES'}, }, get_search_history('NAME WITH SPACES')) eq({ - 'some other man', - 'some_other_man', + {'3', 'some other man'}, + {'3', 'some_other_man'}, }, get_search_history('3 some other man')) eq({ - 'other_man', - 'other_man', + {'3x', 'some other man'}, + {'3x', 'some_other_man'}, + }, get_search_history('3X some other man')) + eq({ + {'3tcl', 'some other man'}, + {'3tcl', 'some_other_man'}, + }, get_search_history('3tcl some other man')) + eq({ + {'n', 'some other man'}, + {'n', 'some_other_man'}, + }, get_search_history('n some other man')) + eq({ + {'', '123some other man'}, + {'', '123some_other_man'}, + }, get_search_history('123some other man')) + eq({ + {'1', 'other_man'}, + {'1', 'other_man'}, }, get_search_history('other_man(1)')) end) end) -- 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') 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') 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/editorconfig_spec.lua | 3 +-- .../plugin/lsp/incremental_sync_spec.lua | 2 +- test/functional/plugin/lsp_spec.lua | 20 ++++++++------- test/functional/plugin/shada_spec.lua | 30 +++++++++++----------- 4 files changed, 28 insertions(+), 27 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/editorconfig_spec.lua b/test/functional/plugin/editorconfig_spec.lua index e6a2550aba..4ad9903032 100644 --- a/test/functional/plugin/editorconfig_spec.lua +++ b/test/functional/plugin/editorconfig_spec.lua @@ -3,7 +3,6 @@ local clear = helpers.clear local command = helpers.command local eq = helpers.eq local pathsep = helpers.get_pathsep() -local curbufmeths = helpers.curbufmeths local funcs = helpers.funcs local meths = helpers.meths @@ -13,7 +12,7 @@ local function test_case(name, expected) local filename = testdir .. pathsep .. name command('edit ' .. filename) for opt, val in pairs(expected) do - eq(val, curbufmeths.get_option(opt), name) + eq(val, meths.get_option_value(opt, {buf=0}), name) end end diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index 4985da9cd7..1dca464d74 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -21,7 +21,7 @@ before_each(function () -- ["mac"] = '\r', -- } - -- local line_ending = format_line_ending[vim.api.nvim_buf_get_option(0, 'fileformat')] + -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {buf=0})] function test_register(bufnr, id, offset_encoding, line_ending) 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 diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index f6145a5809..43222f1904 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -2181,8 +2181,8 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "a"', }, nvim_eval('getline(1, "$")')) - eq(false, curbuf('get_option', 'modified')) - eq('shada', curbuf('get_option', 'filetype')) + eq(false, nvim('get_option_value', 'modified', {buf=0})) + eq('shada', nvim('get_option_value', 'filetype', {buf=0})) nvim_command('edit ' .. fname_tmp) eq({ 'History entry with timestamp ' .. epoch .. ':', @@ -2191,8 +2191,8 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "b"', }, nvim_eval('getline(1, "$")')) - eq(false, curbuf('get_option', 'modified')) - eq('shada', curbuf('get_option', 'filetype')) + eq(false, nvim('get_option_value', 'modified', {buf=0})) + eq('shada', nvim('get_option_value', 'filetype', {buf=0})) eq('++opt not supported', exc_exec('edit ++enc=latin1 ' .. fname)) neq({ 'History entry with timestamp ' .. epoch .. ':', @@ -2201,7 +2201,7 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "a"', }, nvim_eval('getline(1, "$")')) - neq(true, curbuf('get_option', 'modified')) + neq(true, nvim('get_option_value', 'modified', {buf=0})) end) it('event FileReadCmd', function() @@ -2217,8 +2217,8 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "a"', }, nvim_eval('getline(1, "$")')) - eq(true, curbuf('get_option', 'modified')) - neq('shada', curbuf('get_option', 'filetype')) + eq(true, nvim('get_option_value', 'modified', {buf=0})) + neq('shada', nvim('get_option_value', 'filetype', {buf=0})) nvim_command('1,$read ' .. fname_tmp) eq({ '', @@ -2233,9 +2233,9 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "b"', }, nvim_eval('getline(1, "$")')) - eq(true, curbuf('get_option', 'modified')) - neq('shada', curbuf('get_option', 'filetype')) - curbuf('set_option', 'modified', false) + eq(true, nvim('get_option_value', 'modified', {buf=0})) + neq('shada', nvim('get_option_value', 'filetype', {buf=0})) + nvim('set_option_value', 'modified', false, {buf=0}) eq('++opt not supported', exc_exec('$read ++enc=latin1 ' .. fname)) eq({ '', @@ -2250,7 +2250,7 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "b"', }, nvim_eval('getline(1, "$")')) - neq(true, curbuf('get_option', 'modified')) + neq(true, nvim('get_option_value', 'modified', {buf=0})) end) it('event BufWriteCmd', function() @@ -2517,10 +2517,10 @@ describe('ftplugin/shada.vim', function() it('sets options correctly', function() nvim_command('filetype plugin indent on') nvim_command('setlocal filetype=shada') - eq(true, curbuf('get_option', 'expandtab')) - eq(2, curbuf('get_option', 'tabstop')) - eq(2, curbuf('get_option', 'softtabstop')) - eq(2, curbuf('get_option', 'shiftwidth')) + eq(true, nvim('get_option_value', 'expandtab', {buf=0})) + eq(2, nvim('get_option_value', 'tabstop', {buf=0})) + eq(2, nvim('get_option_value', 'softtabstop', {buf=0})) + eq(2, nvim('get_option_value', 'shiftwidth', {buf=0})) end) it('sets indentkeys correctly', function() -- cgit From 576dddb46168e81aa0f78c28816082c662dedea1 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Mon, 22 May 2023 12:47:10 +0600 Subject: test: don't unnecessarily specify win/buf for `nvim_(get|set)_option_value` `nvim_(get|set)_option_value` pick the current buffer / window by default for buffer-local/window-local (but not global-local) options. So specifying `buf = 0` or `win = 0` in opts is unnecessary for those options. This PR removes those to reduce code clutter. --- .../plugin/lsp/incremental_sync_spec.lua | 2 +- test/functional/plugin/shada_spec.lua | 30 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index 1dca464d74..724b3efb97 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -21,7 +21,7 @@ before_each(function () -- ["mac"] = '\r', -- } - -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {buf=0})] + -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})] function test_register(bufnr, id, offset_encoding, line_ending) diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index 43222f1904..8d37100607 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -2181,8 +2181,8 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "a"', }, nvim_eval('getline(1, "$")')) - eq(false, nvim('get_option_value', 'modified', {buf=0})) - eq('shada', nvim('get_option_value', 'filetype', {buf=0})) + eq(false, nvim('get_option_value', 'modified', {})) + eq('shada', nvim('get_option_value', 'filetype', {})) nvim_command('edit ' .. fname_tmp) eq({ 'History entry with timestamp ' .. epoch .. ':', @@ -2191,8 +2191,8 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "b"', }, nvim_eval('getline(1, "$")')) - eq(false, nvim('get_option_value', 'modified', {buf=0})) - eq('shada', nvim('get_option_value', 'filetype', {buf=0})) + eq(false, nvim('get_option_value', 'modified', {})) + eq('shada', nvim('get_option_value', 'filetype', {})) eq('++opt not supported', exc_exec('edit ++enc=latin1 ' .. fname)) neq({ 'History entry with timestamp ' .. epoch .. ':', @@ -2201,7 +2201,7 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "a"', }, nvim_eval('getline(1, "$")')) - neq(true, nvim('get_option_value', 'modified', {buf=0})) + neq(true, nvim('get_option_value', 'modified', {})) end) it('event FileReadCmd', function() @@ -2217,8 +2217,8 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "a"', }, nvim_eval('getline(1, "$")')) - eq(true, nvim('get_option_value', 'modified', {buf=0})) - neq('shada', nvim('get_option_value', 'filetype', {buf=0})) + eq(true, nvim('get_option_value', 'modified', {})) + neq('shada', nvim('get_option_value', 'filetype', {})) nvim_command('1,$read ' .. fname_tmp) eq({ '', @@ -2233,9 +2233,9 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "b"', }, nvim_eval('getline(1, "$")')) - eq(true, nvim('get_option_value', 'modified', {buf=0})) - neq('shada', nvim('get_option_value', 'filetype', {buf=0})) - nvim('set_option_value', 'modified', false, {buf=0}) + eq(true, nvim('get_option_value', 'modified', {})) + neq('shada', nvim('get_option_value', 'filetype', {})) + nvim('set_option_value', 'modified', false, {}) eq('++opt not supported', exc_exec('$read ++enc=latin1 ' .. fname)) eq({ '', @@ -2250,7 +2250,7 @@ describe('plugin/shada.vim', function() ' - contents "ab"', ' - "b"', }, nvim_eval('getline(1, "$")')) - neq(true, nvim('get_option_value', 'modified', {buf=0})) + neq(true, nvim('get_option_value', 'modified', {})) end) it('event BufWriteCmd', function() @@ -2517,10 +2517,10 @@ describe('ftplugin/shada.vim', function() it('sets options correctly', function() nvim_command('filetype plugin indent on') nvim_command('setlocal filetype=shada') - eq(true, nvim('get_option_value', 'expandtab', {buf=0})) - eq(2, nvim('get_option_value', 'tabstop', {buf=0})) - eq(2, nvim('get_option_value', 'softtabstop', {buf=0})) - eq(2, nvim('get_option_value', 'shiftwidth', {buf=0})) + eq(true, nvim('get_option_value', 'expandtab', {})) + eq(2, nvim('get_option_value', 'tabstop', {})) + eq(2, nvim('get_option_value', 'softtabstop', {})) + eq(2, nvim('get_option_value', 'shiftwidth', {})) end) it('sets indentkeys correctly', function() -- 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') 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') 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') 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/helpers.lua | 2 +- test/functional/plugin/lsp_spec.lua | 36 +++++++++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/helpers.lua b/test/functional/plugin/lsp/helpers.lua index 86f7da0d2c..15e6a62781 100644 --- a/test/functional/plugin/lsp/helpers.lua +++ b/test/functional/plugin/lsp/helpers.lua @@ -99,7 +99,7 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options, settings) end; }); workspace_folders = {{ - uri = 'file://' .. vim.loop.cwd(), + uri = 'file://' .. vim.uv.cwd(), name = 'test_folder', }}; on_init = function(client, result) 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') 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') 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 416fe8d185dcc072df7942953d867d9c605e9ffd Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Mon, 5 Jun 2023 00:19:31 -0500 Subject: refactor(lsp): use LPeg for watchfiles matching (#23788) --- test/functional/plugin/lsp/watchfiles_spec.lua | 91 ++++++++++++++++++++------ 1 file changed, 70 insertions(+), 21 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/watchfiles_spec.lua b/test/functional/plugin/lsp/watchfiles_spec.lua index c5d6803a7f..a8260e0c98 100644 --- a/test/functional/plugin/lsp/watchfiles_spec.lua +++ b/test/functional/plugin/lsp/watchfiles_spec.lua @@ -2,7 +2,6 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq local exec_lua = helpers.exec_lua -local has_err = require('luassert').has.errors describe('vim.lsp._watchfiles', function() before_each(helpers.clear) @@ -17,21 +16,31 @@ describe('vim.lsp._watchfiles', function() eq(true, match('', '')) eq(false, match('', 'a')) eq(true, match('a', 'a')) + eq(true, match('/', '/')) eq(true, match('abc', 'abc')) eq(false, match('abc', 'abcdef')) eq(false, match('abc', 'a')) + eq(false, match('abc', 'bc')) eq(false, match('a', 'b')) eq(false, match('.', 'a')) eq(true, match('$', '$')) + eq(true, match('/dir', '/dir')) + eq(true, match('dir/', 'dir/')) + eq(true, match('dir/subdir', 'dir/subdir')) + eq(false, match('dir/subdir', 'subdir')) eq(false, match('dir/subdir', 'dir/subdir/file')) + eq(true, match('🤠', '🤠')) end) it('should match * wildcards', function() - -- eq(false, match('*', '')) -- TODO: this fails + eq(false, match('*', '')) eq(true, match('*', 'a')) + eq(false, match('*', '/')) eq(false, match('*', '/a')) eq(false, match('*', 'a/')) eq(true, match('*', 'aaa')) + eq(true, match('*a', 'aa')) + eq(true, match('*a', 'abca')) eq(true, match('*.txt', 'file.txt')) eq(false, match('*.txt', 'file.txtxt')) eq(false, match('*.txt', 'dir/file.txt')) @@ -40,9 +49,31 @@ describe('vim.lsp._watchfiles', function() eq(false, match('*.dir', 'test.dir/file')) eq(true, match('file.*', 'file.txt')) eq(false, match('file.*', 'not-file.txt')) + eq(true, match('*/file.txt', 'dir/file.txt')) + eq(false, match('*/file.txt', 'dir/subdir/file.txt')) + eq(false, match('*/file.txt', '/dir/file.txt')) + eq(true, match('dir/*', 'dir/file.txt')) + eq(false, match('dir/*', 'dir')) eq(false, match('dir/*.txt', 'file.txt')) eq(true, match('dir/*.txt', 'dir/file.txt')) eq(false, match('dir/*.txt', 'dir/subdir/file.txt')) + eq(false, match('dir/*/file.txt', 'dir/file.txt')) + eq(true, match('dir/*/file.txt', 'dir/subdir/file.txt')) + eq(false, match('dir/*/file.txt', 'dir/subdir/subdir/file.txt')) + + -- TODO: The spec does not describe this, but VSCode only interprets ** when it's by + -- itself in a path segment, and otherwise interprets ** as consecutive * directives. + -- The following tests show how this behavior should work, but is not yet fully implemented. + -- Currently, "a**" parses incorrectly as "a" "**" and "**a" parses correctly as "*" "*" "a". + -- see: https://github.com/microsoft/vscode/blob/eef30e7165e19b33daa1e15e92fa34ff4a5df0d3/src/vs/base/common/glob.ts#L112 + eq(true, match('a**', 'abc')) -- '**' should parse as two '*'s when not by itself in a path segment + eq(true, match('**c', 'abc')) + -- eq(false, match('a**', 'ab')) -- each '*' should still represent at least one character + eq(false, match('**c', 'bc')) + eq(true, match('a**', 'abcd')) + eq(true, match('**d', 'abcd')) + -- eq(false, match('a**', 'abc/d')) + eq(false, match('**d', 'abc/d')) end) it('should match ? wildcards', function() @@ -58,52 +89,64 @@ describe('vim.lsp._watchfiles', function() it('should match ** wildcards', function() eq(true, match('**', '')) eq(true, match('**', 'a')) + eq(true, match('**', '/')) eq(true, match('**', 'a/')) eq(true, match('**', '/a')) eq(true, match('**', 'C:/a')) eq(true, match('**', 'a/a')) eq(true, match('**', 'a/a/a')) - eq(false, match('a**', '')) - eq(true, match('a**', 'a')) - eq(true, match('a**', 'abcd')) - eq(false, match('a**', 'ba')) - eq(false, match('a**', 'a/b')) - eq(false, match('**a', '')) - eq(true, match('**a', 'a')) - eq(true, match('**a', 'dcba')) - eq(false, match('**a', 'ab')) - eq(false, match('**a', 'b/a')) + eq(false, match('/**', '')) -- /** matches leading / literally + eq(true, match('/**', '/')) + eq(true, match('/**', '/a/b/c')) + eq(true, match('**/', '')) -- **/ absorbs trailing / + eq(true, match('**/', '/a/b/c')) + eq(true, match('**/**', '')) + eq(true, match('**/**', 'a')) eq(false, match('a/**', '')) - eq(true, match('a/**', 'a')) + eq(false, match('a/**', 'a')) eq(true, match('a/**', 'a/b')) + eq(true, match('a/**', 'a/b/c')) eq(false, match('a/**', 'b/a')) eq(false, match('a/**', '/a')) eq(false, match('**/a', '')) eq(true, match('**/a', 'a')) eq(false, match('**/a', 'a/b')) eq(true, match('**/a', '/a')) + eq(true, match('**/a', '/b/a')) + eq(true, match('**/a', '/c/b/a')) + eq(true, match('**/a', '/a/a')) + eq(true, match('**/a', '/abc/a')) eq(false, match('a/**/c', 'a')) eq(false, match('a/**/c', 'c')) eq(true, match('a/**/c', 'a/c')) eq(true, match('a/**/c', 'a/b/c')) eq(true, match('a/**/c', 'a/b/b/c')) - eq(true, match('**/a/**', 'a')) - eq(true, match('**/a/**', '/dir/a')) + eq(false, match('**/a/**', 'a')) + eq(true, match('**/a/**', 'a/')) + eq(false, match('**/a/**', '/dir/a')) + eq(false, match('**/a/**', 'dir/a')) + eq(true, match('**/a/**', 'dir/a/')) eq(true, match('**/a/**', 'a/dir')) eq(true, match('**/a/**', 'dir/a/dir')) eq(true, match('**/a/**', '/a/dir')) eq(true, match('**/a/**', 'C:/a/dir')) - -- eq(false, match('**/a/**', 'a.txt')) -- TODO: this fails + eq(false, match('**/a/**', 'a.txt')) end) it('should match {} groups', function() - eq(false, match('{}', '')) - eq(true, match('{,}', '')) + eq(true, match('{}', '')) eq(false, match('{}', 'a')) + eq(true, match('a{}', 'a')) + eq(true, match('{}a', 'a')) + eq(true, match('{,}', '')) + eq(true, match('{a,}', '')) + eq(true, match('{a,}', 'a')) eq(true, match('{a}', 'a')) eq(false, match('{a}', 'aa')) eq(false, match('{a}', 'ab')) + eq(true, match('{a?c}', 'abc')) eq(false, match('{ab}', 'a')) + eq(false, match('{ab}', 'b')) eq(true, match('{ab}', 'ab')) eq(true, match('{a,b}', 'a')) eq(true, match('{a,b}', 'b')) @@ -112,11 +155,11 @@ describe('vim.lsp._watchfiles', function() eq(false, match('{ab,cd}', 'a')) eq(true, match('{ab,cd}', 'cd')) eq(true, match('{a,b,c}', 'c')) - eq(false, match('{a,{b,c}}', 'c')) -- {} can't nest + eq(true, match('{a,{b,c}}', 'c')) end) it('should match [] groups', function() - eq(true, match('[]', '')) + eq(true, match('[]', '[]')) -- empty [] is a literal eq(false, match('[a-z]', '')) eq(true, match('[a-z]', 'a')) eq(false, match('[a-z]', 'ab')) @@ -141,7 +184,7 @@ describe('vim.lsp._watchfiles', function() end) it('should match [!...] groups', function() - has_err(function() match('[!]', '') end) -- not a valid pattern + eq(true, match('[!]', '[!]')) -- [!] is a literal eq(false, match('[!a-z]', '')) eq(false, match('[!a-z]', 'a')) eq(false, match('[!a-z]', 'z')) @@ -159,11 +202,17 @@ describe('vim.lsp._watchfiles', function() it('should match complex patterns', function() eq(false, match('**/*.{c,h}', '')) eq(false, match('**/*.{c,h}', 'c')) + eq(false, match('**/*.{c,h}', 'file.m')) eq(true, match('**/*.{c,h}', 'file.c')) eq(true, match('**/*.{c,h}', 'file.h')) eq(true, match('**/*.{c,h}', '/file.c')) eq(true, match('**/*.{c,h}', 'dir/subdir/file.c')) eq(true, match('**/*.{c,h}', 'dir/subdir/file.h')) + eq(true, match('**/*.{c,h}', '/dir/subdir/file.c')) + eq(true, match('**/*.{c,h}', 'C:/dir/subdir/file.c')) + eq(true, match('/dir/**/*.{c,h}', '/dir/file.c')) + eq(false, match('/dir/**/*.{c,h}', 'dir/file.c')) + eq(true, match('/dir/**/*.{c,h}', '/dir/subdir/subdir/file.c')) eq(true, match('{[0-9],[a-z]}', '0')) eq(true, match('{[0-9],[a-z]}', 'a')) -- cgit From cbbda3bcd77595eeabcc0fb70cee513e473833e6 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Mon, 5 Jun 2023 11:53:13 -0500 Subject: fix(editorconfig): check that buffer is valid (#23922) Fixes: https://github.com/neovim/neovim/issues/23921 --- test/functional/plugin/editorconfig_spec.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/editorconfig_spec.lua b/test/functional/plugin/editorconfig_spec.lua index 4ad9903032..5e38c03b56 100644 --- a/test/functional/plugin/editorconfig_spec.lua +++ b/test/functional/plugin/editorconfig_spec.lua @@ -5,6 +5,7 @@ local eq = helpers.eq local pathsep = helpers.get_pathsep() local funcs = helpers.funcs local meths = helpers.meths +local exec_lua = helpers.exec_lua local testdir = 'Xtest-editorconfig' @@ -206,4 +207,15 @@ But not this one test_case('3_space.txt', { shiftwidth = 42 }) test_case('4_space.py', { shiftwidth = 4 }) end) + + it('does not operate on invalid buffers', function() + local ok, err = unpack(exec_lua([[ + vim.cmd.edit('test.txt') + local bufnr = vim.api.nvim_get_current_buf() + vim.cmd.bwipeout(bufnr) + return {pcall(require('editorconfig').config, bufnr)} + ]])) + + eq(true, ok, err) + end) end) -- cgit From 4382d2ed564b80944345785d780cf1b19fb23ba8 Mon Sep 17 00:00:00 2001 From: Alexandre Teoi Date: Tue, 6 Jun 2023 12:42:26 -0300 Subject: feat(health): fold successful healthchecks #22866 Problem: checkhealth can be noisy, but we don't want to omit info. Solution: Fold OK results by default, if 'foldenable' is enabled. Resolves #22796 --- test/functional/plugin/health_spec.lua | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 488a213a9e..50b1d03f36 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -136,7 +136,7 @@ describe('health.vim', function() Bar = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGrey }, }) command("checkhealth foo success1") - command("set nowrap laststatus=0") + command("set nofoldenable nowrap laststatus=0") screen:expect{grid=[[ ^ | {Bar:──────────────────────────────────────────────────}| @@ -153,6 +153,22 @@ describe('health.vim', function() ]]} end) + it("fold healthchecks", function() + local screen = Screen.new(50, 7) + screen:attach() + command("checkhealth foo success1") + command("set nowrap laststatus=0") + screen:expect{grid=[[ + ^ | + ──────────────────────────────────────────────────| + +WE 4 lines: foo: ·······························| + ──────────────────────────────────────────────────| + +-- 8 lines: test_plug.success1: require("test_pl| + ~ | + | + ]]} + end) + it("gracefully handles invalid healthcheck", function() command("checkhealth non_existent_healthcheck") -- luacheck: ignore 613 -- 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/inlay_hint_spec.lua | 132 +++++++++++++++++++++++++ test/functional/plugin/lsp_spec.lua | 4 +- 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 test/functional/plugin/lsp/inlay_hint_spec.lua (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua new file mode 100644 index 0000000000..b134095c4f --- /dev/null +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -0,0 +1,132 @@ +local helpers = require('test.functional.helpers')(after_each) +local lsp_helpers = require('test.functional.plugin.lsp.helpers') +local Screen = require('test.functional.ui.screen') + +local dedent = helpers.dedent +local exec_lua = helpers.exec_lua +local insert = helpers.insert + +local clear_notrace = lsp_helpers.clear_notrace +local create_server_definition = lsp_helpers.create_server_definition + +before_each(function() + clear_notrace() +end) + +after_each(function() + exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") +end) + +describe('inlay hints', function() + local screen + before_each(function() + screen = Screen.new(50, 9) + screen:attach() + end) + + describe('general', function() + local text = dedent([[ + auto add(int a, int b) { return a + b; } + + int main() { + int x = 1; + int y = 2; + return add(x,y); + } + }]]) + + + local response = [==[ + [ + {"kind":1,"paddingLeft":false,"label":"-> int","position":{"character":22,"line":0},"paddingRight":false}, + {"kind":2,"paddingLeft":false,"label":"a:","position":{"character":15,"line":5},"paddingRight":true}, + {"kind":2,"paddingLeft":false,"label":"b:","position":{"character":17,"line":5},"paddingRight":true} + ] + ]==] + + + before_each(function() + exec_lua(create_server_definition) + exec_lua([[ + local response = ... + server = _create_server({ + capabilities = { + inlayHintProvider = true, + }, + handlers = { + ['textDocument/inlayHint'] = function() + return vim.json.decode(response) + end, + } + }) + ]], response) + end) + + it( + 'inlay hints are applied when vim.lsp._inlay_hint.refresh() is called', + function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + exec_lua([[vim.lsp._inlay_hint.refresh({bufnr = bufnr})]]) + screen:expect({ + grid = [[ + auto add(int a, int b)-> int { return a + b; } | + | + int main() { | + int x = 1; | + int y = 2; | + return add(a: x,b: y); | + } | + ^} | + | +]] + }) + end) + + it( + 'inlay hints are cleared when vim.lsp._inlay_hint.clear() is called', + function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + exec_lua([[vim.lsp._inlay_hint.refresh({bufnr = bufnr})]]) + screen:expect({ + grid = [[ + auto add(int a, int b)-> int { return a + b; } | + | + int main() { | + int x = 1; | + int y = 2; | + return add(a: x,b: y); | + } | + ^} | + | +]] + }) + exec_lua([[vim.lsp._inlay_hint.clear()]]) + screen:expect({ + grid = [[ + auto add(int a, int b) { return a + b; } | + | + int main() { | + int x = 1; | + int y = 2; | + return add(x,y); | + } | + ^} | + | +]], + unchanged = true + }) + end) + end) +end) 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') 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 ca5de9306c00d07cce1daef1f0038c937098bc66 Mon Sep 17 00:00:00 2001 From: Chinmay Dalal Date: Tue, 20 Jun 2023 11:36:54 +0530 Subject: feat(lsp): inlay hints #23984 Add automatic refresh and a public interface on top of #23736 * add on_reload, on_detach handlers in `enable()` buf_attach, and LspDetach autocommand in case of manual detach * unify `__buffers` and `hint_cache_by_buf` * use callback bufnr in `on_lines` callback, bufstate: remove __index override * move user-facing functions into vim.lsp.buf, unify enable/disable/toggle Closes #18086 --- test/functional/plugin/lsp/inlay_hint_spec.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua index b134095c4f..103fd8d968 100644 --- a/test/functional/plugin/lsp/inlay_hint_spec.lua +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -63,7 +63,7 @@ describe('inlay hints', function() end) it( - 'inlay hints are applied when vim.lsp._inlay_hint.refresh() is called', + 'inlay hints are applied when vim.lsp.buf.inlay_hint(true) is called', function() exec_lua([[ bufnr = vim.api.nvim_get_current_buf() @@ -72,7 +72,7 @@ describe('inlay hints', function() ]]) insert(text) - exec_lua([[vim.lsp._inlay_hint.refresh({bufnr = bufnr})]]) + exec_lua([[vim.lsp.buf.inlay_hint(bufnr, true)]]) screen:expect({ grid = [[ auto add(int a, int b)-> int { return a + b; } | @@ -89,7 +89,7 @@ describe('inlay hints', function() end) it( - 'inlay hints are cleared when vim.lsp._inlay_hint.clear() is called', + 'inlay hints are cleared when vim.lsp.buf.inlay_hint(false) is called', function() exec_lua([[ bufnr = vim.api.nvim_get_current_buf() @@ -98,7 +98,7 @@ describe('inlay hints', function() ]]) insert(text) - exec_lua([[vim.lsp._inlay_hint.refresh({bufnr = bufnr})]]) + exec_lua([[vim.lsp.buf.inlay_hint(bufnr, true)]]) screen:expect({ grid = [[ auto add(int a, int b)-> int { return a + b; } | @@ -112,7 +112,7 @@ describe('inlay hints', function() | ]] }) - exec_lua([[vim.lsp._inlay_hint.clear()]]) + exec_lua([[vim.lsp.buf.inlay_hint(bufnr, false)]]) screen:expect({ grid = [[ auto add(int a, int b) { return a + b; } | -- 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') 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 12c2c16acf7051d364d29cfd71f2542b0943d8e8 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 22 Jun 2023 19:39:57 +0200 Subject: feat(lsp): opt-in to dynamicRegistration for inlay hints (#24102) Since https://github.com/neovim/neovim/pull/23681 there is dynamic registration support. We should use that for new features unless there is a good reason to turn it off. --- test/functional/plugin/lsp/inlay_hint_spec.lua | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua index 103fd8d968..574a4fa5a0 100644 --- a/test/functional/plugin/lsp/inlay_hint_spec.lua +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local lsp_helpers = require('test.functional.plugin.lsp.helpers') local Screen = require('test.functional.ui.screen') +local eq = helpers.eq local dedent = helpers.dedent local exec_lua = helpers.exec_lua local insert = helpers.insert @@ -65,11 +66,17 @@ describe('inlay hints', function() it( 'inlay hints are applied when vim.lsp.buf.inlay_hint(true) is called', function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) + local res = exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local client = vim.lsp.get_client_by_id(client_id) + return { + supports_method = client.supports_method("textDocument/inlayHint") + } + ]]) + eq(res, { supports_method = true }) + insert(text) exec_lua([[vim.lsp.buf.inlay_hint(bufnr, true)]]) -- cgit From 37079fca58f396fd866dc7b7d87a0100c17ee760 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 30 Jun 2023 11:33:28 +0200 Subject: feat(lsp): move inlay_hint() to vim.lsp (#24130) Allows to keep more functions hidden and gives a path forward for further inlay_hint related functions - like applying textEdits. See https://github.com/neovim/neovim/pull/23984#pullrequestreview-1486624668 --- test/functional/plugin/lsp/inlay_hint_spec.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua index 574a4fa5a0..b19f2ba146 100644 --- a/test/functional/plugin/lsp/inlay_hint_spec.lua +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -64,7 +64,7 @@ describe('inlay hints', function() end) it( - 'inlay hints are applied when vim.lsp.buf.inlay_hint(true) is called', + 'inlay hints are applied when vim.lsp.inlay_hint(true) is called', function() local res = exec_lua([[ bufnr = vim.api.nvim_get_current_buf() @@ -79,7 +79,7 @@ describe('inlay hints', function() insert(text) - exec_lua([[vim.lsp.buf.inlay_hint(bufnr, true)]]) + exec_lua([[vim.lsp.inlay_hint(bufnr, true)]]) screen:expect({ grid = [[ auto add(int a, int b)-> int { return a + b; } | @@ -96,7 +96,7 @@ describe('inlay hints', function() end) it( - 'inlay hints are cleared when vim.lsp.buf.inlay_hint(false) is called', + 'inlay hints are cleared when vim.lsp.inlay_hint(false) is called', function() exec_lua([[ bufnr = vim.api.nvim_get_current_buf() @@ -105,7 +105,7 @@ describe('inlay hints', function() ]]) insert(text) - exec_lua([[vim.lsp.buf.inlay_hint(bufnr, true)]]) + exec_lua([[vim.lsp.inlay_hint(bufnr, true)]]) screen:expect({ grid = [[ auto add(int a, int b)-> int { return a + b; } | @@ -119,7 +119,7 @@ describe('inlay hints', function() | ]] }) - exec_lua([[vim.lsp.buf.inlay_hint(bufnr, false)]]) + exec_lua([[vim.lsp.inlay_hint(bufnr, false)]]) screen:expect({ grid = [[ auto add(int a, int b) { return a + b; } | -- 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') 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 251ca45ac94851c896db0d27685622fb78a73b3e Mon Sep 17 00:00:00 2001 From: Mike <10135646+mikesmithgh@users.noreply.github.com> Date: Sun, 16 Jul 2023 06:11:45 -0400 Subject: fix(lsp): markdown code fence should allow space before info string #24364 Problem: Bash language server returns "hover" markdown content that starts with a code fence and info string of `man` preceded by whitespace, which Nvim does not render properly. See https://github.com/bash-lsp/bash-language-server/blob/0ee73c53cebdc18311d4a4ad9367185ea4d98a03/server/src/server.ts#L821C15-L821C15 ```typescript function getMarkdownContent(documentation: string, language?: string): LSP.MarkupContent { return { value: language ? // eslint-disable-next-line prefer-template ['``` ' + language, documentation, '```'].join('\n') : documentation, kind: LSP.MarkupKind.Markdown, } } ``` For example, ``` ``` man NAME git - the stupid content tracker ``` ``` If I remove the white space, then it is properly formatted. ``` ```man instead of ``` man ``` Per CommonMark Spec https://spec.commonmark.org/0.30/#info-string whitespace is allowed before and after the `info string` which identifies the language in a codeblock. > The line with the opening code fence may optionally contain some text > following the code fence; this is trimmed of leading and trailing > spaces or tabs and called the [info > string](https://spec.commonmark.org/0.30/#info-string). If the [info > string](https://spec.commonmark.org/0.30/#info-string) comes after > a backtick fence, it may not contain any backtick characters. (The > reason for this restriction is that otherwise some inline code would > be incorrectly interpreted as the beginning of a fenced code block.) Solution: Adjust stylize_markdown() to allow whitespace before codeblock info. --- test/functional/plugin/lsp/utils_spec.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index 3e53b6d574..c91fffa90f 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -34,6 +34,19 @@ describe('vim.lsp.util', function() eq(expected, stylize_markdown(lines, opts)) end) + it('code fences with whitespace surrounded info string', function() + local lines = { + "``` lua ", + "local hello = 'world'", + "```", + } + local expected = { + "local hello = 'world'", + } + local opts = {} + eq(expected, stylize_markdown(lines, opts)) + end) + it('adds separator after code block', function() local lines = { "```lua", -- 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') 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 63b3408551561127f7845470eb51404bcd6f547b Mon Sep 17 00:00:00 2001 From: Chris AtLee Date: Thu, 20 Jul 2023 03:03:48 -0400 Subject: feat(lsp): implement textDocument/diagnostic (#24128) --- test/functional/plugin/lsp/diagnostic_spec.lua | 96 ++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index f58016bf01..d1c3fd6b1e 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -1,10 +1,13 @@ local helpers = require('test.functional.helpers')(after_each) +local lsp_helpers = require('test.functional.plugin.lsp.helpers') local clear = helpers.clear local exec_lua = helpers.exec_lua local eq = helpers.eq local neq = require('test.helpers').neq +local create_server_definition = lsp_helpers.create_server_definition + describe('vim.lsp.diagnostic', function() local fake_uri @@ -265,4 +268,97 @@ describe('vim.lsp.diagnostic', function() eq(exec_lua([[return #vim.diagnostic.get(...)]], bufnr), 0) end) end) + + describe('vim.lsp.diagnostic.on_diagnostic', function() + before_each(function() + exec_lua(create_server_definition) + exec_lua([[ + server = _create_server({ + capabilities = { + diagnosticProvider = { + } + } + }) + + function get_extmarks(bufnr, client_id) + local namespace = vim.lsp.diagnostic.get_namespace(client_id, true) + local ns = vim.diagnostic.get_namespace(namespace) + local extmarks = {} + if ns.user_data.virt_text_ns then + for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.virt_text_ns, 0, -1, {details=true})) do + table.insert(extmarks, e) + end + end + if ns.user_data.underline_ns then + for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.underline_ns, 0, -1, {details=true})) do + table.insert(extmarks, e) + end + end + return extmarks + end + + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + end) + + it('adds diagnostics to vim.diagnostics', function() + local diags = exec_lua([[ + vim.lsp.diagnostic.on_diagnostic(nil, + { + kind = 'full', + items = { + make_error('Pull Diagnostic', 4, 4, 4, 4), + } + }, + { + params = { + textDocument = { uri = fake_uri }, + }, + uri = fake_uri, + client_id = client_id, + }, + {} + ) + + return vim.diagnostic.get(diagnostic_bufnr) + ]]) + eq(1, #diags) + eq('Pull Diagnostic', diags[1].message) + end) + + it('allows configuring the virtual text via vim.lsp.with', function() + local expected_spacing = 10 + local extmarks = exec_lua( + [[ + Diagnostic = vim.lsp.with(vim.lsp.diagnostic.on_diagnostic, { + virtual_text = { + spacing = ..., + }, + }) + + Diagnostic(nil, + { + kind = 'full', + items = { + make_error('Pull Diagnostic', 4, 4, 4, 4), + } + }, + { + params = { + textDocument = { uri = fake_uri }, + }, + uri = fake_uri, + client_id = client_id, + }, + {} + ) + + return get_extmarks(diagnostic_bufnr, client_id) + ]], + expected_spacing + ) + eq(2, #extmarks) + eq(expected_spacing, #extmarks[1][4].virt_text[1][1]) + end) + end) 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') 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') 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 e72c0cd92090c1fc1e5665a060b3e1d0094d7f30 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Tue, 15 Aug 2023 09:25:51 -0500 Subject: feat(highlight): Allow hyphens (-) in highlight group names (#24714) Fixes: https://github.com/neovim/neovim/issues/23184 --- test/functional/plugin/lsp/semantic_tokens_spec.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index d1ffb72ef5..b7ac53f270 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -106,6 +106,7 @@ describe('semantic token highlighting', function() exec_lua([[ bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_win_set_buf(0, bufnr) + vim.bo[bufnr].filetype = 'some-filetype' client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) ]]) -- cgit From abb8c2c453d1e084f8ab3e9bbaa8b27515c81a9f Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 25 Aug 2023 12:23:05 +0100 Subject: fix(editorconfig): do not set 'endofline' Problem: 'endofline' can be used to detect if a file ends of , however editorconfig can break this. Solution: Set 'endofline' during BufWritePre Fixes: #24869 --- test/functional/plugin/editorconfig_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/editorconfig_spec.lua b/test/functional/plugin/editorconfig_spec.lua index 5e38c03b56..ac78003a8c 100644 --- a/test/functional/plugin/editorconfig_spec.lua +++ b/test/functional/plugin/editorconfig_spec.lua @@ -160,8 +160,8 @@ describe('editorconfig', function() end) it('sets newline options', function() - test_case('with_newline.txt', { fixendofline = true, endofline = true }) - test_case('without_newline.txt', { fixendofline = false, endofline = false }) + test_case('with_newline.txt', { fixendofline = true }) + test_case('without_newline.txt', { fixendofline = false }) end) it('respects trim_trailing_whitespace', function() -- cgit From c235959fd909d75248c066a781475e207606c5aa Mon Sep 17 00:00:00 2001 From: Chris AtLee Date: Thu, 31 Aug 2023 04:00:24 -0400 Subject: fix(lsp): only disable inlay hints / diagnostics if no other clients are connected (#24535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the issue where the LspNotify handlers for inlay_hint / diagnostics would end up refreshing all attached clients. The handler would call util._refresh, which called vim.lsp.buf_request, which calls the method on all attached clients. Now util._refresh takes an optional client_id parameter, which is used to specify a specific client to update. This commit also fixes util._refresh's handling of the `only_visible` flag. Previously if `only_visible` was false, two requests would be made to the server: one for the visible region, and one for the entire file. Co-authored-by: Stanislav Asunkin <1353637+stasjok@users.noreply.github.com> Co-authored-by: Mathias Fußenegger --- test/functional/plugin/lsp/diagnostic_spec.lua | 59 ++++++++++++++++ test/functional/plugin/lsp/inlay_hint_spec.lua | 98 ++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index d1c3fd6b1e..1da0222114 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -84,6 +84,7 @@ describe('vim.lsp.diagnostic', function() local lines = {"1st line of text", "2nd line of text", "wow", "cool", "more", "lines"} vim.fn.bufload(diagnostic_bufnr) vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) return diagnostic_bufnr ]], fake_uri) end) @@ -360,5 +361,63 @@ describe('vim.lsp.diagnostic', function() eq(2, #extmarks) eq(expected_spacing, #extmarks[1][4].virt_text[1][1]) end) + + it('clears diagnostics when client detaches', function() + exec_lua([[ + vim.lsp.diagnostic.on_diagnostic(nil, + { + kind = 'full', + items = { + make_error('Pull Diagnostic', 4, 4, 4, 4), + } + }, + { + params = { + textDocument = { uri = fake_uri }, + }, + uri = fake_uri, + client_id = client_id, + }, + {} + ) + ]]) + local diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]]) + eq(1, #diags) + + exec_lua([[ vim.lsp.stop_client(client_id) ]]) + + diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]]) + eq(0, #diags) + end) + + it('keeps diagnostics when one client detaches and others still are attached', function() + exec_lua([[ + client_id2 = vim.lsp.start({ name = 'dummy2', cmd = server.cmd }) + + vim.lsp.diagnostic.on_diagnostic(nil, + { + kind = 'full', + items = { + make_error('Pull Diagnostic', 4, 4, 4, 4), + } + }, + { + params = { + textDocument = { uri = fake_uri }, + }, + uri = fake_uri, + client_id = client_id, + }, + {} + ) + ]]) + local diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]]) + eq(1, #diags) + + exec_lua([[ vim.lsp.stop_client(client_id2) ]]) + + diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]]) + eq(1, #diags) + end) end) end) diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua index b19f2ba146..eec86fdb8e 100644 --- a/test/functional/plugin/lsp/inlay_hint_spec.lua +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -131,6 +131,104 @@ describe('inlay hints', function() } | ^} | | +]], + unchanged = true + }) + end) + + it( + 'inlay hints are cleared when the client detaches', + function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + exec_lua([[vim.lsp.inlay_hint(bufnr, true)]]) + screen:expect({ + grid = [[ + auto add(int a, int b)-> int { return a + b; } | + | + int main() { | + int x = 1; | + int y = 2; | + return add(a: x,b: y); | + } | + ^} | + | +]] + }) + exec_lua([[vim.lsp.stop_client(client_id)]]) + screen:expect({ + grid = [[ + auto add(int a, int b) { return a + b; } | + | + int main() { | + int x = 1; | + int y = 2; | + return add(x,y); | + } | + ^} | + | +]], + unchanged = true + }) + end) + + it( + 'inlay hints are not cleared when one of several clients detaches', + function() + -- Start two clients + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + server2 = _create_server({ + capabilities = { + inlayHintProvider = true, + }, + handlers = { + ['textDocument/inlayHint'] = function() + return {} + end, + } + }) + client1 = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd }) + ]]) + + insert(text) + exec_lua([[vim.lsp.inlay_hint(bufnr, true)]]) + screen:expect({ + grid = [[ + auto add(int a, int b)-> int { return a + b; } | + | + int main() { | + int x = 1; | + int y = 2; | + return add(a: x,b: y); | + } | + ^} | + | +]] + }) + + -- Now stop one client + exec_lua([[ vim.lsp.stop_client(client2) ]]) + + -- We should still see the hints + screen:expect({ + grid = [[ + auto add(int a, int b)-> int { return a + b; } | + | + int main() { | + int x = 1; | + int y = 2; | + return add(a: x,b: y); | + } | + ^} | + | ]], unchanged = true }) -- 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') 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 5e3cf9fb4bc7f236c858f609ca83c57fb1779ab0 Mon Sep 17 00:00:00 2001 From: Grace Petryk Date: Sun, 10 Sep 2023 01:02:23 -0700 Subject: feat(lsp): improve control over placement of floating windows (#24494) --- test/functional/plugin/lsp/utils_spec.lua | 96 +++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index c91fffa90f..f544255d81 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -1,4 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local feed = helpers.feed local eq = helpers.eq local exec_lua = helpers.exec_lua @@ -85,4 +87,98 @@ describe('vim.lsp.util', function() eq(expected, stylize_markdown(lines, opts)) end) end) + + describe("make_floating_popup_options", function () + + local function assert_anchor(anchor_bias, expected_anchor) + local opts = exec_lua([[ + local args = { ... } + local anchor_bias = args[1] + return vim.lsp.util.make_floating_popup_options(30, 10, { anchor_bias = anchor_bias }) + ]], anchor_bias) + + eq(expected_anchor, string.sub(opts.anchor, 1, 1)) + end + + local screen + before_each(function () + helpers.clear() + screen = Screen.new(80, 80) + screen:attach() + feed("79i") -- fill screen with empty lines + end) + + describe('when on the first line it places window below', function () + before_each(function () + feed('gg') + end) + + it('for anchor_bias = "auto"', function () + assert_anchor('auto', 'N') + end) + + it('for anchor_bias = "above"', function () + assert_anchor('above', 'N') + end) + + it('for anchor_bias = "below"', function () + assert_anchor('below', 'N') + end) + end) + + describe('when on the last line it places window above', function () + before_each(function () + feed('G') + end) + + it('for anchor_bias = "auto"', function () + assert_anchor('auto', 'S') + end) + + it('for anchor_bias = "above"', function () + assert_anchor('above', 'S') + end) + + it('for anchor_bias = "below"', function () + assert_anchor('below', 'S') + end) + end) + + describe('with 20 lines above, 59 lines below', function () + before_each(function () + feed('gg20j') + end) + + it('places window below for anchor_bias = "auto"', function () + assert_anchor('auto', 'N') + end) + + it('places window above for anchor_bias = "above"', function () + assert_anchor('above', 'S') + end) + + it('places window below for anchor_bias = "below"', function () + assert_anchor('below', 'N') + end) + end) + + describe('with 59 lines above, 20 lines below', function () + before_each(function () + feed('G20k') + end) + + it('places window above for anchor_bias = "auto"', function () + assert_anchor('auto', 'S') + end) + + it('places window above for anchor_bias = "above"', function () + assert_anchor('above', 'S') + end) + + it('places window below for anchor_bias = "below"', function () + assert_anchor('below', 'N') + end) + end) + end) + end) -- 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') 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 054839437e63a7cc985e266052a1539c560a0682 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 17 Sep 2023 06:07:53 +0800 Subject: test(plugin/man_spec): use pesc() on actual_file in pattern (#25199) --- test/functional/plugin/man_spec.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index d5c1a78fc8..815ddbc523 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -9,6 +9,7 @@ local matches = helpers.matches local write_file = helpers.write_file local tmpname = helpers.tmpname local eq = helpers.eq +local pesc = helpers.pesc local skip = helpers.skip local is_ci = helpers.is_ci @@ -189,7 +190,7 @@ describe(':Man', function() write_file(actual_file, '') local args = {nvim_prog, '--headless', '+:Man ' .. actual_file, '+q'} matches(('Error detected while processing command line:\r\n' .. - 'man.lua: "no manual entry for %s"'):format(actual_file), + 'man.lua: "no manual entry for %s"'):format(pesc(actual_file)), funcs.system(args, {''})) os.remove(actual_file) end) -- 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') 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 b2265bb72c268c95180dc92c129be11fd87f995d Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Tue, 19 Sep 2023 21:30:22 -0700 Subject: test(lsp): add normalize_markdown tests --- test/functional/plugin/lsp/utils_spec.lua | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index f544255d81..12763cfef5 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -88,6 +88,40 @@ describe('vim.lsp.util', function() end) end) + describe('normalize_markdown', function () + it('collapses consecutive blank lines', function () + local result = exec_lua [[ + local lines = { + 'foo', + '', + '', + '', + 'bar', + '', + 'baz' + } + return vim.lsp.util._normalize_markdown(lines) + ]] + local expected = {'foo', '', 'bar', '', 'baz'} + eq(expected, result) + end) + + it('removes preceding and trailing empty lines', function () + local result = exec_lua [[ + local lines = { + '', + 'foo', + 'bar', + '', + '' + } + return vim.lsp.util._normalize_markdown(lines) + ]] + local expected = {'foo', 'bar'} + eq(expected, result) + end) + end) + describe("make_floating_popup_options", function () local function assert_anchor(anchor_bias, expected_anchor) -- 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') 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 bc6fc0123d2f02b1f209cbec740665033fbb5892 Mon Sep 17 00:00:00 2001 From: Leonardo Mello Date: Tue, 26 Sep 2023 10:31:35 -0300 Subject: fix(tutor): Tutor steps don't work on Windows #25251 Problem: Some steps in :Tutor don't work on Windows. Solution: Add support for `{unix:...,win:...}` format and transform the Tutor contents depending on the platform. Fix https://github.com/neovim/neovim/issues/24166 --- test/functional/plugin/tutor_spec.lua | 94 +++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 test/functional/plugin/tutor_spec.lua (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/tutor_spec.lua b/test/functional/plugin/tutor_spec.lua new file mode 100644 index 0000000000..5c84db6d4b --- /dev/null +++ b/test/functional/plugin/tutor_spec.lua @@ -0,0 +1,94 @@ +local Screen = require('test.functional.ui.screen') +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local feed = helpers.feed +local is_os = helpers.is_os + +describe(':Tutor', function() + before_each(function() + clear({ args = { '-u', 'NORC' } }) + command('set cmdheight=0') + command('Tutor') + end) + + it('should apply transformation', function() + local expected = is_os('win') and [[ + {0: }^ | + {0: } 3. To verify that a file was retrieved, cursor back and notice that there | + {0: } are now two copies of Lesson 5.3, the original and the retrieved version. | + {0: } | + {0: }{1:NOTE}: You can also read the output of an external command. For example, | + {0: } | + {0: } :r {4:!}dir | + {0: } | + {0: } reads the output of the ls command and puts it below the cursor. | + {0: } | + {0: }{3:#}{5: Lesson 5 SUMMARY} | + {0: } | + {0: } 1. {2::!command} executes an external command. | + {0: } | + {0: } Some useful examples are: | + {0: } :{4:!}dir - shows a directory listing | + {0: } :{4:!}del FILENAME - removes file FILENAME | + {0: } | + {0: } 2. {2::w} FILENAME writes the current Neovim file to disk with | + {0: } name FILENAME. | + {0: } | + {0: } 3. {2:v} motion :w FILENAME saves the Visually selected lines in file | + {0: } FILENAME. | + {0: } | + {0: } 4. {2::r} FILENAME retrieves disk file FILENAME and puts it | + {0: } below the cursor position. | + {0: } | + {0: } 5. {2::r !dir} reads the output of the dir command and | + {0: } puts it below the cursor position. | + {0: } | + ]] or [[ + {0: }^ | + {0: } 3. To verify that a file was retrieved, cursor back and notice that there | + {0: } are now two copies of Lesson 5.3, the original and the retrieved version. | + {0: } | + {0: }{1:NOTE}: You can also read the output of an external command. For example, | + {0: } | + {0: } :r {4:!}ls | + {0: } | + {0: } reads the output of the ls command and puts it below the cursor. | + {0: } | + {0: }{3:#}{5: Lesson 5 SUMMARY} | + {0: } | + {0: } 1. {2::!command} executes an external command. | + {0: } | + {0: } Some useful examples are: | + {0: } :{4:!}ls - shows a directory listing | + {0: } :{4:!}rm FILENAME - removes file FILENAME | + {0: } | + {0: } 2. {2::w} FILENAME writes the current Neovim file to disk with | + {0: } name FILENAME. | + {0: } | + {0: } 3. {2:v} motion :w FILENAME saves the Visually selected lines in file | + {0: } FILENAME. | + {0: } | + {0: } 4. {2::r} FILENAME retrieves disk file FILENAME and puts it | + {0: } below the cursor position. | + {0: } | + {0: } 5. {2::r !ls} reads the output of the ls command and | + {0: } puts it below the cursor position. | + {0: } | + ]] + + local screen = Screen.new(80, 30) + screen:set_default_attr_ids({ + [0] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray }, + [1] = { bold = true }, + [2] = { underline = true, foreground = tonumber('0x0088ff') }, + [3] = { foreground = Screen.colors.SlateBlue }, + [4] = { bold = true, foreground = Screen.colors.Brown }, + [5] = { bold = true, foreground = Screen.colors.Magenta1 }, + }) + screen:attach() + + feed(':700z') + screen:expect(expected) + end) +end) -- cgit From de9348978999aa78f8351b5f55930bb109e742f7 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 26 Sep 2023 06:41:53 -0700 Subject: refactor(tutor): cleanup --- test/functional/plugin/tutor_spec.lua | 141 +++++++++++++++++----------------- 1 file changed, 71 insertions(+), 70 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/tutor_spec.lua b/test/functional/plugin/tutor_spec.lua index 5c84db6d4b..bd214e9c03 100644 --- a/test/functional/plugin/tutor_spec.lua +++ b/test/functional/plugin/tutor_spec.lua @@ -6,78 +6,13 @@ local feed = helpers.feed local is_os = helpers.is_os describe(':Tutor', function() + local screen + before_each(function() - clear({ args = { '-u', 'NORC' } }) + clear({ args = { '--clean' } }) command('set cmdheight=0') command('Tutor') - end) - - it('should apply transformation', function() - local expected = is_os('win') and [[ - {0: }^ | - {0: } 3. To verify that a file was retrieved, cursor back and notice that there | - {0: } are now two copies of Lesson 5.3, the original and the retrieved version. | - {0: } | - {0: }{1:NOTE}: You can also read the output of an external command. For example, | - {0: } | - {0: } :r {4:!}dir | - {0: } | - {0: } reads the output of the ls command and puts it below the cursor. | - {0: } | - {0: }{3:#}{5: Lesson 5 SUMMARY} | - {0: } | - {0: } 1. {2::!command} executes an external command. | - {0: } | - {0: } Some useful examples are: | - {0: } :{4:!}dir - shows a directory listing | - {0: } :{4:!}del FILENAME - removes file FILENAME | - {0: } | - {0: } 2. {2::w} FILENAME writes the current Neovim file to disk with | - {0: } name FILENAME. | - {0: } | - {0: } 3. {2:v} motion :w FILENAME saves the Visually selected lines in file | - {0: } FILENAME. | - {0: } | - {0: } 4. {2::r} FILENAME retrieves disk file FILENAME and puts it | - {0: } below the cursor position. | - {0: } | - {0: } 5. {2::r !dir} reads the output of the dir command and | - {0: } puts it below the cursor position. | - {0: } | - ]] or [[ - {0: }^ | - {0: } 3. To verify that a file was retrieved, cursor back and notice that there | - {0: } are now two copies of Lesson 5.3, the original and the retrieved version. | - {0: } | - {0: }{1:NOTE}: You can also read the output of an external command. For example, | - {0: } | - {0: } :r {4:!}ls | - {0: } | - {0: } reads the output of the ls command and puts it below the cursor. | - {0: } | - {0: }{3:#}{5: Lesson 5 SUMMARY} | - {0: } | - {0: } 1. {2::!command} executes an external command. | - {0: } | - {0: } Some useful examples are: | - {0: } :{4:!}ls - shows a directory listing | - {0: } :{4:!}rm FILENAME - removes file FILENAME | - {0: } | - {0: } 2. {2::w} FILENAME writes the current Neovim file to disk with | - {0: } name FILENAME. | - {0: } | - {0: } 3. {2:v} motion :w FILENAME saves the Visually selected lines in file | - {0: } FILENAME. | - {0: } | - {0: } 4. {2::r} FILENAME retrieves disk file FILENAME and puts it | - {0: } below the cursor position. | - {0: } | - {0: } 5. {2::r !ls} reads the output of the ls command and | - {0: } puts it below the cursor position. | - {0: } | - ]] - - local screen = Screen.new(80, 30) + screen = Screen.new(80, 30) screen:set_default_attr_ids({ [0] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray }, [1] = { bold = true }, @@ -87,8 +22,74 @@ describe(':Tutor', function() [5] = { bold = true, foreground = Screen.colors.Magenta1 }, }) screen:attach() + end) + + it('applies {unix:…,win:…} transform', function() + local expected = is_os('win') and [[ + {0: }^ | + {0: } 3. To verify that a file was retrieved, cursor back and notice that there | + {0: } are now two copies of Lesson 5.3, the original and the retrieved version. | + {0: } | + {0: }{1:NOTE}: You can also read the output of an external command. For example, | + {0: } | + {0: } :r {4:!}dir | + {0: } | + {0: } reads the output of the ls command and puts it below the cursor. | + {0: } | + {0: }{3:#}{5: Lesson 5 SUMMARY} | + {0: } | + {0: } 1. {2::!command} executes an external command. | + {0: } | + {0: } Some useful examples are: | + {0: } :{4:!}dir - shows a directory listing | + {0: } :{4:!}del FILENAME - removes file FILENAME | + {0: } | + {0: } 2. {2::w} FILENAME writes the current Neovim file to disk with | + {0: } name FILENAME. | + {0: } | + {0: } 3. {2:v} motion :w FILENAME saves the Visually selected lines in file | + {0: } FILENAME. | + {0: } | + {0: } 4. {2::r} FILENAME retrieves disk file FILENAME and puts it | + {0: } below the cursor position. | + {0: } | + {0: } 5. {2::r !dir} reads the output of the dir command and | + {0: } puts it below the cursor position. | + {0: } | + ]] or [[ + {0: }^ | + {0: } 3. To verify that a file was retrieved, cursor back and notice that there | + {0: } are now two copies of Lesson 5.3, the original and the retrieved version. | + {0: } | + {0: }{1:NOTE}: You can also read the output of an external command. For example, | + {0: } | + {0: } :r {4:!}ls | + {0: } | + {0: } reads the output of the ls command and puts it below the cursor. | + {0: } | + {0: }{3:#}{5: Lesson 5 SUMMARY} | + {0: } | + {0: } 1. {2::!command} executes an external command. | + {0: } | + {0: } Some useful examples are: | + {0: } :{4:!}ls - shows a directory listing | + {0: } :{4:!}rm FILENAME - removes file FILENAME | + {0: } | + {0: } 2. {2::w} FILENAME writes the current Neovim file to disk with | + {0: } name FILENAME. | + {0: } | + {0: } 3. {2:v} motion :w FILENAME saves the Visually selected lines in file | + {0: } FILENAME. | + {0: } | + {0: } 4. {2::r} FILENAME retrieves disk file FILENAME and puts it | + {0: } below the cursor position. | + {0: } | + {0: } 5. {2::r !ls} reads the output of the ls command and | + {0: } puts it below the cursor position. | + {0: } | + ]] - feed(':700z') + feed(':700zt') screen:expect(expected) end) end) -- 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') 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/snippet_spec.lua | 243 +++++++++------------------- test/functional/plugin/lsp_spec.lua | 4 +- 2 files changed, 82 insertions(+), 165 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/snippet_spec.lua b/test/functional/plugin/lsp/snippet_spec.lua index 7903885420..13df861b91 100644 --- a/test/functional/plugin/lsp/snippet_spec.lua +++ b/test/functional/plugin/lsp/snippet_spec.lua @@ -1,130 +1,70 @@ local helpers = require('test.functional.helpers')(after_each) -local snippet = require('vim.lsp._snippet') +local snippet = require('vim.lsp._snippet_grammar') local eq = helpers.eq local exec_lua = helpers.exec_lua -describe('vim.lsp._snippet', function() +describe('vim.lsp._snippet_grammar', function() before_each(helpers.clear) after_each(helpers.clear) local parse = function(...) - return exec_lua('return require("vim.lsp._snippet").parse(...)', ...) + local res = exec_lua('return require("vim.lsp._snippet_grammar").parse(...)', ...) + return res.data.children end - it('should parse only text', function() + it('parses only text', function() eq({ - type = snippet.NodeType.SNIPPET, - children = { - { - type = snippet.NodeType.TEXT, - raw = 'TE\\$\\}XT', - esc = 'TE$}XT', - }, - }, + { type = snippet.NodeType.Text, data = { text = 'TE$}XT' } }, }, parse('TE\\$\\}XT')) end) - it('should parse tabstop', function() + it('parses tabstops', function() eq({ - type = snippet.NodeType.SNIPPET, - children = { - { - type = snippet.NodeType.TABSTOP, - tabstop = 1, - }, - { - type = snippet.NodeType.TABSTOP, - tabstop = 2, - }, - }, + { type = snippet.NodeType.Tabstop, data = { tabstop = 1 } }, + { type = snippet.NodeType.Tabstop, data = { tabstop = 2 } }, }, parse('$1${2}')) end) - it('should parse placeholders', function() + it('parses nested placeholders', function() eq({ - type = snippet.NodeType.SNIPPET, - children = { - { - type = snippet.NodeType.PLACEHOLDER, + { + type = snippet.NodeType.Placeholder, + data = { tabstop = 1, - children = { - { - type = snippet.NodeType.PLACEHOLDER, + value = { + type = snippet.NodeType.Placeholder, + data = { tabstop = 2, - children = { - { - type = snippet.NodeType.TEXT, - raw = 'TE\\$\\}XT', - esc = 'TE$}XT', - }, - { - type = snippet.NodeType.TABSTOP, - tabstop = 3, - }, - { - type = snippet.NodeType.TABSTOP, - tabstop = 1, - transform = { - type = snippet.NodeType.TRANSFORM, - pattern = 'regex', - option = 'i', - format = { - { - type = snippet.NodeType.FORMAT, - capture_index = 1, - modifier = 'upcase', - }, - }, - }, - }, - { - type = snippet.NodeType.TEXT, - raw = 'TE\\$\\}XT', - esc = 'TE$}XT', - }, - }, + value = { type = snippet.NodeType.Tabstop, data = { tabstop = 3 } }, }, }, }, }, - }, parse('${1:${2:TE\\$\\}XT$3${1/regex/${1:/upcase}/i}TE\\$\\}XT}}')) + }, parse('${1:${2:${3}}}')) end) - it('should parse variables', function() + it('parses variables', function() eq({ - type = snippet.NodeType.SNIPPET, - children = { - { - type = snippet.NodeType.VARIABLE, - name = 'VAR', - }, - { - type = snippet.NodeType.VARIABLE, + { type = snippet.NodeType.Variable, data = { name = 'VAR' } }, + { type = snippet.NodeType.Variable, data = { name = 'VAR' } }, + { + type = snippet.NodeType.Variable, + data = { name = 'VAR', + default = { type = snippet.NodeType.Tabstop, data = { tabstop = 1 } }, }, - { - type = snippet.NodeType.VARIABLE, + }, + { + type = snippet.NodeType.Variable, + data = { name = 'VAR', - children = { + regex = 'regex', + options = '', + format = { { - type = snippet.NodeType.TABSTOP, - tabstop = 1, - }, - }, - }, - { - type = snippet.NodeType.VARIABLE, - name = 'VAR', - transform = { - type = snippet.NodeType.TRANSFORM, - pattern = 'regex', - format = { - { - type = snippet.NodeType.FORMAT, - capture_index = 1, - modifier = 'upcase', - }, + type = snippet.NodeType.Format, + data = { capture = 1, modifier = 'upcase' }, }, }, }, @@ -132,105 +72,82 @@ describe('vim.lsp._snippet', function() }, parse('$VAR${VAR}${VAR:$1}${VAR/regex/${1:/upcase}/}')) end) - it('should parse choice', function() + it('parses choice', function() eq({ - type = snippet.NodeType.SNIPPET, - children = { - { - type = snippet.NodeType.CHOICE, - tabstop = 1, - items = { - ',', - '|', - }, - }, + { + type = snippet.NodeType.Choice, + data = { tabstop = 1, values = { ',', '|' } }, }, }, parse('${1|\\,,\\||}')) end) - it('should parse format', function() - eq({ - type = snippet.NodeType.SNIPPET, - children = { + it('parses format', function() + eq( + { { - type = snippet.NodeType.VARIABLE, - name = 'VAR', - transform = { - type = snippet.NodeType.TRANSFORM, - pattern = 'regex', + type = snippet.NodeType.Variable, + data = { + name = 'VAR', + regex = 'regex', + options = '', format = { { - type = snippet.NodeType.FORMAT, - capture_index = 1, - modifier = 'upcase', + type = snippet.NodeType.Format, + data = { capture = 1, modifier = 'upcase' }, }, { - type = snippet.NodeType.FORMAT, - capture_index = 1, - if_text = 'if_text', - else_text = '', + type = snippet.NodeType.Format, + data = { capture = 1, if_text = 'if_text' }, }, { - type = snippet.NodeType.FORMAT, - capture_index = 1, - if_text = '', - else_text = 'else_text', + type = snippet.NodeType.Format, + data = { capture = 1, else_text = 'else_text' }, }, { - type = snippet.NodeType.FORMAT, - capture_index = 1, - else_text = 'else_text', - if_text = 'if_text', + type = snippet.NodeType.Format, + data = { capture = 1, if_text = 'if_text', else_text = 'else_text' }, }, { - type = snippet.NodeType.FORMAT, - capture_index = 1, - if_text = '', - else_text = 'else_text', + type = snippet.NodeType.Format, + data = { capture = 1, else_text = 'else_text' }, }, }, }, }, }, - }, parse('${VAR/regex/${1:/upcase}${1:+if_text}${1:-else_text}${1:?if_text:else_text}${1:else_text}/}')) + parse( + '${VAR/regex/${1:/upcase}${1:+if_text}${1:-else_text}${1:?if_text:else_text}${1:else_text}/}' + ) + ) end) - it('should parse empty strings', function() + it('parses empty strings', function() eq({ - children = { - { - children = { { - esc = '', - raw = '', - type = 7, - } }, + { + type = snippet.NodeType.Placeholder, + data = { tabstop = 1, - type = 2, - }, - { - esc = ' ', - raw = ' ', - type = 7, + value = { type = snippet.NodeType.Text, data = { text = '' } }, }, - { + }, + { + type = snippet.NodeType.Text, + data = { text = ' ' }, + }, + { + type = snippet.NodeType.Variable, + data = { name = 'VAR', - transform = { - format = { - { - capture_index = 1, - else_text = '', - if_text = '', - type = 6, - }, + regex = 'erg', + format = { + { + type = snippet.NodeType.Format, + data = { capture = 1, if_text = '' }, }, - option = 'g', - pattern = 'erg', - type = 5, }, - type = 3, + options = 'g', }, }, - type = 0, - }, parse('${1:} ${VAR/erg/${1:?:}/g}')) + }, parse('${1:} ${VAR/erg/${1:+}/g}')) end) end) 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 9abced6ad95f6300ae80cd8b8aa124ebcf511b50 Mon Sep 17 00:00:00 2001 From: LW Date: Sun, 8 Oct 2023 01:09:25 -0700 Subject: fix(lsp): account for border height in max floating popup height (#25539) --- test/functional/plugin/lsp/utils_spec.lua | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index 12763cfef5..804dc32f0d 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -212,6 +212,14 @@ describe('vim.lsp.util', function() it('places window below for anchor_bias = "below"', function () assert_anchor('below', 'N') end) + + it('bordered window truncates dimensions correctly', function () + local opts = exec_lua([[ + return vim.lsp.util.make_floating_popup_options(100, 100, { border = 'single' }) + ]]) + + eq(56, opts.height) + end) end) end) -- 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') 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 ee156ca60ede95c95160cb8dff0197d40cb1a491 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sat, 14 Oct 2023 00:06:40 -0700 Subject: fix(lsp): refactor escaping snippet text (#25611) --- test/functional/plugin/lsp/snippet_spec.lua | 66 ++++++++++++++++++----------- 1 file changed, 42 insertions(+), 24 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/snippet_spec.lua b/test/functional/plugin/lsp/snippet_spec.lua index 13df861b91..ba8bc7fe04 100644 --- a/test/functional/plugin/lsp/snippet_spec.lua +++ b/test/functional/plugin/lsp/snippet_spec.lua @@ -1,5 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) local snippet = require('vim.lsp._snippet_grammar') +local type = snippet.NodeType local eq = helpers.eq local exec_lua = helpers.exec_lua @@ -15,28 +16,28 @@ describe('vim.lsp._snippet_grammar', function() it('parses only text', function() eq({ - { type = snippet.NodeType.Text, data = { text = 'TE$}XT' } }, + { type = type.Text, data = { text = 'TE$}XT' } }, }, parse('TE\\$\\}XT')) end) it('parses tabstops', function() eq({ - { type = snippet.NodeType.Tabstop, data = { tabstop = 1 } }, - { type = snippet.NodeType.Tabstop, data = { tabstop = 2 } }, + { type = type.Tabstop, data = { tabstop = 1 } }, + { type = type.Tabstop, data = { tabstop = 2 } }, }, parse('$1${2}')) end) it('parses nested placeholders', function() eq({ { - type = snippet.NodeType.Placeholder, + type = type.Placeholder, data = { tabstop = 1, value = { - type = snippet.NodeType.Placeholder, + type = type.Placeholder, data = { tabstop = 2, - value = { type = snippet.NodeType.Tabstop, data = { tabstop = 3 } }, + value = { type = type.Tabstop, data = { tabstop = 3 } }, }, }, }, @@ -46,24 +47,24 @@ describe('vim.lsp._snippet_grammar', function() it('parses variables', function() eq({ - { type = snippet.NodeType.Variable, data = { name = 'VAR' } }, - { type = snippet.NodeType.Variable, data = { name = 'VAR' } }, + { type = type.Variable, data = { name = 'VAR' } }, + { type = type.Variable, data = { name = 'VAR' } }, { - type = snippet.NodeType.Variable, + type = type.Variable, data = { name = 'VAR', - default = { type = snippet.NodeType.Tabstop, data = { tabstop = 1 } }, + default = { type = type.Tabstop, data = { tabstop = 1 } }, }, }, { - type = snippet.NodeType.Variable, + type = type.Variable, data = { name = 'VAR', regex = 'regex', options = '', format = { { - type = snippet.NodeType.Format, + type = type.Format, data = { capture = 1, modifier = 'upcase' }, }, }, @@ -75,7 +76,7 @@ describe('vim.lsp._snippet_grammar', function() it('parses choice', function() eq({ { - type = snippet.NodeType.Choice, + type = type.Choice, data = { tabstop = 1, values = { ',', '|' } }, }, }, parse('${1|\\,,\\||}')) @@ -85,30 +86,30 @@ describe('vim.lsp._snippet_grammar', function() eq( { { - type = snippet.NodeType.Variable, + type = type.Variable, data = { name = 'VAR', regex = 'regex', options = '', format = { { - type = snippet.NodeType.Format, + type = type.Format, data = { capture = 1, modifier = 'upcase' }, }, { - type = snippet.NodeType.Format, + type = type.Format, data = { capture = 1, if_text = 'if_text' }, }, { - type = snippet.NodeType.Format, + type = type.Format, data = { capture = 1, else_text = 'else_text' }, }, { - type = snippet.NodeType.Format, + type = type.Format, data = { capture = 1, if_text = 'if_text', else_text = 'else_text' }, }, { - type = snippet.NodeType.Format, + type = type.Format, data = { capture = 1, else_text = 'else_text' }, }, }, @@ -124,24 +125,24 @@ describe('vim.lsp._snippet_grammar', function() it('parses empty strings', function() eq({ { - type = snippet.NodeType.Placeholder, + type = type.Placeholder, data = { tabstop = 1, - value = { type = snippet.NodeType.Text, data = { text = '' } }, + value = { type = type.Text, data = { text = '' } }, }, }, { - type = snippet.NodeType.Text, + type = type.Text, data = { text = ' ' }, }, { - type = snippet.NodeType.Variable, + type = type.Variable, data = { name = 'VAR', regex = 'erg', format = { { - type = snippet.NodeType.Format, + type = type.Format, data = { capture = 1, if_text = '' }, }, }, @@ -150,4 +151,21 @@ describe('vim.lsp._snippet_grammar', function() }, }, parse('${1:} ${VAR/erg/${1:+}/g}')) end) + + it('parses closing curly brace as text', function() + eq( + { + { type = type.Text, data = { text = 'function ' } }, + { type = type.Tabstop, data = { tabstop = 1 } }, + { type = type.Text, data = { text = '() {\n ' } }, + { type = type.Tabstop, data = { tabstop = 0 } }, + { type = type.Text, data = { text = '\n}' } }, + }, + parse(table.concat({ + 'function $1() {', + ' $0', + '}', + }, '\n')) + ) + end) end) -- 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') 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/completion_spec.lua | 183 +++++++++++++++++++++++++ test/functional/plugin/lsp_spec.lua | 47 ------- 2 files changed, 183 insertions(+), 47 deletions(-) create mode 100644 test/functional/plugin/lsp/completion_spec.lua (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua new file mode 100644 index 0000000000..aa13d6f4b1 --- /dev/null +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -0,0 +1,183 @@ +---@diagnostic disable: no-unknown +local helpers = require('test.functional.helpers')(after_each) +local eq = helpers.eq +local exec_lua = helpers.exec_lua + + +--- Convert completion results. +--- +---@param line string line contents. Mark cursor position with `|` +---@param candidates lsp.CompletionList|lsp.CompletionItem[] +---@param lnum? integer 0-based, defaults to 0 +---@return {items: table[], server_start_boundary: integer?} +local function complete(line, candidates, lnum) + lnum = lnum or 0 + local cursor_col = line:find("|") + line = line:gsub("|", "") + return exec_lua([[ + local line, cursor_col, lnum, result = ... + local line_to_cursor = line:sub(1, cursor_col) + local client_start_boundary = vim.fn.match(line_to_cursor, '\\k*$') + local items, server_start_boundary = require("vim.lsp._completion")._convert_results( + line, + lnum, + client_start_boundary, + nil, + result, + "utf-16" + ) + return { + items = items, + server_start_boundary = server_start_boundary + } + ]], line, cursor_col, lnum, candidates) +end + + +describe("vim.lsp._completion", function() + before_each(helpers.clear) + + -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion + it('prefers textEdit over label as word', function() + local range0 = { + start = { line = 0, character = 0 }, + ["end"] = { line = 0, character = 0 }, + } + local completion_list = { + -- resolves into label + { label = 'foobar', sortText = 'a', documentation = 'documentation' }, + { + label = 'foobar', + sortText = 'b', + documentation = { value = 'documentation' }, + }, + -- resolves into insertText + { label = 'foocar', sortText = 'c', insertText = 'foobar' }, + { label = 'foocar', sortText = 'd', insertText = 'foobar' }, + -- resolves into textEdit.newText + { label = 'foocar', sortText = 'e', insertText = 'foodar', textEdit = { newText = 'foobar', range = range0 } }, + { label = 'foocar', sortText = 'f', textEdit = { newText = 'foobar', range = range0 } }, + -- real-world snippet text + { + label = 'foocar', + sortText = 'g', + insertText = 'foodar', + insertTextFormat = 2, + textEdit = { newText = 'foobar(${1:place holder}, ${2:more ...holder{\\}})', range = range0 }, + }, + { + label = 'foocar', + sortText = 'h', + insertText = 'foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', + insertTextFormat = 2, + }, + -- nested snippet tokens + { + label = 'foocar', + sortText = 'i', + insertText = 'foodar(${1:${2|typ1,typ2|}}) {$0\\}', + insertTextFormat = 2, + }, + -- braced tabstop + { label = 'foocar', sortText = 'j', insertText = 'foodar()${0}', insertTextFormat = 2}, + -- plain text + { + label = 'foocar', + sortText = 'k', + insertText = 'foodar(${1:var1})', + insertTextFormat = 1, + }, + } + local expected = { + { + abbr = 'foobar', + word = 'foobar', + }, + { + abbr = 'foobar', + word = 'foobar', + }, + { + abbr = 'foocar', + word = 'foobar', + }, + { + abbr = 'foocar', + word = 'foobar', + }, + { + abbr = 'foocar', + word = 'foobar', + }, + { + abbr = 'foocar', + word = 'foobar', + }, + { + abbr = 'foocar', + word = 'foobar(place holder, more ...holder{})', + }, + { + abbr = 'foocar', + word = 'foodar(var1 typ1, var2 *typ2) {}', + }, + { + abbr = 'foocar', + word = 'foodar(typ1) {}', + }, + { + abbr = 'foocar', + word = 'foodar()', + }, + { + abbr = 'foocar', + word = 'foodar(${1:var1})', + }, + } + local result = complete('|', completion_list) + result = vim.tbl_map(function(x) + return { + abbr = x.abbr, + word = x.word + } + end, result.items) + eq(expected, result) + end) + it("uses correct start boundary", function() + local completion_list = { + isIncomplete = false, + items = { + { + filterText = "this_thread", + insertText = "this_thread", + insertTextFormat = 1, + kind = 9, + label = " this_thread", + score = 1.3205767869949, + sortText = "4056f757this_thread", + textEdit = { + newText = "this_thread", + range = { + start = { line = 0, character = 7 }, + ["end"] = { line = 0, character = 11 }, + }, + } + }, + } + } + local expected = { + abbr = ' this_thread', + dup = 1, + empty = 1, + icase = 1, + kind = 'Module', + menu = '', + word = 'this_thread', + } + local result = complete(" std::this|", completion_list) + eq(7, result.server_start_boundary) + local item = result.items[1] + item.user_data = nil + eq(expected, item) + end) +end) 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 ba6761eafe615a7f904c585dba3b7d6e98f665e1 Mon Sep 17 00:00:00 2001 From: Lajos Koszti Date: Thu, 26 Oct 2023 22:40:36 +0200 Subject: fix(lsp): fix omnicomplete in middle of the line (#25787) Fixes a regression from 5e5f5174e3faa862a9bc353aa7da41487911140b Until that commit we had a logic like this: `local prefix = startbyte and line:sub(startbyte + 1) or line_to_cursor:sub(word_boundary)` The commit changed the logic and no longer cut off the line at the cursor, resulting in a prefix that included trailing characters --- test/functional/plugin/lsp/completion_spec.lua | 58 +++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index aa13d6f4b1..9354654afe 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -12,7 +12,8 @@ local exec_lua = helpers.exec_lua ---@return {items: table[], server_start_boundary: integer?} local function complete(line, candidates, lnum) lnum = lnum or 0 - local cursor_col = line:find("|") + -- nvim_win_get_cursor returns 0 based column, line:find returns 1 based + local cursor_col = line:find("|") - 1 line = line:gsub("|", "") return exec_lua([[ local line, cursor_col, lnum, result = ... @@ -21,6 +22,7 @@ local function complete(line, candidates, lnum) local items, server_start_boundary = require("vim.lsp._completion")._convert_results( line, lnum, + cursor_col, client_start_boundary, nil, result, @@ -180,4 +182,58 @@ describe("vim.lsp._completion", function() item.user_data = nil eq(expected, item) end) + + it("should search from start boundary to cursor position", function() + local completion_list = { + isIncomplete = false, + items = { + { + filterText = "this_thread", + insertText = "this_thread", + insertTextFormat = 1, + kind = 9, + label = " this_thread", + score = 1.3205767869949, + sortText = "4056f757this_thread", + textEdit = { + newText = "this_thread", + range = { + start = { line = 0, character = 7 }, + ["end"] = { line = 0, character = 11 }, + }, + } + }, + { + filterText = "notthis_thread", + insertText = "notthis_thread", + insertTextFormat = 1, + kind = 9, + label = " notthis_thread", + score = 1.3205767869949, + sortText = "4056f757this_thread", + textEdit = { + newText = "notthis_thread", + range = { + start = { line = 0, character = 7 }, + ["end"] = { line = 0, character = 11 }, + }, + } + }, + } + } + local expected = { + abbr = ' this_thread', + dup = 1, + empty = 1, + icase = 1, + kind = 'Module', + menu = '', + word = 'this_thread', + } + local result = complete(" std::this|is", completion_list) + eq(1, #result.items) + local item = result.items[1] + item.user_data = nil + eq(expected, item) + end) end) -- 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/inlay_hint_spec.lua | 363 +++++++++++-------------- test/functional/plugin/lsp_spec.lua | 2 +- 2 files changed, 166 insertions(+), 199 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua index eec86fdb8e..d0d55df72b 100644 --- a/test/functional/plugin/lsp/inlay_hint_spec.lua +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -10,228 +10,195 @@ local insert = helpers.insert local clear_notrace = lsp_helpers.clear_notrace local create_server_definition = lsp_helpers.create_server_definition +local text = dedent([[ +auto add(int a, int b) { return a + b; } + +int main() { + int x = 1; + int y = 2; + return add(x,y); +} +}]]) + +local response = [==[ +[ +{"kind":1,"paddingLeft":false,"label":"-> int","position":{"character":22,"line":0},"paddingRight":false}, +{"kind":2,"paddingLeft":false,"label":"a:","position":{"character":15,"line":5},"paddingRight":true}, +{"kind":2,"paddingLeft":false,"label":"b:","position":{"character":17,"line":5},"paddingRight":true} +] +]==] + +local grid_without_inlay_hints = [[ + auto add(int a, int b) { return a + b; } | + | + int main() { | + int x = 1; | + int y = 2; | + return add(x,y); | + } | + ^} | + | +]] + +local grid_with_inlay_hints = [[ + auto add(int a, int b)-> int { return a + b; } | + | + int main() { | + int x = 1; | + int y = 2; | + return add(a: x,b: y); | + } | + ^} | + | +]] + +--- @type test.functional.ui.screen +local screen before_each(function() clear_notrace() + screen = Screen.new(50, 9) + screen:attach() + + exec_lua(create_server_definition) + exec_lua([[ + local response = ... + server = _create_server({ + capabilities = { + inlayHintProvider = true, + }, + handlers = { + ['textDocument/inlayHint'] = function() + return vim.json.decode(response) + end, + } + }) + + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]], response) + + insert(text) + exec_lua([[vim.lsp.inlay_hint.enable(bufnr)]]) + screen:expect({ grid = grid_with_inlay_hints }) end) after_each(function() exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") end) -describe('inlay hints', function() - local screen - before_each(function() - screen = Screen.new(50, 9) - screen:attach() +describe('vim.lsp.inlay_hint', function() + it('clears inlay hints when sole client detaches', function() + exec_lua([[vim.lsp.stop_client(client_id)]]) + screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) + end) + + it('does not clear inlay hints when one of several clients detaches', function() + exec_lua([[ + server2 = _create_server({ + capabilities = { + inlayHintProvider = true, + }, + handlers = { + ['textDocument/inlayHint'] = function() + return {} + end, + } + }) + client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd }) + vim.lsp.inlay_hint.enable(bufnr) + ]]) + + exec_lua([[ vim.lsp.stop_client(client2) ]]) + screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) end) - describe('general', function() - local text = dedent([[ - auto add(int a, int b) { return a + b; } + describe('enable()', function() + it('clears/applies inlay hints when passed false/true/nil', function() + exec_lua([[vim.lsp.inlay_hint.enable(bufnr, false)]]) + screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) - int main() { - int x = 1; - int y = 2; - return add(x,y); - } - }]]) + exec_lua([[vim.lsp.inlay_hint.enable(bufnr, true)]]) + screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) + exec_lua([[vim.lsp.inlay_hint.enable(bufnr, not vim.lsp.inlay_hint.is_enabled(bufnr))]]) + screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) - local response = [==[ - [ - {"kind":1,"paddingLeft":false,"label":"-> int","position":{"character":22,"line":0},"paddingRight":false}, - {"kind":2,"paddingLeft":false,"label":"a:","position":{"character":15,"line":5},"paddingRight":true}, - {"kind":2,"paddingLeft":false,"label":"b:","position":{"character":17,"line":5},"paddingRight":true} - ] - ]==] + exec_lua([[vim.lsp.inlay_hint.enable(bufnr)]]) + screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) + end) + end) + describe('get()', function() + it('returns filtered inlay hints', function() + --- @type lsp.InlayHint[] + local expected = vim.json.decode(response) + local expected2 = { + kind = 1, + paddingLeft = false, + label = ': int', + position = { + character = 10, + line = 2, + }, + paddingRight = false, + } - before_each(function() - exec_lua(create_server_definition) exec_lua([[ - local response = ... - server = _create_server({ + local expected2 = ... + server2 = _create_server({ capabilities = { inlayHintProvider = true, }, handlers = { ['textDocument/inlayHint'] = function() - return vim.json.decode(response) + return { expected2 } end, } }) - ]], response) + client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd }) + vim.lsp.inlay_hint.enable(bufnr) + ]], expected2) + + --- @type vim.lsp.inlay_hint.get.ret + local res = exec_lua([[return vim.lsp.inlay_hint.get()]]) + eq(res, { + { bufnr = 1, client_id = 1, inlay_hint = expected[1] }, + { bufnr = 1, client_id = 1, inlay_hint = expected[2] }, + { bufnr = 1, client_id = 1, inlay_hint = expected[3] }, + { bufnr = 1, client_id = 2, inlay_hint = expected2 }, + }) + + --- @type vim.lsp.inlay_hint.get.ret + res = exec_lua([[return vim.lsp.inlay_hint.get({ + range = { + start = { line = 2, character = 10 }, + ["end"] = { line = 2, character = 10 }, + }, + })]]) + eq(res, { + { bufnr = 1, client_id = 2, inlay_hint = expected2 }, + }) + + --- @type vim.lsp.inlay_hint.get.ret + res = exec_lua([[return vim.lsp.inlay_hint.get({ + bufnr = vim.api.nvim_get_current_buf(), + range = { + start = { line = 4, character = 18 }, + ["end"] = { line = 5, character = 17 }, + }, + })]]) + eq(res, { + { bufnr = 1, client_id = 1, inlay_hint = expected[2] }, + { bufnr = 1, client_id = 1, inlay_hint = expected[3] }, + }) + + --- @type vim.lsp.inlay_hint.get.ret + res = exec_lua([[return vim.lsp.inlay_hint.get({ + bufnr = vim.api.nvim_get_current_buf() + 1, + })]]) + eq(res, {}) end) - - it( - 'inlay hints are applied when vim.lsp.inlay_hint(true) is called', - function() - local res = exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - local client = vim.lsp.get_client_by_id(client_id) - return { - supports_method = client.supports_method("textDocument/inlayHint") - } - ]]) - eq(res, { supports_method = true }) - - - insert(text) - exec_lua([[vim.lsp.inlay_hint(bufnr, true)]]) - screen:expect({ - grid = [[ - auto add(int a, int b)-> int { return a + b; } | - | - int main() { | - int x = 1; | - int y = 2; | - return add(a: x,b: y); | - } | - ^} | - | -]] - }) - end) - - it( - 'inlay hints are cleared when vim.lsp.inlay_hint(false) is called', - function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) - - insert(text) - exec_lua([[vim.lsp.inlay_hint(bufnr, true)]]) - screen:expect({ - grid = [[ - auto add(int a, int b)-> int { return a + b; } | - | - int main() { | - int x = 1; | - int y = 2; | - return add(a: x,b: y); | - } | - ^} | - | -]] - }) - exec_lua([[vim.lsp.inlay_hint(bufnr, false)]]) - screen:expect({ - grid = [[ - auto add(int a, int b) { return a + b; } | - | - int main() { | - int x = 1; | - int y = 2; | - return add(x,y); | - } | - ^} | - | -]], - unchanged = true - }) - end) - - it( - 'inlay hints are cleared when the client detaches', - function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) - - insert(text) - exec_lua([[vim.lsp.inlay_hint(bufnr, true)]]) - screen:expect({ - grid = [[ - auto add(int a, int b)-> int { return a + b; } | - | - int main() { | - int x = 1; | - int y = 2; | - return add(a: x,b: y); | - } | - ^} | - | -]] - }) - exec_lua([[vim.lsp.stop_client(client_id)]]) - screen:expect({ - grid = [[ - auto add(int a, int b) { return a + b; } | - | - int main() { | - int x = 1; | - int y = 2; | - return add(x,y); | - } | - ^} | - | -]], - unchanged = true - }) - end) - - it( - 'inlay hints are not cleared when one of several clients detaches', - function() - -- Start two clients - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - server2 = _create_server({ - capabilities = { - inlayHintProvider = true, - }, - handlers = { - ['textDocument/inlayHint'] = function() - return {} - end, - } - }) - client1 = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd }) - ]]) - - insert(text) - exec_lua([[vim.lsp.inlay_hint(bufnr, true)]]) - screen:expect({ - grid = [[ - auto add(int a, int b)-> int { return a + b; } | - | - int main() { | - int x = 1; | - int y = 2; | - return add(a: x,b: y); | - } | - ^} | - | -]] - }) - - -- Now stop one client - exec_lua([[ vim.lsp.stop_client(client2) ]]) - - -- We should still see the hints - screen:expect({ - grid = [[ - auto add(int a, int b)-> int { return a + b; } | - | - int main() { | - int x = 1; | - int y = 2; | - return add(a: x,b: y); | - } | - ^} | - | -]], - unchanged = true - }) - end) end) end) 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') 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