commit b3fbc8d6fa2abe732b996d67cbaf5ab9c6d3414b
parent a37e101dc76232483f5aa6bfc1087b4fff6950d2
Author: Yi Ming <ofseed@foxmail.com>
Date: Mon, 21 Jul 2025 13:10:54 +0800
refactor(lsp): move `util.enable` to `capability.enable`
Diffstat:
6 files changed, 109 insertions(+), 96 deletions(-)
diff --git 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
@@ -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
@@ -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<vim.lsp.capability.Name, boolean?>
---
--- 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 <vim.lsp.capability.Name, boolean?>
+ self._enabled_capabilities = {}
+
--- @type table<string|integer, string> 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
@@ -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
@@ -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
@@ -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