aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjdrouhard <john@drouhard.dev>2022-12-09 04:54:09 -0600
committerGitHub <noreply@github.com>2022-12-09 11:54:09 +0100
commit5e6a288ce7ee079e7695525f2e9e99d071ccdfbf (patch)
tree723adee97dc5dcf22f1c4d4b6ca46ff3cc857671
parentb5edea6553b4c1b5dd3530061d907848d7272d8c (diff)
downloadrneovim-5e6a288ce7ee079e7695525f2e9e99d071ccdfbf.tar.gz
rneovim-5e6a288ce7ee079e7695525f2e9e99d071ccdfbf.tar.bz2
rneovim-5e6a288ce7ee079e7695525f2e9e99d071ccdfbf.zip
fix(lsp): followup fixes for semantic tokens support (#21357)
1. The algorithm for applying edits was slightly incorrect. It needs to preserve the original token list as the edits are applied instead of mutating it as it iterates. From the spec: Semantic token edits behave conceptually like text edits on documents: if an edit description consists of n edits all n edits are based on the same state Sm of the number array. They will move the number array from state Sm to Sm+1. 2. Schedule the semantic token engine start() call in the client._on_attach() function so that users who schedule_wrap() their config.on_attach() functions (like nvim-lspconfig does) can still disable semantic tokens by deleting the semanticTokensProvider from their server capabilities.
-rw-r--r--runtime/lua/vim/lsp.lua11
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua17
-rw-r--r--test/functional/plugin/lsp/semantic_tokens_spec.lua231
3 files changed, 237 insertions, 22 deletions
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 3d3c856fcb..f3ee484024 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1531,9 +1531,14 @@ function lsp.start_client(config)
pcall(config.on_attach, client, bufnr)
end
- if vim.tbl_get(client.server_capabilities, 'semanticTokensProvider', 'full') then
- semantic_tokens.start(bufnr, client.id)
- end
+ -- schedule the initialization of semantic tokens to give the above
+ -- on_attach and LspAttach callbacks the ability to schedule wrap the
+ -- opt-out (deleting the semanticTokensProvider from capabilities)
+ vim.schedule(function()
+ if vim.tbl_get(client.server_capabilities, 'semanticTokensProvider', 'full') then
+ semantic_tokens.start(bufnr, client.id)
+ end
+ end)
client.attached_buffers[bufnr] = true
end
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index 99cdc20f54..66e656abb6 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -313,18 +313,15 @@ function STHighlighter:process_response(response, client, version)
return a.start < b.start
end)
- ---@private
- local function _splice(list, start, remove_count, data)
- local ret = vim.list_slice(list, 1, start)
- vim.list_extend(ret, data)
- vim.list_extend(ret, list, start + remove_count + 1)
- return ret
- end
-
- tokens = state.current_result.tokens
+ tokens = {}
+ local old_tokens = state.current_result.tokens
+ local idx = 1
for _, token_edit in ipairs(token_edits) do
- tokens = _splice(tokens, token_edit.start, token_edit.deleteCount, token_edit.data)
+ vim.list_extend(tokens, old_tokens, idx, token_edit.start)
+ vim.list_extend(tokens, token_edit.data)
+ idx = token_edit.start + token_edit.deleteCount + 1
end
+ vim.list_extend(tokens, old_tokens, idx)
else
tokens = response.data
end
diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua
index 1646108416..3aeb4b264b 100644
--- a/test/functional/plugin/lsp/semantic_tokens_spec.lua
+++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua
@@ -359,9 +359,9 @@ describe('semantic token highlighting', function()
client_id = vim.lsp.start({
name = 'dummy',
cmd = server.cmd,
- on_attach = function(client, bufnr)
+ on_attach = vim.schedule_wrap(function(client, bufnr)
client.server_capabilities.semanticTokensProvider = nil
- end,
+ end),
})
]])
eq(true, exec_lua("return vim.lsp.buf_is_attached(bufnr, client_id)"))
@@ -533,7 +533,7 @@ int main()
#else
comment
#endif
-}]] ,
+}]],
response = [[{"data": [1, 4, 4, 3, 8193, 2, 9, 11, 19, 8192, 1, 12, 1, 1, 1033, 1, 2, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1032, 0, 5, 3, 15, 8448, 0, 5, 4, 3, 8448, 1, 0, 7, 20, 0, 1, 0, 11, 20, 0, 1, 0, 8, 20, 0], "resultId": "1"}]],
legend = [[{
"tokenTypes": [
@@ -681,7 +681,7 @@ b = "as"]],
break rust;
/// what?
}
-]] ,
+]],
response = [[{"data": [0, 0, 3, 1, 0, 0, 4, 2, 1, 0, 0, 3, 4, 14, 524290, 0, 4, 1, 45, 0, 0, 1, 1, 45, 0, 0, 2, 1, 26, 0, 1, 4, 5, 1, 8192, 0, 6, 4, 52, 0, 0, 4, 1, 48, 0, 1, 4, 9, 0, 1, 1, 0, 1, 26, 0], "resultId": "1"}]],
legend = [[{
"tokenTypes": [
@@ -693,7 +693,7 @@ b = "as"]],
"documentation", "declaration", "definition", "static", "abstract", "deprecated", "readonly", "defaultLibrary", "async", "attribute", "callable", "constant", "consuming", "controlFlow", "crateRoot", "injected", "intraDocLink",
"library", "mutable", "public", "reference", "trait", "unsafe"
]
- }]],
+ }]],
expected = {
{
line = 0,
@@ -818,11 +818,11 @@ b = "as"]],
end)
end
end)
+
describe('token decoding with deltas', function()
for _, test in ipairs({
{
it = 'semantic_tokens_delta: clangd-15 on C',
- name = 'semantic_tokens_delta',
legend = [[{
"tokenTypes": [
"variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment"
@@ -831,7 +831,7 @@ b = "as"]],
"declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope"
]
}]],
- text = [[char* foo = "\n";]],
+ text1 = [[char* foo = "\n";]],
edit = [[ggO<Esc>]],
response1 = [[{"data": [0, 6, 3, 0, 8193], "resultId": "1"}]],
response2 = [[{"edits": [{ "start": 0, "deleteCount": 1, "data": [1] }], "resultId": "2"}]],
@@ -861,6 +861,205 @@ b = "as"]],
extmark_added = true,
}
},
+ },
+ {
+ it = 'response with multiple delta edits',
+ legend = [[{
+ "tokenTypes": [
+ "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment"
+ ],
+ "tokenModifiers": [
+ "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope"
+ ]
+ }]],
+ text1 = dedent([[
+ #include <iostream>
+
+ int main()
+ {
+ int x;
+ #ifdef __cplusplus
+ std::cout << x << "\n";
+ #else
+ printf("%d\n", x);
+ #endif
+ }]]),
+ text2 = [[#include <iostream>
+
+int main()
+{
+ int x();
+ double y;
+#ifdef __cplusplus
+ std::cout << x << "\n";
+#else
+ printf("%d\n", x);
+#endif
+}]],
+ response1 = [[{
+ "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1024, 1, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ],
+ "resultId": 1
+ }]],
+ response2 = [[{
+ "edits": [ {"data": [ 2, 8, 1, 3, 8193, 1, 11, 1, 1, 1025 ], "deleteCount": 5, "start": 5}, {"data": [ 0, 8, 1, 3, 8192 ], "deleteCount": 5, "start": 25 } ],
+ "resultId":"2"
+ }]],
+ expected1 = {
+ {
+ line = 2,
+ start_col = 4,
+ end_col = 8,
+ modifiers = { 'declaration', 'globalScope' },
+ type = 'function',
+ extmark_added = true,
+ },
+ {
+ line = 4,
+ start_col = 8,
+ end_col = 9,
+ modifiers = { 'declaration', 'functionScope' },
+ type = 'variable',
+ extmark_added = true,
+ },
+ {
+ line = 5,
+ start_col = 7,
+ end_col = 18,
+ modifiers = { 'globalScope' },
+ type = 'macro',
+ extmark_added = true,
+ },
+ {
+ line = 6,
+ start_col = 4,
+ end_col = 7,
+ modifiers = { 'defaultLibrary', 'globalScope' },
+ type = 'namespace',
+ extmark_added = true,
+ },
+ {
+ line = 6,
+ start_col = 9,
+ end_col = 13,
+ modifiers = { 'defaultLibrary', 'globalScope' },
+ type = 'variable',
+ extmark_added = true,
+ },
+ {
+ line = 6,
+ start_col = 17,
+ end_col = 18,
+ extmark_added = true,
+ modifiers = { 'functionScope' },
+ type = 'variable',
+ },
+ {
+ line = 7,
+ start_col = 0,
+ end_col = 5,
+ extmark_added = true,
+ modifiers = {},
+ type = 'comment',
+ },
+ {
+ line = 8,
+ end_col = 22,
+ modifiers = {},
+ start_col = 0,
+ type = 'comment',
+ extmark_added = true,
+ },
+ {
+ line = 9,
+ start_col = 0,
+ end_col = 6,
+ modifiers = {},
+ type = 'comment',
+ extmark_added = true,
+ }
+ },
+ expected2 = {
+ {
+ line = 2,
+ start_col = 4,
+ end_col = 8,
+ modifiers = { 'declaration', 'globalScope' },
+ type = 'function',
+ extmark_added = true,
+ },
+ {
+ line = 4,
+ start_col = 8,
+ end_col = 9,
+ modifiers = { 'declaration', 'globalScope' },
+ type = 'function',
+ extmark_added = true,
+ },
+ {
+ line = 5,
+ end_col = 12,
+ start_col = 11,
+ modifiers = { 'declaration', 'functionScope' },
+ type = 'variable',
+ extmark_added = true,
+ },
+ {
+ line = 6,
+ start_col = 7,
+ end_col = 18,
+ modifiers = { 'globalScope' },
+ type = 'macro',
+ extmark_added = true,
+ },
+ {
+ line = 7,
+ start_col = 4,
+ end_col = 7,
+ modifiers = { 'defaultLibrary', 'globalScope' },
+ type = 'namespace',
+ extmark_added = true,
+ },
+ {
+ line = 7,
+ start_col = 9,
+ end_col = 13,
+ modifiers = { 'defaultLibrary', 'globalScope' },
+ type = 'variable',
+ extmark_added = true,
+ },
+ {
+ line = 7,
+ start_col = 17,
+ end_col = 18,
+ extmark_added = true,
+ modifiers = { 'globalScope' },
+ type = 'function',
+ },
+ {
+ line = 8,
+ start_col = 0,
+ end_col = 5,
+ extmark_added = true,
+ modifiers = {},
+ type = 'comment',
+ },
+ {
+ line = 9,
+ end_col = 22,
+ modifiers = {},
+ start_col = 0,
+ type = 'comment',
+ extmark_added = true,
+ },
+ {
+ line = 10,
+ start_col = 0,
+ end_col = 6,
+ modifiers = {},
+ type = 'comment',
+ extmark_added = true,
+ }
+ },
}
}) do
it(test.it, function()
@@ -886,10 +1085,16 @@ b = "as"]],
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 })
+
+ -- speed up vim.api.nvim_buf_set_lines calls by changing debounce to 10 for these tests
semantic_tokens = vim.lsp.semantic_tokens
+ vim.schedule(function()
+ semantic_tokens.stop(bufnr, client_id)
+ semantic_tokens.start(bufnr, client_id, { debounce = 10 })
+ end)
]], test.legend, test.response1, test.response2)
- insert(test.text)
+ insert(test.text1)
local highlights = exec_lua([[
return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
@@ -897,7 +1102,15 @@ b = "as"]],
eq(test.expected1, highlights)
- feed(test.edit)
+ if test.edit then
+ feed(test.edit)
+ else
+ 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
+ ]], test.text2)
+ end
highlights = exec_lua([[
return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights