diff --git a/runtime/lua/vim/lsp/linked_editing_range.lua b/runtime/lua/vim/lsp/linked_editing_range.lua index 2a9ff1f7e6..10aace6096 100644 --- a/runtime/lua/vim/lsp/linked_editing_range.lua +++ b/runtime/lua/vim/lsp/linked_editing_range.lua @@ -164,6 +164,25 @@ function LinkedEditor:refresh() end) end +---@package +function LinkedEditor:new(bufnr) + self = Capability.new(self, bufnr) + + nvim_on({ 'TextChanged', 'TextChangedI' }, self.augroup, { buf = self.bufnr }, function() + for _, state in pairs(self.client_state) do + update_ranges(self.bufnr, state) + end + + self:refresh() + end) + + nvim_on('CursorMoved', self.augroup, { buf = self.bufnr }, function() + self:refresh() + end) + + return self +end + ---@package ---@param client_id integer function LinkedEditor:on_attach(client_id) @@ -176,15 +195,6 @@ function LinkedEditor:on_attach(client_id) self.client_state[client_id] = state end - nvim_on({ 'TextChanged', 'TextChangedI' }, self.augroup, { buf = self.bufnr }, function() - update_ranges(self.bufnr, state) - self:refresh() - end) - - nvim_on('CursorMoved', self.augroup, { buf = self.bufnr }, function() - self:refresh() - end) - self:refresh() end @@ -195,7 +205,6 @@ function LinkedEditor:on_detach(client_id) if client_state then --TODO: delete namespace if/when that becomes possible clear_ranges(self.bufnr, client_state) - api.nvim_clear_autocmds({ group = self.augroup }) self.client_state[client_id] = nil end end diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 685070da1e..b985a00762 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -36,12 +36,12 @@ local M = {} --- @field active_request STActiveRequest --- @field active_range_request STActiveRequest --- @field current_result STCurrentResult +--- @field timer uv.uv_timer_t? uv_timer for debouncing requests for new tokens --- @field has_full_result boolean ---@class (private) STHighlighter : vim.lsp.Capability ---@field active table ---@field debounce integer milliseconds to debounce requests for new tokens ----@field timer uv.uv_timer_t? uv_timer for debouncing requests for new tokens ---@field client_state table local STHighlighter = { name = 'semantic_tokens', @@ -207,6 +207,34 @@ local function tokens_to_ranges(data, bufnr, client, request, ranges) return ranges end +---@package +function STHighlighter:new(bufnr) + self = Capability.new(self, bufnr) + + nvim_on('LspNotify', self.augroup, { buf = self.bufnr }, function(opts) + local client_id = opts.data.client_id ---@type integer + if opts.data.method == 'textDocument/didClose' then + self:reset(client_id) + end + + if + opts.data.method == 'textDocument/didChange' or opts.data.method == 'textDocument/didOpen' + then + self:send_request(client_id) + end + end) + + nvim_on('WinScrolled', self.augroup, { buf = self.bufnr }, function() + for client_id, state in pairs(self.client_state) do + if state.supports_range then + self:debounce_request(client_id) + end + end + end) + + return self +end + ---@package function STHighlighter:on_attach(client_id) local client = vim.lsp.get_client_by_id(client_id) @@ -228,25 +256,7 @@ function STHighlighter:on_attach(client_id) self.client_state[client_id] = state end - nvim_on('LspNotify', self.augroup, { buf = self.bufnr }, function(opts) - if opts.data.method == 'textDocument/didClose' then - self:reset() - end - - if - opts.data.method == 'textDocument/didChange' or opts.data.method == 'textDocument/didOpen' - then - self:send_request() - end - end) - - if state.supports_range then - nvim_on('WinScrolled', self.augroup, { buf = self.bufnr }, function() - self:debounce_request() - end) - end - - self:send_request() + self:send_request(client_id) end ---@package @@ -255,7 +265,6 @@ function STHighlighter:on_detach(client_id) if state then --TODO: delete namespace if/when that becomes possible api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) - api.nvim_clear_autocmds({ group = self.augroup }) self.client_state[client_id] = nil end end @@ -277,28 +286,30 @@ end --- facilitate document synchronization in the response. --- ---@package -function STHighlighter:send_request() +---@param client_id integer +function STHighlighter:send_request(client_id) local version = util.buf_versions[self.bufnr] - self:reset_timer() + self:reset_timer(client_id) - for client_id, state in pairs(self.client_state) do - local client = vim.lsp.get_client_by_id(client_id) - if client then - -- If the server supports range and there's no full result yet, then start with a range - -- request - if state.supports_range and not state.has_full_result then - self:send_range_request(client, state, version) - end + local client = vim.lsp.get_client_by_id(client_id) + local state = self.client_state[client_id] - if - (not state.has_full_result or state.current_result.version ~= version) - and state.active_request.version ~= version - then - self:send_full_delta_request(client, state, version) - end + if client and state then + -- If the server supports range and there's no full result yet, then start with a range + -- request + if state.supports_range and not state.has_full_result then + self:send_range_request(client, state, version) + end + + if + (not state.has_full_result or state.current_result.version ~= version) + and state.active_request.version ~= version + then + self:send_full_delta_request(client, state, version) end end + -- end end --- Send a range request for the visible area @@ -713,13 +724,13 @@ end --- Reset the buffer's highlighting state and clears the extmark highlights. --- ---@package -function STHighlighter:reset() - for client_id, state in pairs(self.client_state) do - api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) - state.current_result = {} - state.has_full_result = false - self:cancel_active_request(client_id) - end +---@param client_id integer +function STHighlighter:reset(client_id) + local state = assert(self.client_state[client_id]) + api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) + state.current_result = {} + state.has_full_result = false + self:cancel_active_request(client_id) end --- Mark a client's results as dirty. This method will cancel any active @@ -747,22 +758,27 @@ function STHighlighter:mark_dirty(client_id) end ---@package -function STHighlighter:debounce_request() - self:reset_timer() +---@param client_id integer +function STHighlighter:debounce_request(client_id) + local state = assert(self.client_state[client_id]) + + self:reset_timer(client_id) if self.debounce > 0 then - self.timer = vim.defer_fn(function() - self:send_request() + state.timer = vim.defer_fn(function() + self:send_request(client_id) end, self.debounce) else - self:send_request() + self:send_request(client_id) end end ---@private -function STHighlighter:reset_timer() - local timer = self.timer +---@param client_id integer +function STHighlighter:reset_timer(client_id) + local state = self.client_state[client_id] + local timer = state.timer if timer then - self.timer = nil + state.timer = nil if not timer:is_closing() then timer:stop() timer:close() @@ -914,8 +930,10 @@ function M.force_refresh(bufnr) for _, buffer in ipairs(buffers) do local highlighter = STHighlighter.active[buffer] if highlighter then - highlighter:reset() - highlighter:send_request() + for client_id, _ in pairs(highlighter.client_state) do + highlighter:reset(client_id) + highlighter:send_request(client_id) + end end end end @@ -974,7 +992,7 @@ function M._refresh(err, _, ctx) if not vim.tbl_isempty(vim.fn.win_findbuf(bufnr)) then -- some LSPs send rapid fire refresh notifications, so we'll debounce them with debounce_request() - highlighter:debounce_request() + highlighter:debounce_request(ctx.client_id) end end end