From eb37241d38ad35b9e6bfac6379dd10e60aa0350c Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sun, 26 May 2024 10:27:12 -0700 Subject: fix(tohtml): properly handle multiple hl groups #29012 Problem: :TOhtml doesn't properly handle virtual text when it has multiple highlight groups. It also improperly calculates position offset for multi-byte virt_text characters. Solution: Apply the `vim.api.nvim_strwidth` broadly to properly calculate character offset, and handle the cases where the `hl` argument can be a table of multiple hl groups. --- test/functional/plugin/tohtml_spec.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/tohtml_spec.lua b/test/functional/plugin/tohtml_spec.lua index 200a5f34b2..be5bada901 100644 --- a/test/functional/plugin/tohtml_spec.lua +++ b/test/functional/plugin/tohtml_spec.lua @@ -287,7 +287,13 @@ describe(':TOhtml', function() 0, { virt_text = { { 'foo' } }, virt_text_pos = 'overlay' } ) - api.nvim_buf_set_extmark(0, ns, 2, 0, { virt_text = { { 'foo' } }, virt_text_pos = 'inline' }) + api.nvim_buf_set_extmark( + 0, + ns, + 2, + 0, + { virt_text = { { 'fo┊o', { 'Conceal', 'Comment' } } }, virt_text_pos = 'inline' } + ) --api.nvim_buf_set_extmark(0,ns,3,0,{virt_text={{'foo'}},virt_text_pos='right_align'}) run_tohtml_and_assert(screen) end) -- cgit From 292365fa1b8f543ffa2240bb30af34051ad2d7c8 Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Mon, 27 May 2024 11:06:03 -0400 Subject: fix(lsp): do not detach from buffer if there are uninitialized clients (#29029) Problem: if on_lines is called before the LSP is initialized, the buffer is detached. Solution: check for uninitialized clients before detaching. --- test/functional/plugin/lsp_spec.lua | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index c95a96baca..0cf84b50c2 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -530,6 +530,34 @@ describe('LSP', function() ]]) end) + it('should allow on_lines + nvim_buf_delete during LSP initialization #28575', function() + clear() + exec_lua(create_server_definition) + exec_lua([[ + local initialized = false + local server = _create_server({ + handlers = { + initialize = function(method, params, callback) + vim.schedule(function() + callback(nil, { capabilities = {} }) + initialized = true + end) + end + } + }) + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(bufnr) + local client_id = vim.lsp.start({ + name = 'detach-dummy', + cmd = server.cmd, + }) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {"hello"}) + vim.api.nvim_buf_delete(bufnr, {}) + local ok = vim.wait(1000, function() return initialized end) + assert(ok, "lsp did not initialize") + ]]) + end) + it('client should return settings via workspace/configuration handler', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, -- cgit From ff097f2091e7a970e5b12960683b4dade5563040 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sun, 4 Feb 2024 14:13:23 -0800 Subject: feat(lsp): completion side effects --- test/functional/plugin/lsp/completion_spec.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 2798d57381..1052b00dae 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -21,7 +21,7 @@ local function complete(line, candidates, lnum) 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( + local items, server_start_boundary = require("vim.lsp.completion")._convert_results( line, lnum, cursor_col, @@ -42,7 +42,7 @@ local function complete(line, candidates, lnum) ) end -describe('vim.lsp._completion', function() +describe('vim.lsp.completion', function() before_each(n.clear) -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion @@ -159,6 +159,7 @@ describe('vim.lsp._completion', function() end, result.items) eq(expected, result) end) + it('uses correct start boundary', function() local completion_list = { isIncomplete = false, -- cgit From 490c2109e6139c268b64c6a88f4678f7c7af51ea Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sat, 27 Apr 2024 14:59:39 -0700 Subject: test(lsp): add completion tests --- test/functional/plugin/lsp/completion_spec.lua | 232 ++++++++++++++++++++++++- 1 file changed, 231 insertions(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 1052b00dae..5b7232ad7e 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -1,9 +1,16 @@ ---@diagnostic disable: no-unknown local t = require('test.testutil') +local t_lsp = require('test.functional.plugin.lsp.testutil') local n = require('test.functional.testnvim')() +local clear = n.clear local eq = t.eq +local neq = t.neq local exec_lua = n.exec_lua +local feed = n.feed +local retry = t.retry + +local create_server_definition = t_lsp.create_server_definition --- Convert completion results. --- @@ -25,6 +32,7 @@ local function complete(line, candidates, lnum) line, lnum, cursor_col, + 1, client_start_boundary, nil, result, @@ -42,7 +50,7 @@ local function complete(line, candidates, lnum) ) end -describe('vim.lsp.completion', function() +describe('vim.lsp.completion: item conversion', function() before_each(n.clear) -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion @@ -187,6 +195,7 @@ describe('vim.lsp.completion', function() dup = 1, empty = 1, icase = 1, + info = '', kind = 'Module', menu = '', word = 'this_thread', @@ -241,6 +250,7 @@ describe('vim.lsp.completion', function() dup = 1, empty = 1, icase = 1, + info = '', kind = 'Module', menu = '', word = 'this_thread', @@ -279,4 +289,224 @@ describe('vim.lsp.completion', function() eq('item-property-has-priority', item.data) eq({ line = 1, character = 1 }, item.textEdit.range.start) end) + + it( + 'uses insertText as textEdit.newText if there are editRange defaults but no textEditText', + function() + --- @type lsp.CompletionList + local completion_list = { + isIncomplete = false, + itemDefaults = { + editRange = { + start = { line = 1, character = 1 }, + ['end'] = { line = 1, character = 4 }, + }, + insertTextFormat = 2, + data = 'foobar', + }, + items = { + { + insertText = 'the-insertText', + label = 'hello', + data = 'item-property-has-priority', + }, + }, + } + local result = complete('|', completion_list) + eq(1, #result.items) + local text = result.items[1].user_data.nvim.lsp.completion_item.textEdit.newText + eq('the-insertText', text) + end + ) +end) + +describe('vim.lsp.completion: protocol', function() + before_each(function() + clear() + exec_lua(create_server_definition) + exec_lua([[ + _G.capture = {} + vim.fn.complete = function(col, matches) + _G.capture.col = col + _G.capture.matches = matches + end + ]]) + end) + + after_each(clear) + + --- @param completion_result lsp.CompletionList + --- @return integer + local function create_server(completion_result) + return exec_lua( + [[ + local result = ... + local server = _create_server({ + capabilities = { + completionProvider = { + triggerCharacters = { '.' } + } + }, + handlers = { + ['textDocument/completion'] = function(_, _, callback) + callback(nil, result) + end + } + }) + + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + return vim.lsp.start({ name = 'dummy', cmd = server.cmd, on_attach = function(client, bufnr) + vim.lsp.completion.enable(true, client.id, bufnr) + end}) + ]], + completion_result + ) + end + + local function assert_matches(fn) + retry(nil, nil, function() + fn(exec_lua('return _G.capture.matches')) + end) + end + + --- @param pos { [1]: integer, [2]: integer } + local function trigger_at_pos(pos) + exec_lua( + [[ + local win = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_cursor(win, ...) + vim.lsp.completion.trigger() + ]], + pos + ) + + retry(nil, nil, function() + neq(nil, exec_lua('return _G.capture.col')) + end) + end + + it('fetches completions and shows them using complete on trigger', function() + create_server({ + isIncomplete = false, + items = { + { + label = 'hello', + }, + }, + }) + + feed('ih') + trigger_at_pos({ 1, 1 }) + + assert_matches(function(matches) + eq({ + { + abbr = 'hello', + dup = 1, + empty = 1, + icase = 1, + info = '', + kind = 'Unknown', + menu = '', + user_data = { + nvim = { + lsp = { + client_id = 1, + completion_item = { + label = 'hello', + }, + }, + }, + }, + word = 'hello', + }, + }, matches) + end) + end) + + it('merges results from multiple clients', function() + create_server({ + isIncomplete = false, + items = { + { + label = 'hello', + }, + }, + }) + create_server({ + isIncomplete = false, + items = { + { + label = 'hallo', + }, + }, + }) + + feed('ih') + trigger_at_pos({ 1, 1 }) + + assert_matches(function(matches) + eq(2, #matches) + eq('hello', matches[1].word) + eq('hallo', matches[2].word) + end) + end) + + it('executes commands', function() + local completion_list = { + isIncomplete = false, + items = { + { + label = 'hello', + command = { + arguments = { '1', '0' }, + command = 'dummy', + title = '', + }, + }, + }, + } + local client_id = create_server(completion_list) + + exec_lua( + [[ + _G.called = false + local client = vim.lsp.get_client_by_id(...) + client.commands.dummy = function () + _G.called = true + end + ]], + client_id + ) + + feed('ih') + trigger_at_pos({ 1, 1 }) + + exec_lua( + [[ + local client_id, item = ... + vim.v.completed_item = { + user_data = { + nvim = { + lsp = { + client_id = client_id, + completion_item = item + } + } + } + } + ]], + client_id, + completion_list.items[1] + ) + + feed('') + + assert_matches(function(matches) + eq(1, #matches) + eq('hello', matches[1].word) + eq(true, exec_lua('return _G.called')) + end) + end) end) -- cgit From 0df2c6b5d09fab392dd1a14e4b2e6a3b03203aaa Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Tue, 28 May 2024 21:37:46 +0200 Subject: feat(lsp): use fuzzy match on filterText instead of prefix match The `complete()` mechanism matches completion candidates against the typed text, so strict pre-filtering isn't necessary. This is a first step towards supporting postfix snippets (like `items@insert` in luals) --- test/functional/plugin/lsp/completion_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 5b7232ad7e..078abdf653 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -228,7 +228,7 @@ describe('vim.lsp.completion: item conversion', function() }, }, { - filterText = 'notthis_thread', + filterText = 'no_match', insertText = 'notthis_thread', insertTextFormat = 1, kind = 9, -- cgit From b2bad0ac91ddb9b33c3547b6fd4f7278794818d9 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Tue, 28 May 2024 23:20:25 +0200 Subject: feat(lsp): support postfix snippets in completion --- test/functional/plugin/lsp/completion_spec.lua | 108 +++++++++++++++++-------- 1 file changed, 73 insertions(+), 35 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 078abdf653..d7755dd0c4 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -78,32 +78,6 @@ describe('vim.lsp.completion: item conversion', function() 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', @@ -139,23 +113,87 @@ describe('vim.lsp.completion: item conversion', function() }, { abbr = 'foocar', - word = 'foobar(place holder, more ...holder{})', + word = 'foodar(${1:var1})', -- marked as PlainText, text is used as is + }, + } + 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('prefers wordlike components for snippets', function() + -- There are two goals here: + -- + -- 1. The `word` should match what the user started typing, so that vim.fn.complete() doesn't + -- filter it away, preventing snippet expansion + -- + -- For example, if they type `items@ins`, luals returns `table.insert(items, $0)` as + -- textEdit.newText and `insert` as label. + -- There would be no prefix match if textEdit.newText is used as `word` + -- + -- 2. If users do not expand a snippet, but continue typing, they should see a somewhat reasonable + -- `word` getting inserted. + -- + -- For example in: + -- + -- insertText: "testSuites ${1:Env}" + -- label: "testSuites" + -- + -- "testSuites" should have priority as `word`, as long as the full snippet gets expanded on accept () + local range0 = { + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 }, + } + local completion_list = { + -- luals postfix snippet (typed text: items@ins|) + { + label = 'insert', + insertTextFormat = 2, + textEdit = { + newText = 'table.insert(items, $0)', + range = range0, + }, }, + + -- eclipse.jdt.ls `new` snippet { - abbr = 'foocar', - word = 'foodar(var1 typ1, var2 *typ2) {}', + label = 'new', + insertTextFormat = 2, + textEdit = { + newText = '${1:Object} ${2:foo} = new ${1}(${3});\n${0}', + range = range0, + }, + textEditText = '${1:Object} ${2:foo} = new ${1}(${3});\n${0}', }, + + -- eclipse.jdt.ls `List.copyO` function call completion { - abbr = 'foocar', - word = 'foodar(typ1) {}', + label = 'copyOf(Collection coll) : List', + insertTextFormat = 2, + insertText = 'copyOf', + textEdit = { + newText = 'copyOf(${1:coll})', + range = range0, + }, }, + } + local expected = { { - abbr = 'foocar', - word = 'foodar()', + abbr = 'copyOf(Collection coll) : List', + word = 'copyOf', }, { - abbr = 'foocar', - word = 'foodar(${1:var1})', + abbr = 'insert', + word = 'insert', + }, + { + abbr = 'new', + word = 'new', }, } local result = complete('|', completion_list) -- cgit From 5c33815448e11b514678f39cecc74e68131d4628 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 30 May 2024 10:46:26 +0200 Subject: refactor(lsp): replace util.buf_versions with changedtick (#28943) `lsp.util.buf_versions` was already derived from changedtick (`on_lines` from `buf_attach` synced the version) As far as I can tell there is no need to keep track of the state in a separate table. --- .../functional/plugin/lsp/semantic_tokens_spec.lua | 24 +++++++++--------- test/functional/plugin/lsp_spec.lua | 29 ++++++++-------------- 2 files changed, 22 insertions(+), 31 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 7908c5d2e7..9babb080e7 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -111,6 +111,7 @@ describe('semantic token highlighting', function() end) it('buffer is highlighted when attached', function() + insert(text) exec_lua([[ bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_win_set_buf(0, bufnr) @@ -118,8 +119,6 @@ describe('semantic token highlighting', function() client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) ]]) - insert(text) - screen:expect { grid = [[ #include | @@ -141,6 +140,7 @@ describe('semantic token highlighting', function() end) it('use LspTokenUpdate and highlight_token', function() + insert(text) exec_lua([[ vim.api.nvim_create_autocmd("LspTokenUpdate", { callback = function(args) @@ -157,8 +157,6 @@ describe('semantic token highlighting', function() client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) ]]) - insert(text) - screen:expect { grid = [[ #include | @@ -180,14 +178,17 @@ describe('semantic token highlighting', function() end) it('buffer is unhighlighted when client is detached', function() + insert(text) + 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 }) + vim.wait(1000, function() + return #server.messages > 1 + end) ]]) - insert(text) - exec_lua([[ vim.notify = function() end vim.lsp.buf_detach_client(bufnr, client_id) @@ -331,14 +332,13 @@ describe('semantic token highlighting', function() end) it('buffer is re-highlighted when force refreshed', function() + insert(text) 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) - screen:expect { grid = [[ #include | @@ -412,13 +412,14 @@ describe('semantic token highlighting', function() end) it('updates highlights with delta request on buffer change', function() + insert(text) + 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) screen:expect { grid = [[ #include | @@ -597,6 +598,7 @@ describe('semantic token highlighting', function() end) it('does not send delta requests if not supported by server', function() + insert(text) exec_lua( [[ local legend, response, edit_response = ... @@ -625,7 +627,6 @@ describe('semantic token highlighting', function() edit_response ) - insert(text) screen:expect { grid = [[ #include | @@ -1449,6 +1450,7 @@ int main() }, }) do it(test.it, function() + insert(test.text1) exec_lua(create_server_definition) exec_lua( [[ @@ -1485,8 +1487,6 @@ int main() test.response2 ) - insert(test.text1) - test.expected_screen1() local highlights = exec_lua([[ diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 0cf84b50c2..85891e59b5 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -255,7 +255,7 @@ describe('LSP', function() return end local expected_handlers = { - { NIL, {}, { method = 'shutdown', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'shutdown', bufnr = 1, client_id = 1, version = 2 } }, { NIL, {}, { method = 'test', client_id = 1 } }, } test_rpc_server { @@ -948,7 +948,11 @@ describe('LSP', function() it('should forward ContentModified to callback', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { { code = -32801 }, NIL, { method = 'error_code_test', bufnr = 1, client_id = 1 } }, + { + { code = -32801 }, + NIL, + { method = 'error_code_test', bufnr = 1, client_id = 1, version = 2 }, + }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -978,7 +982,7 @@ describe('LSP', function() it('should track pending requests to the language server', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 2 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1045,7 +1049,7 @@ describe('LSP', function() it('should clear pending and cancel requests on reply', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 2 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1084,7 +1088,7 @@ describe('LSP', function() it('should trigger LspRequest autocmd when requests table changes', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 2 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1364,6 +1368,7 @@ describe('LSP', function() }, bufnr = 2, client_id = 1, + version = 2, }, }, { NIL, {}, { method = 'start', client_id = 1 } }, @@ -2117,7 +2122,6 @@ describe('LSP', function() local args = {...} local bufnr = select(1, ...) local text_edit = select(2, ...) - vim.lsp.util.buf_versions[bufnr] = 10 vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16') ]], target_bufnr, @@ -2134,7 +2138,6 @@ describe('LSP', function() [[ local args = {...} local versionedBuf = args[2] - vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion vim.lsp.util.apply_text_document_edit(args[1], nil, 'utf-16') ]], edit, @@ -2239,18 +2242,6 @@ describe('LSP', function() } vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - - local update_changed_tick = function() - vim.lsp.util.buf_versions[bufnr] = vim.api.nvim_buf_get_var(bufnr, 'changedtick') - end - - update_changed_tick() - vim.api.nvim_buf_attach(bufnr, false, { - on_changedtick = function() - update_changed_tick() - end - }) - return {bufnr, vim.api.nvim_buf_get_var(bufnr, 'changedtick')} ]] -- cgit From 19be3d26830ced203631045f2f622e75e6d857a7 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sun, 2 Jun 2024 09:54:15 +0200 Subject: fix(lsp): trim trailing whitespace from completion words (#29122) the `complete()` mechanism doesn't play nicely with trailing newlines or tabs. A newline causes it to insert a null character, showing up as `^@`. --- test/functional/plugin/lsp/completion_spec.lua | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index d7755dd0c4..d8a3e0acbd 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -126,6 +126,41 @@ describe('vim.lsp.completion: item conversion', function() eq(expected, result) end) + it('trims trailing newline or tab from textEdit', function() + local range0 = { + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 }, + } + local items = { + { + detail = 'ansible.builtin', + filterText = 'lineinfile ansible.builtin.lineinfile builtin ansible', + kind = 7, + label = 'ansible.builtin.lineinfile', + sortText = '2_ansible.builtin.lineinfile', + textEdit = { + newText = 'ansible.builtin.lineinfile:\n ', + range = range0, + }, + }, + } + local result = complete('|', items) + result = vim.tbl_map(function(x) + return { + abbr = x.abbr, + word = x.word, + } + end, result.items) + + local expected = { + { + abbr = 'ansible.builtin.lineinfile', + word = 'ansible.builtin.lineinfile:', + }, + } + eq(expected, result) + end) + it('prefers wordlike components for snippets', function() -- There are two goals here: -- -- cgit From 8cbb1f20e557461c8417583a7f69d53aaaef920b Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Tue, 4 Jun 2024 09:06:02 -0400 Subject: refactor(lua): use tuple syntax everywhere #29111 --- test/functional/plugin/lsp/completion_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index d8a3e0acbd..0e81e4fddb 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -443,7 +443,7 @@ describe('vim.lsp.completion: protocol', function() end) end - --- @param pos { [1]: integer, [2]: integer } + --- @param pos [integer, integer] local function trigger_at_pos(pos) exec_lua( [[ -- cgit From 2e6d295f799c27372e5c0c44727fa613c81717fd Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Tue, 4 Jun 2024 17:21:37 +0200 Subject: fix(lsp): account for changedtick version gap on modified reset (#29170) Follow up to https://github.com/neovim/neovim/pull/28943 Fixes https://github.com/neovim/neovim/issues/29163 --- test/functional/plugin/lsp_spec.lua | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 85891e59b5..23f6b733d5 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2133,15 +2133,13 @@ describe('LSP', function() }, buf_lines(target_bufnr)) end) it('skips the edit if the version of the edit is behind the local buffer ', function() - local apply_edit_mocking_current_version = function(edit, versionedBuf) + local apply_edit_mocking_current_version = function(edit) exec_lua( [[ local args = {...} - local versionedBuf = args[2] vim.lsp.util.apply_text_document_edit(args[1], nil, 'utf-16') ]], - edit, - versionedBuf + edit ) end @@ -2153,17 +2151,17 @@ describe('LSP', function() eq(baseText, buf_lines(target_bufnr)) -- Apply an edit for an old version, should skip - apply_edit_mocking_current_version( - text_document_edit(2), - { currentVersion = 7, bufnr = target_bufnr } - ) + apply_edit_mocking_current_version(text_document_edit(1)) eq(baseText, buf_lines(target_bufnr)) -- no change -- Sanity check that next version to current does apply change - apply_edit_mocking_current_version( - text_document_edit(8), - { currentVersion = 7, bufnr = target_bufnr } - ) + apply_edit_mocking_current_version(text_document_edit(exec_lua( + [[ + local bufnr = ... + return vim.b[bufnr].changedtick + ]], + target_bufnr + ))) eq({ 'First ↥ 🤦 🦄 line of text', '2nd line of 语text', -- cgit From cb6c0fda718e4503fc1bfc49a9fe92411f5f9005 Mon Sep 17 00:00:00 2001 From: sus-domesticus <134197728+sus-domesticus@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:16:43 +0300 Subject: feat(editorconfig): add support for spelling_language (#28638) --- test/functional/plugin/editorconfig_spec.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/editorconfig_spec.lua b/test/functional/plugin/editorconfig_spec.lua index 839a723405..242ed9b57f 100644 --- a/test/functional/plugin/editorconfig_spec.lua +++ b/test/functional/plugin/editorconfig_spec.lua @@ -16,8 +16,16 @@ local testdir = 'Xtest-editorconfig' local function test_case(name, expected) local filename = testdir .. pathsep .. name command('edit ' .. filename) + for opt, val in pairs(expected) do - eq(val, api.nvim_get_option_value(opt, { buf = 0 }), name) + local opt_info = api.nvim_get_option_info2(opt, {}) + if opt_info.scope == 'win' then + eq(val, api.nvim_get_option_value(opt, { win = 0 }), name) + elseif opt_info.scope == 'buf' then + eq(val, api.nvim_get_option_value(opt, { buf = 0 }), name) + else + eq(val, api.nvim_get_option_value(opt, {}), name) + end end end @@ -93,6 +101,12 @@ setup(function() [max_line_length.txt] max_line_length = 42 + + [short_spelling_language.txt] + spelling_language = de + + [long_spelling_language.txt] + spelling_language = en-NZ ]] ) end) @@ -222,4 +236,9 @@ But not this one eq(true, ok, err) end) + + it('sets spelllang', function() + test_case('short_spelling_language.txt', { spelllang = 'de' }) + test_case('long_spelling_language.txt', { spelllang = 'en_nz' }) + end) end) -- cgit From 6e45cd7f0026ee33b8c397b810dcfe5b4678bbd8 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 7 Jun 2024 11:36:46 +0200 Subject: fix(lsp): revert buf_versions deprecation/replacement (#29217) * Revert "fix(lsp): account for changedtick version gap on modified reset (#29170)" This reverts commit 2e6d295f799c27372e5c0c44727fa613c81717fd. * Revert "refactor(lsp): replace util.buf_versions with changedtick (#28943)" This reverts commit 5c33815448e11b514678f39cecc74e68131d4628. --- .../functional/plugin/lsp/semantic_tokens_spec.lua | 24 +++++----- test/functional/plugin/lsp_spec.lua | 51 +++++++++++++--------- 2 files changed, 43 insertions(+), 32 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 9babb080e7..7908c5d2e7 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -111,7 +111,6 @@ describe('semantic token highlighting', function() end) it('buffer is highlighted when attached', function() - insert(text) exec_lua([[ bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_win_set_buf(0, bufnr) @@ -119,6 +118,8 @@ describe('semantic token highlighting', function() client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) ]]) + insert(text) + screen:expect { grid = [[ #include | @@ -140,7 +141,6 @@ describe('semantic token highlighting', function() end) it('use LspTokenUpdate and highlight_token', function() - insert(text) exec_lua([[ vim.api.nvim_create_autocmd("LspTokenUpdate", { callback = function(args) @@ -157,6 +157,8 @@ describe('semantic token highlighting', function() client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) ]]) + insert(text) + screen:expect { grid = [[ #include | @@ -178,17 +180,14 @@ describe('semantic token highlighting', function() end) it('buffer is unhighlighted when client is detached', function() - insert(text) - 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 }) - vim.wait(1000, function() - return #server.messages > 1 - end) ]]) + insert(text) + exec_lua([[ vim.notify = function() end vim.lsp.buf_detach_client(bufnr, client_id) @@ -332,13 +331,14 @@ describe('semantic token highlighting', function() end) it('buffer is re-highlighted when force refreshed', function() - insert(text) 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) + screen:expect { grid = [[ #include | @@ -412,14 +412,13 @@ describe('semantic token highlighting', function() end) it('updates highlights with delta request on buffer change', function() - insert(text) - 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) screen:expect { grid = [[ #include | @@ -598,7 +597,6 @@ describe('semantic token highlighting', function() end) it('does not send delta requests if not supported by server', function() - insert(text) exec_lua( [[ local legend, response, edit_response = ... @@ -627,6 +625,7 @@ describe('semantic token highlighting', function() edit_response ) + insert(text) screen:expect { grid = [[ #include | @@ -1450,7 +1449,6 @@ int main() }, }) do it(test.it, function() - insert(test.text1) exec_lua(create_server_definition) exec_lua( [[ @@ -1487,6 +1485,8 @@ int main() test.response2 ) + insert(test.text1) + test.expected_screen1() local highlights = exec_lua([[ diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 23f6b733d5..0cf84b50c2 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -255,7 +255,7 @@ describe('LSP', function() return end local expected_handlers = { - { NIL, {}, { method = 'shutdown', bufnr = 1, client_id = 1, version = 2 } }, + { NIL, {}, { method = 'shutdown', bufnr = 1, client_id = 1 } }, { NIL, {}, { method = 'test', client_id = 1 } }, } test_rpc_server { @@ -948,11 +948,7 @@ describe('LSP', function() it('should forward ContentModified to callback', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { - { code = -32801 }, - NIL, - { method = 'error_code_test', bufnr = 1, client_id = 1, version = 2 }, - }, + { { code = -32801 }, NIL, { method = 'error_code_test', bufnr = 1, client_id = 1 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -982,7 +978,7 @@ describe('LSP', function() it('should track pending requests to the language server', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 2 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1049,7 +1045,7 @@ describe('LSP', function() it('should clear pending and cancel requests on reply', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 2 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1088,7 +1084,7 @@ describe('LSP', function() it('should trigger LspRequest autocmd when requests table changes', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 2 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1368,7 +1364,6 @@ describe('LSP', function() }, bufnr = 2, client_id = 1, - version = 2, }, }, { NIL, {}, { method = 'start', client_id = 1 } }, @@ -2122,6 +2117,7 @@ describe('LSP', function() local args = {...} local bufnr = select(1, ...) local text_edit = select(2, ...) + vim.lsp.util.buf_versions[bufnr] = 10 vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16') ]], target_bufnr, @@ -2133,13 +2129,16 @@ describe('LSP', function() }, buf_lines(target_bufnr)) end) it('skips the edit if the version of the edit is behind the local buffer ', function() - local apply_edit_mocking_current_version = function(edit) + local apply_edit_mocking_current_version = function(edit, versionedBuf) exec_lua( [[ local args = {...} + local versionedBuf = args[2] + vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion vim.lsp.util.apply_text_document_edit(args[1], nil, 'utf-16') ]], - edit + edit, + versionedBuf ) end @@ -2151,17 +2150,17 @@ describe('LSP', function() eq(baseText, buf_lines(target_bufnr)) -- Apply an edit for an old version, should skip - apply_edit_mocking_current_version(text_document_edit(1)) + apply_edit_mocking_current_version( + text_document_edit(2), + { currentVersion = 7, bufnr = target_bufnr } + ) eq(baseText, buf_lines(target_bufnr)) -- no change -- Sanity check that next version to current does apply change - apply_edit_mocking_current_version(text_document_edit(exec_lua( - [[ - local bufnr = ... - return vim.b[bufnr].changedtick - ]], - target_bufnr - ))) + apply_edit_mocking_current_version( + text_document_edit(8), + { currentVersion = 7, bufnr = target_bufnr } + ) eq({ 'First ↥ 🤦 🦄 line of text', '2nd line of 语text', @@ -2240,6 +2239,18 @@ describe('LSP', function() } vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + + local update_changed_tick = function() + vim.lsp.util.buf_versions[bufnr] = vim.api.nvim_buf_get_var(bufnr, 'changedtick') + end + + update_changed_tick() + vim.api.nvim_buf_attach(bufnr, false, { + on_changedtick = function() + update_changed_tick() + end + }) + return {bufnr, vim.api.nvim_buf_get_var(bufnr, 'changedtick')} ]] -- cgit From 2ce4a4d91e4abee0aab8b98c47eea9fbd4849ba6 Mon Sep 17 00:00:00 2001 From: Al Colmenar <57642956+alcolmenar@users.noreply.github.com> Date: Fri, 7 Jun 2024 02:54:43 -0700 Subject: fix(lsp): fix reverse sorting of same position text edits (#29212) Problem: Text edits with the same position (both line and character) were being reverse sorted prior to being applied which differs from the lsp spec Solution: Change the sort order for just the same position edits --- test/functional/plugin/lsp_spec.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 0cf84b50c2..6d28b83be8 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1789,9 +1789,9 @@ describe('LSP', function() } exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') eq({ - '', - '123', - 'fooFbar', + '3', + 'foo', + '12Fbar', '123irst guy', 'baz line of text', 'The next line of text', @@ -1813,9 +1813,9 @@ describe('LSP', function() } exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') eq({ - '', - '123', - 'fooFbar', + '3', + 'foo', + '12Fbar', '123irst guy', 'baz line of text', 'The next line of text', -- cgit From da6f68ee6966ebf434eee840b22a4f45e61d77dd Mon Sep 17 00:00:00 2001 From: Lennard Hofmann Date: Fri, 7 Jun 2024 21:43:17 +0200 Subject: fix(man): filter OSC 8 hyperlink markup #29171 Problem: `man cmake` shows "8;;https://cmake.orghttps://cmake.org8;;" Solution: Remove noise so that it shows as "https://cmake.org". See also: https://en.wikipedia.org/wiki/ANSI_escape_code#OSC --- test/functional/plugin/man_spec.lua | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 978178191c..5e4ec53f8d 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -117,6 +117,29 @@ describe(':Man', function() ]]) end) + it('clears OSC 8 hyperlink markup from text', function() + feed( + [[ + ithis ]8;;http://example.com\Link Title]8;;\]] + ) + + screen:expect { + grid = [=[ + this {c:^[}]8;;http://example.com{c:^[}\Link Title{c:^[}]8;;{c:^[}^\ | + {eob:~ }|*3 + | + ]=], + } + + exec_lua [[require'man'.init_pager()]] + + screen:expect([[ + ^this Link Title | + {eob:~ }|*3 + | + ]]) + end) + it('highlights multibyte text', function() feed( [[ -- cgit From 20f22f75ee629ae2db4cd99e730fa0af26553177 Mon Sep 17 00:00:00 2001 From: Tom Praschan <13141438+tom-anders@users.noreply.github.com> Date: Mon, 10 Jun 2024 03:14:55 +0200 Subject: feat(lsp): include end_col, end_lnum in vim.lsp.buf.locations_to_items #29164 --- test/functional/plugin/lsp_spec.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 6d28b83be8..50e9c0cc0f 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2690,13 +2690,15 @@ describe('LSP', function() { filename = '/fake/uri', lnum = 1, + end_lnum = 2, col = 3, + end_col = 4, text = 'testing', user_data = { uri = 'file:///fake/uri', range = { start = { line = 0, character = 2 }, - ['end'] = { line = 0, character = 3 }, + ['end'] = { line = 1, character = 3 }, }, }, }, @@ -2710,7 +2712,7 @@ describe('LSP', function() uri = 'file:///fake/uri', range = { start = { line = 0, character = 2 }, - ['end'] = { line = 0, character = 3 }, + ['end'] = { line = 1, character = 3 }, } }, } @@ -2723,7 +2725,9 @@ describe('LSP', function() { filename = '/fake/uri', lnum = 1, + end_lnum = 1, col = 3, + end_col = 4, text = 'testing', user_data = { targetUri = 'file:///fake/uri', -- cgit From 0a9c81d70964f905112857900fbaa6aae590a96d Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Fri, 14 Jun 2024 05:03:58 -0400 Subject: refactor(lsp): use metatable for buf_versions (#29304) This reduces the number of nil checks around buf_versions usage Test changes were lifted from 5c33815 Co-authored-by: Mathias Fussenegger --- .../functional/plugin/lsp/semantic_tokens_spec.lua | 24 +++++++++++----------- test/functional/plugin/lsp_spec.lua | 15 +++++++++----- 2 files changed, 22 insertions(+), 17 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 7908c5d2e7..9babb080e7 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -111,6 +111,7 @@ describe('semantic token highlighting', function() end) it('buffer is highlighted when attached', function() + insert(text) exec_lua([[ bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_win_set_buf(0, bufnr) @@ -118,8 +119,6 @@ describe('semantic token highlighting', function() client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) ]]) - insert(text) - screen:expect { grid = [[ #include | @@ -141,6 +140,7 @@ describe('semantic token highlighting', function() end) it('use LspTokenUpdate and highlight_token', function() + insert(text) exec_lua([[ vim.api.nvim_create_autocmd("LspTokenUpdate", { callback = function(args) @@ -157,8 +157,6 @@ describe('semantic token highlighting', function() client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) ]]) - insert(text) - screen:expect { grid = [[ #include | @@ -180,14 +178,17 @@ describe('semantic token highlighting', function() end) it('buffer is unhighlighted when client is detached', function() + insert(text) + 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 }) + vim.wait(1000, function() + return #server.messages > 1 + end) ]]) - insert(text) - exec_lua([[ vim.notify = function() end vim.lsp.buf_detach_client(bufnr, client_id) @@ -331,14 +332,13 @@ describe('semantic token highlighting', function() end) it('buffer is re-highlighted when force refreshed', function() + insert(text) 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) - screen:expect { grid = [[ #include | @@ -412,13 +412,14 @@ describe('semantic token highlighting', function() end) it('updates highlights with delta request on buffer change', function() + insert(text) + 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) screen:expect { grid = [[ #include | @@ -597,6 +598,7 @@ describe('semantic token highlighting', function() end) it('does not send delta requests if not supported by server', function() + insert(text) exec_lua( [[ local legend, response, edit_response = ... @@ -625,7 +627,6 @@ describe('semantic token highlighting', function() edit_response ) - insert(text) screen:expect { grid = [[ #include | @@ -1449,6 +1450,7 @@ int main() }, }) do it(test.it, function() + insert(test.text1) exec_lua(create_server_definition) exec_lua( [[ @@ -1485,8 +1487,6 @@ int main() test.response2 ) - insert(test.text1) - test.expected_screen1() local highlights = exec_lua([[ diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 50e9c0cc0f..b345a3288c 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -255,7 +255,7 @@ describe('LSP', function() return end local expected_handlers = { - { NIL, {}, { method = 'shutdown', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'shutdown', bufnr = 1, client_id = 1, version = 0 } }, { NIL, {}, { method = 'test', client_id = 1 } }, } test_rpc_server { @@ -948,7 +948,11 @@ describe('LSP', function() it('should forward ContentModified to callback', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { { code = -32801 }, NIL, { method = 'error_code_test', bufnr = 1, client_id = 1 } }, + { + { code = -32801 }, + NIL, + { method = 'error_code_test', bufnr = 1, client_id = 1, version = 0 }, + }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -978,7 +982,7 @@ describe('LSP', function() it('should track pending requests to the language server', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 0 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1045,7 +1049,7 @@ describe('LSP', function() it('should clear pending and cancel requests on reply', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 0 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1084,7 +1088,7 @@ describe('LSP', function() it('should trigger LspRequest autocmd when requests table changes', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 0 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1364,6 +1368,7 @@ describe('LSP', function() }, bufnr = 2, client_id = 1, + version = 0, }, }, { NIL, {}, { method = 'start', client_id = 1 } }, -- cgit From aa47af7e69bb32c4486510dce27f45d9028e0a6c Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 14 Jun 2024 19:32:34 +0200 Subject: fix(lsp): tune completion word extraction for decorated labels (#29331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: For snippets lsp.completion prefers the label if it is shorter than the insertText or textEdit to support postfix completion cases but clangd adds decoration characters to labels. E.g.: `•INT16_C(c)` Solution: Use parse_snippet on insertText/textEdit before checking if it is shorter than the label. Fixes https://github.com/neovim/neovim/issues/29301 --- test/functional/plugin/lsp/completion_spec.lua | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 0e81e4fddb..1b56d1740a 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -81,10 +81,21 @@ describe('vim.lsp.completion: item conversion', function() -- plain text { label = 'foocar', - sortText = 'k', + sortText = 'g', insertText = 'foodar(${1:var1})', insertTextFormat = 1, }, + { + label = '•INT16_C(c)', + insertText = 'INT16_C(${1:c})', + insertTextFormat = 2, + filterText = 'INT16_C', + sortText = 'h', + textEdit = { + newText = 'INT16_C(${1:c})', + range = range0, + }, + }, } local expected = { { @@ -115,6 +126,10 @@ describe('vim.lsp.completion: item conversion', function() abbr = 'foocar', word = 'foodar(${1:var1})', -- marked as PlainText, text is used as is }, + { + abbr = '•INT16_C(c)', + word = 'INT16_C', + }, } local result = complete('|', completion_list) result = vim.tbl_map(function(x) -- cgit From 5581a95534e44b8714e715c925c9de2d95ae1c21 Mon Sep 17 00:00:00 2001 From: Tom Praschan <13141438+tom-anders@users.noreply.github.com> Date: Mon, 24 Jun 2024 16:54:56 +0200 Subject: feat(lsp): vim.lsp.buf.format() supports textDocument/rangesFormatting #27323 While this relies on a proposed LSP 3.18 feature, it's fully backwards compatible, so IMO there's no harm in adding this already. Looks like some servers already support for this e.g. - gopls: https://go-review.googlesource.com/c/tools/+/510235 - clangd: https://github.com/llvm/llvm-project/pull/80180 Fixes #27293 --- test/functional/plugin/lsp_spec.lua | 80 +++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index b345a3288c..be303f21ce 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -4537,6 +4537,86 @@ describe('LSP', function() end, } end) + it('Sends textDocument/rangeFormatting request to format a range', function() + local expected_handlers = { + { NIL, {}, { method = 'shutdown', client_id = 1 } }, + { NIL, {}, { method = 'start', client_id = 1 } }, + } + local client + test_rpc_server { + test_name = 'range_formatting', + on_init = function(c) + client = c + end, + on_handler = function(_, _, ctx) + table.remove(expected_handlers) + if ctx.method == 'start' then + local notify_msg = exec_lua([[ + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar'}) + vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + local notify_msg + local notify = vim.notify + vim.notify = function(msg, log_level) + notify_msg = msg + end + vim.lsp.buf.format({ bufnr = bufnr, range = { + start = {1, 1}, + ['end'] = {1, 1}, + }}) + vim.notify = notify + return notify_msg + ]]) + eq(NIL, notify_msg) + elseif ctx.method == 'shutdown' then + client.stop() + end + end, + } + end) + it('Sends textDocument/rangesFormatting request to format multiple ranges', function() + local expected_handlers = { + { NIL, {}, { method = 'shutdown', client_id = 1 } }, + { NIL, {}, { method = 'start', client_id = 1 } }, + } + local client + test_rpc_server { + test_name = 'ranges_formatting', + on_init = function(c) + client = c + end, + on_handler = function(_, _, ctx) + table.remove(expected_handlers) + if ctx.method == 'start' then + local notify_msg = exec_lua([[ + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar', 'baz'}) + vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + local notify_msg + local notify = vim.notify + vim.notify = function(msg, log_level) + notify_msg = msg + end + vim.lsp.buf.format({ bufnr = bufnr, range = { + { + start = {1, 1}, + ['end'] = {1, 1}, + }, + { + start = {2, 2}, + ['end'] = {2, 2}, + } + }}) + vim.notify = notify + return notify_msg + ]]) + eq(NIL, notify_msg) + elseif ctx.method == 'shutdown' then + client.stop() + end + end, + } + end) it('Can format async', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, -- cgit From bda63d5b97dfb333de6f4bd757dbb978906062a2 Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 25 Jun 2024 15:33:47 +0200 Subject: refactor(typval)!: remove distinction of binary and nonbinary strings This is a breaking change which will make refactor of typval and shada code a lot easier. In particular, code that would use or check for v:msgpack_types.binary in the wild would be broken. This appears to be rarely used in existing plugins. Also some cases where v:msgpack_type.string would be used to represent a binary string of "string" type, we use a BLOB instead, which is vimscripts native type for binary blobs, and already was used for BIN formats when necessary. msgpackdump(msgpackparse(data)) no longer preserves the distinction of BIN and STR strings. This is very common behavior for language-specific msgpack bindings. Nvim uses msgpack as a tool to serialize its data. Nvim is not a tool to bit-perfectly manipulate arbitrary msgpack data out in the wild. The changed tests should indicate how behavior changes in various edge cases. --- test/functional/plugin/msgpack_spec.lua | 65 +++++----------------------- test/functional/plugin/shada_spec.lua | 77 ++++++++++++++++----------------- 2 files changed, 48 insertions(+), 94 deletions(-) (limited to 'test/functional/plugin') diff --git a/test/functional/plugin/msgpack_spec.lua b/test/functional/plugin/msgpack_spec.lua index 1d5d20ec02..61ab730da8 100644 --- a/test/functional/plugin/msgpack_spec.lua +++ b/test/functional/plugin/msgpack_spec.lua @@ -58,23 +58,11 @@ describe('autoload/msgpack.vim', function() msgpack_eq(1, '"abc\\ndef"', '"abc\\ndef"') msgpack_eq(0, '"abc\\ndef"', '"abc\\nghi"') end) - it('compares binary specials correctly', function() - msgpack_eq(1, sp('binary', '["abc\\n", "def"]'), sp('binary', '["abc\\n", "def"]')) - msgpack_eq(0, sp('binary', '["abc", "def"]'), sp('binary', '["abc\\n", "def"]')) - end) - it('compares binary specials with raw binaries correctly', function() - msgpack_eq(1, sp('binary', '["abc", "def"]'), '"abc\\ndef"') - msgpack_eq(0, sp('binary', '["abc", "def"]'), '"abcdef"') - end) it('compares string specials correctly', function() msgpack_eq(1, sp('string', '["abc\\n", "def"]'), sp('string', '["abc\\n", "def"]')) msgpack_eq(0, sp('string', '["abc", "def"]'), sp('string', '["abc\\n", "def"]')) - end) - it('compares string specials with binary correctly', function() - msgpack_eq(0, sp('string', '["abc\\n", "def"]'), sp('binary', '["abc\\n", "def"]')) - msgpack_eq(0, sp('string', '["abc", "def"]'), '"abc\\ndef"') - msgpack_eq(0, sp('binary', '["abc\\n", "def"]'), sp('string', '["abc\\n", "def"]')) - msgpack_eq(0, '"abc\\ndef"', sp('string', '["abc", "def"]')) + msgpack_eq(1, sp('string', '["abc", "def"]'), '"abc\\ndef"') + msgpack_eq(1, '"abc\\ndef"', sp('string', '["abc", "def"]')) end) it('compares ext specials correctly', function() msgpack_eq(1, sp('ext', '[1, ["", "ac"]]'), sp('ext', '[1, ["", "ac"]]')) @@ -92,20 +80,16 @@ describe('autoload/msgpack.vim', function() end) it('compares map specials correctly', function() msgpack_eq(1, mapsp(), mapsp()) - msgpack_eq(1, mapsp(sp('binary', '[""]'), '""'), mapsp(sp('binary', '[""]'), '""')) msgpack_eq( 1, mapsp(mapsp('1', '1'), mapsp('1', '1')), mapsp(mapsp('1', '1'), mapsp('1', '1')) ) msgpack_eq(0, mapsp(), mapsp('1', '1')) - msgpack_eq(0, mapsp(sp('binary', '["a"]'), '""'), mapsp(sp('binary', '[""]'), '""')) -