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 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'test/functional/plugin/lsp') 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) -- 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 +++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 test/functional/plugin/lsp/watchfiles_spec.lua (limited to 'test/functional/plugin/lsp') 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) -- 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 ------------------------- 1 file changed, 173 deletions(-) delete mode 100644 test/functional/plugin/lsp/watchfiles_spec.lua (limited to 'test/functional/plugin/lsp') 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) -- 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/lsp') 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 +++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 test/functional/plugin/lsp/watchfiles_spec.lua (limited to 'test/functional/plugin/lsp') 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) -- 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/lsp') 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 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 ++ 1 file changed, 2 insertions(+) (limited to 'test/functional/plugin/lsp') 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), -- 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/lsp') 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 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/lsp') 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp') 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 -- 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/lsp') 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 1fe1bb084d0099fc4f9bfdc11189485d0f74b75a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 19 Dec 2022 16:37:45 +0000 Subject: refactor(options): deprecate nvim[_buf|_win]_[gs]et_option Co-authored-by: zeertzjq Co-authored-by: famiu --- test/functional/plugin/lsp/incremental_sync_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp') 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) -- 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. --- test/functional/plugin/lsp/incremental_sync_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp') 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) -- 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin/lsp') 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) -- 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/lsp') 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 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 +++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 test/functional/plugin/lsp/inlay_hint_spec.lua (limited to 'test/functional/plugin/lsp') 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) -- 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/lsp') 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 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/lsp') 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/lsp') 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 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/lsp') 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 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/lsp') 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 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/lsp') 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 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/lsp') 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 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/lsp') 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 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/lsp') 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 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 +++++++++------------------- 1 file changed, 80 insertions(+), 163 deletions(-) (limited to 'test/functional/plugin/lsp') 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) -- 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/lsp') 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 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/lsp') 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 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 +++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 test/functional/plugin/lsp/completion_spec.lua (limited to 'test/functional/plugin/lsp') 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) -- 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/lsp') 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 +++++++++++-------------- 1 file changed, 165 insertions(+), 198 deletions(-) (limited to 'test/functional/plugin/lsp') 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) -- cgit