From fec4045601c78fd0a8e5e80e45d8590f515d5e25 Mon Sep 17 00:00:00 2001 From: "neovim-backports[bot]" <175700243+neovim-backports[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:27:35 -0400 Subject: [PATCH] backport: feat(lsp): clean up semantic token initialization and debounce #40266 Problem: The refactor to use Capability left around some cruft and semi-broken configuration for debounce. Solution: Clean up now-unnecessary helper methods and simplify deprecated ones to pass through to the non-deprecated ones. `debounce` now defaults to 200 for all buffers but is overridable via the deprecated start() method, which continues to take the max value specified for any client attached to the buffer. If we wish to expose changing the debounce in a non-deprecated way, we will need to consider a "configuration" function, or even a bespoke method to set the debounce time on the main metaclass (or provide options to override for a particular buffer). General configuration of specific LSP features is an as-of-yet unsolved problem. (cherry picked from commit 859790e244932e59dcc8c83fdf1f70245f58f0ec) --- runtime/lua/vim/lsp/semantic_tokens.lua | 89 +++---------------- .../plugin/lsp/semantic_tokens_spec.lua | 6 +- 2 files changed, 13 insertions(+), 82 deletions(-) diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 4cf1498d6e..be9f8e7298 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -47,6 +47,7 @@ local M = {} local STHighlighter = { name = 'semantic_tokens', method = 'textDocument/semanticTokens', + debounce = 200, active = {}, } STHighlighter.__index = STHighlighter @@ -207,17 +208,6 @@ local function tokens_to_ranges(data, bufnr, client, request, ranges) return ranges end ---- Construct a new STHighlighter for the buffer ---- ----@private ----@param bufnr integer ----@return STHighlighter -function STHighlighter:new(bufnr) - self.debounce = 200 - self = Capability.new(self, bufnr) - return self -end - ---@package function STHighlighter:on_attach(client_id) local client = vim.lsp.get_client_by_id(client_id) @@ -795,23 +785,6 @@ function STHighlighter:reset_timer() end end ----@param bufnr (integer) Buffer number, or `0` for current buffer ----@param client_id (integer) The ID of the |vim.lsp.Client| ----@param debounce? (integer) (default: 200): Debounce token requests ---- to the server by the given number in milliseconds -function M._start(bufnr, client_id, debounce) - local highlighter = STHighlighter.active[bufnr] - - if not highlighter then - highlighter = STHighlighter:new(bufnr) - highlighter.debounce = debounce or 200 - else - highlighter.debounce = debounce or highlighter.debounce - end - - highlighter:on_attach(client_id) -end - --- Start the semantic token highlighting engine for the given buffer with the --- given client. The client must already be attached to the buffer. --- @@ -836,66 +809,24 @@ function M.start(bufnr, client_id, opts) vim.validate('bufnr', bufnr, 'number') vim.validate('client_id', client_id, 'number') - bufnr = vim._resolve_bufnr(bufnr) + M.enable(true, { bufnr = bufnr, client_id = client_id }) - opts = opts or {} - assert( - (not opts.debounce or type(opts.debounce) == 'number'), - 'opts.debounce must be a number with the debounce time in milliseconds' - ) - - local client = vim.lsp.get_client_by_id(client_id) - if not client then - vim.notify('[LSP] No client with id ' .. client_id, vim.log.levels.ERROR) - return + if opts and opts.debounce then + local highlighter = STHighlighter.active[bufnr] + local prev = rawget(highlighter, 'debounce') + highlighter.debounce = prev and math.max(prev, opts.debounce) or opts.debounce end - - if not vim.lsp.buf_is_attached(bufnr, client_id) then - vim.notify( - '[LSP] Client with id ' .. client_id .. ' not attached to buffer ' .. bufnr, - vim.log.levels.WARN - ) - return - end - - if - not client:supports_method('textDocument/semanticTokens/full', bufnr) - and not client:supports_method('textDocument/semanticTokens/range', bufnr) - then - vim.notify('[LSP] Server does not support semantic tokens', vim.log.levels.WARN) - return - end - - M._start(bufnr, client_id, opts.debounce) end --- Stop the semantic token highlighting engine for the given buffer with the --- given client. --- ---- NOTE: This is automatically called by a |LspDetach| autocmd that is set up as part ---- of `start()`, so you should only need this function to manually disengage the semantic ---- token engine without fully detaching the LSP client from the buffer. ---- ---@deprecated ---@param bufnr (integer) Buffer number, or `0` for current buffer ---@param client_id (integer) The ID of the |vim.lsp.Client| function M.stop(bufnr, client_id) vim.deprecate('vim.lsp.semantic_tokens.stop', 'vim.lsp.semantic_tokens.enable(false)', '0.13.0') - vim.validate('bufnr', bufnr, 'number') - vim.validate('client_id', client_id, 'number') - - bufnr = vim._resolve_bufnr(bufnr) - - local highlighter = STHighlighter.active[bufnr] - if not highlighter then - return - end - - highlighter:on_detach(client_id) - - if vim.tbl_isempty(highlighter.client_state) then - highlighter:destroy() - end + M.enable(false, { bufnr = bufnr, client_id = client_id }) end --- Query whether semantic tokens is enabled in the {filter}ed scope @@ -1076,12 +1007,12 @@ api.nvim_set_decoration_provider(namespace, { end, }) +-- Semantic tokens is enabled by default +M.enable(true) + --- for testing only! there is no guarantee of API stability with this! --- ---@private M.__STHighlighter = STHighlighter --- Semantic tokens is enabled by default -vim.lsp._capability.enable('semantic_tokens', true) - return M diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 10ad9b7a2d..964f2d5d8f 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -31,6 +31,9 @@ before_each(function() clear_notrace() exec_lua(create_server_definition) exec_lua(create_start_server) + exec_lua(function() + vim.lsp.semantic_tokens.__STHighlighter.debounce = 0 -- disable internal debounce to make tests faster + end) end) after_each(function() @@ -371,9 +374,6 @@ describe('semantic token highlighting', function() }) local bufnr = vim.api.nvim_get_current_buf() local client_id = assert(_G._start_server(_G.server2)) - vim.schedule(function() - vim.lsp.semantic_tokens._start(bufnr, client_id, 0) - end) return client_id, bufnr end)