From b3fbc8d6fa2abe732b996d67cbaf5ab9c6d3414b Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Mon, 21 Jul 2025 13:10:54 +0800 Subject: [PATCH] refactor(lsp): move `util.enable` to `capability.enable` --- runtime/doc/lsp.txt | 18 +++-- runtime/lua/vim/lsp/_capability.lua | 85 ++++++++++++++++++++ runtime/lua/vim/lsp/client.lua | 6 +- runtime/lua/vim/lsp/linked_editing_range.lua | 7 +- runtime/lua/vim/lsp/semantic_tokens.lua | 10 +-- runtime/lua/vim/lsp/util.lua | 79 ------------------ 6 files changed, 109 insertions(+), 96 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index d18329aee8..6b9f81fd45 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -2431,10 +2431,11 @@ enable({enable}, {filter}) *vim.lsp.semantic_tokens.enable()* Parameters: ~ • {enable} (`boolean?`) true/nil to enable, false to disable - • {filter} (`table?`) A table with the following fields: - • {bufnr}? (`integer`) Buffer number, or 0 for current - buffer, or nil for all. - • {client_id}? (`integer`) Client ID, or nil for all + • {filter} (`table?`) Optional filters |kwargs|, + • {bufnr}? (`integer`, default: all) Buffer number, or 0 for + current buffer, or nil for all. + • {client_id}? (`integer`, default: all) Client ID, or nil + for all. force_refresh({bufnr}) *vim.lsp.semantic_tokens.force_refresh()* Force a refresh of all semantic tokens @@ -2493,10 +2494,11 @@ is_enabled({filter}) *vim.lsp.semantic_tokens.is_enabled()* Query whether semantic tokens is enabled in the {filter}ed scope Parameters: ~ - • {filter} (`table?`) A table with the following fields: - • {bufnr}? (`integer`) Buffer number, or 0 for current - buffer, or nil for all. - • {client_id}? (`integer`) Client ID, or nil for all + • {filter} (`table?`) Optional filters |kwargs|, + • {bufnr}? (`integer`, default: all) Buffer number, or 0 for + current buffer, or nil for all. + • {client_id}? (`integer`, default: all) Client ID, or nil + for all. ============================================================================== diff --git a/runtime/lua/vim/lsp/_capability.lua b/runtime/lua/vim/lsp/_capability.lua index 5b38f93e30..5acd9bf067 100644 --- a/runtime/lua/vim/lsp/_capability.lua +++ b/runtime/lua/vim/lsp/_capability.lua @@ -3,6 +3,7 @@ local api = vim.api ---@alias vim.lsp.capability.Name ---| 'semantic_tokens' ---| 'folding_range' +---| 'linked_editing_range' --- Tracks all supported capabilities, all of which derive from `vim.lsp.Capability`. --- Returns capability *prototypes*, not their instances. @@ -99,6 +100,90 @@ function M:on_detach(client_id) self.client_state[client_id] = nil end +---@param name vim.lsp.capability.Name +local function make_enable_var(name) + return ('_lsp_enabled_%s'):format(name) +end + +--- Optional filters |kwargs|, +---@class vim.lsp.capability.enable.Filter +---@inlinedoc +--- +--- Buffer number, or 0 for current buffer, or nil for all. +--- (default: all) +---@field bufnr? integer +--- +--- Client ID, or nil for all. +--- (default: all) +---@field client_id? integer + +---@param name vim.lsp.capability.Name +---@param enable? boolean +---@param filter? vim.lsp.capability.enable.Filter +function M.enable(name, enable, filter) + vim.validate('name', name, 'string') + vim.validate('enable', enable, 'boolean', true) + vim.validate('filter', filter, 'table', true) + + enable = enable == nil or enable + filter = filter or {} + local bufnr = filter.bufnr and vim._resolve_bufnr(filter.bufnr) + local client_id = filter.client_id + assert(not (bufnr and client_id), '`bufnr` and `client_id` are mutually exclusive.') + + local var = make_enable_var(name) + local client = client_id and vim.lsp.get_client_by_id(client_id) + + -- Updates the marker value. + -- If local marker matches the global marker, set it to nil + -- so that `is_enable` falls back to the global marker. + if client then + if enable == vim.g[var] then + client._enabled_capabilities[name] = nil + else + client._enabled_capabilities[name] = enable + end + elseif bufnr then + if enable == vim.g[var] then + vim.b[bufnr][var] = nil + else + vim.b[bufnr][var] = enable + end + else + vim.g[var] = enable + for _, it_bufnr in ipairs(api.nvim_list_bufs()) do + if api.nvim_buf_is_loaded(it_bufnr) and vim.b[it_bufnr][var] == enable then + vim.b[it_bufnr][var] = nil + end + end + for _, it_client in ipairs(vim.lsp.get_clients()) do + if it_client._enabled_capabilities[name] == enable then + it_client._enabled_capabilities[name] = nil + end + end + end +end + +---@param name vim.lsp.capability.Name +---@param filter? vim.lsp.capability.enable.Filter +function M.is_enabled(name, filter) + vim.validate('name', name, 'string') + vim.validate('filter', filter, 'table', true) + + filter = filter or {} + local bufnr = filter.bufnr and vim._resolve_bufnr(filter.bufnr) + local client_id = filter.client_id + + local var = make_enable_var(name) + local client = client_id and vim.lsp.get_client_by_id(client_id) + + -- As a fallback when not explicitly enabled or disabled: + -- Clients are treated as "enabled" since their capabilities can control behavior. + -- Buffers are treated as "disabled" to allow users to enable them as needed. + return vim.F.if_nil(client and client._enabled_capabilities[name], vim.g[var], true) + and vim.F.if_nil(bufnr and vim.b[bufnr][var], vim.g[var], false) +end + M.all = all_capabilities return M diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 7d99f226e0..25f8307b70 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -209,8 +209,7 @@ local all_clients = {} --- See [vim.lsp.ClientConfig]. --- @field workspace_folders lsp.WorkspaceFolder[]? --- ---- Whether linked editing ranges are enabled for this client. ---- @field _linked_editing_enabled boolean? +--- @field _enabled_capabilities table --- --- Track this so that we can escalate automatically if we've already tried a --- graceful shutdown @@ -436,6 +435,9 @@ function Client.create(config) end, } + ---@type table + self._enabled_capabilities = {} + --- @type table title of unfinished progress sequences by token self.progress.pending = {} diff --git a/runtime/lua/vim/lsp/linked_editing_range.lua b/runtime/lua/vim/lsp/linked_editing_range.lua index ad18a9180e..1146ddd4ee 100644 --- a/runtime/lua/vim/lsp/linked_editing_range.lua +++ b/runtime/lua/vim/lsp/linked_editing_range.lua @@ -268,7 +268,10 @@ api.nvim_create_autocmd('LspAttach', { desc = 'Enable linked editing ranges for all buffers this client attaches to, if enabled', callback = function(ev) local client = assert(lsp.get_client_by_id(ev.data.client_id)) - if not client._linked_editing_enabled or not client:supports_method(method, ev.buf) then + if + not client._enabled_capabilities['linked_editing_range'] + or not client:supports_method(method, ev.buf) + then return end @@ -286,7 +289,7 @@ local function toggle_linked_editing_for_client(enable, client) handler(bufnr, client) end - client._linked_editing_enabled = enable + client._enabled_capabilities['linked_editing_range'] = enable end ---@param enable boolean diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index cd3c1ebc53..faf147ee00 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -670,9 +670,9 @@ function M.stop(bufnr, client_id) end --- Query whether semantic tokens is enabled in the {filter}ed scope ----@param filter? vim.lsp.enable.Filter +---@param filter? vim.lsp.capability.enable.Filter function M.is_enabled(filter) - return util._is_enabled('semantic_tokens', filter) + return vim.lsp._capability.is_enabled('semantic_tokens', filter) end --- Enables or disables semantic tokens for the {filter}ed scope. @@ -684,9 +684,9 @@ end --- ``` --- ---@param enable? boolean true/nil to enable, false to disable ----@param filter? vim.lsp.enable.Filter +---@param filter? vim.lsp.capability.enable.Filter function M.enable(enable, filter) - util._enable('semantic_tokens', enable, filter) + vim.lsp._capability.enable('semantic_tokens', enable, filter) for _, bufnr in ipairs(api.nvim_list_bufs()) do local highlighter = STHighlighter.active[bufnr] @@ -863,6 +863,6 @@ api.nvim_set_decoration_provider(namespace, { M.__STHighlighter = STHighlighter -- Semantic tokens is enabled by default -util._enable('semantic_tokens', true) +vim.lsp._capability.enable('semantic_tokens', true) return M diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 6c3641e627..1d8265436d 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -2331,85 +2331,6 @@ function M._cancel_requests(filter) end end ----@param feature string ----@param client_id? integer -local function make_enable_var(feature, client_id) - return ('_lsp_enabled_%s%s'):format(feature, client_id and ('_client_%d'):format(client_id) or '') -end - ----@class vim.lsp.enable.Filter ----@inlinedoc ---- ---- Buffer number, or 0 for current buffer, or nil for all. ----@field bufnr? integer ---- ---- Client ID, or nil for all ----@field client_id? integer - ----@param feature string ----@param filter? vim.lsp.enable.Filter -function M._is_enabled(feature, filter) - vim.validate('feature', feature, 'string') - vim.validate('filter', filter, 'table', true) - - filter = filter or {} - local bufnr = filter.bufnr - local client_id = filter.client_id - - local var = make_enable_var(feature) - local client_var = make_enable_var(feature, client_id) - return vim.F.if_nil(client_id and vim.g[client_var], vim.g[var]) - and vim.F.if_nil(bufnr and vim.b[bufnr][var], vim.g[var]) -end - ----@param feature 'semantic_tokens' ----@param enable? boolean ----@param filter? vim.lsp.enable.Filter -function M._enable(feature, enable, filter) - vim.validate('feature', feature, 'string') - vim.validate('enable', enable, 'boolean', true) - vim.validate('filter', filter, 'table', true) - - enable = enable == nil or enable - filter = filter or {} - local bufnr = filter.bufnr - local client_id = filter.client_id - assert( - not (bufnr and client_id), - 'Only one of `bufnr` or `client_id` filters can be specified at a time.' - ) - - local var = make_enable_var(feature) - local client_var = make_enable_var(feature, client_id) - - if client_id then - if enable == vim.g[var] then - vim.g[client_var] = nil - else - vim.g[client_var] = enable - end - elseif bufnr then - if enable == vim.g[var] then - vim.b[bufnr][var] = nil - else - vim.b[bufnr][var] = enable - end - else - vim.g[var] = enable - for _, it_bufnr in ipairs(api.nvim_list_bufs()) do - if api.nvim_buf_is_loaded(it_bufnr) and vim.b[it_bufnr][var] == enable then - vim.b[it_bufnr][var] = nil - end - end - for _, it_client in ipairs(vim.lsp.get_clients()) do - local it_client_var = make_enable_var(feature, it_client.id) - if vim.g[it_client_var] and vim.g[it_client_var] == enable then - vim.g[it_client_var] = nil - end - end - end -end - M._get_line_byte_from_position = get_line_byte_from_position ---@nodoc