mirror of
https://github.com/neovim/neovim.git
synced 2025-12-16 19:35:37 +00:00
Merge #35018 refactor(lsp): centralized enable/is_enabled strategy
This commit is contained in:
@@ -2431,10 +2431,11 @@ enable({enable}, {filter}) *vim.lsp.semantic_tokens.enable()*
|
|||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
• {enable} (`boolean?`) true/nil to enable, false to disable
|
• {enable} (`boolean?`) true/nil to enable, false to disable
|
||||||
• {filter} (`table?`) A table with the following fields:
|
• {filter} (`table?`) Optional filters |kwargs|,
|
||||||
• {bufnr}? (`integer`) Buffer number, or 0 for current
|
• {bufnr}? (`integer`, default: all) Buffer number, or 0 for
|
||||||
buffer, or nil for all.
|
current buffer, or nil for all.
|
||||||
• {client_id}? (`integer`) Client ID, 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_refresh({bufnr}) *vim.lsp.semantic_tokens.force_refresh()*
|
||||||
Force a refresh of all semantic tokens
|
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
|
Query whether semantic tokens is enabled in the {filter}ed scope
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
• {filter} (`table?`) A table with the following fields:
|
• {filter} (`table?`) Optional filters |kwargs|,
|
||||||
• {bufnr}? (`integer`) Buffer number, or 0 for current
|
• {bufnr}? (`integer`, default: all) Buffer number, or 0 for
|
||||||
buffer, or nil for all.
|
current buffer, or nil for all.
|
||||||
• {client_id}? (`integer`) Client ID, or nil for all
|
• {client_id}? (`integer`, default: all) Client ID, or nil
|
||||||
|
for all.
|
||||||
|
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|||||||
@@ -1,18 +1,32 @@
|
|||||||
local api = vim.api
|
local api = vim.api
|
||||||
|
|
||||||
--- `vim.lsp.Capability` is expected to be created one-to-one with a buffer
|
---@alias vim.lsp.capability.Name
|
||||||
--- when there is at least one supported client attached to that buffer,
|
---| 'semantic_tokens'
|
||||||
--- and will be destroyed when all supporting clients are detached.
|
---| 'folding_range'
|
||||||
|
---| 'linked_editing_range'
|
||||||
|
|
||||||
|
--- Tracks all supported capabilities, all of which derive from `vim.lsp.Capability`.
|
||||||
|
--- Returns capability *prototypes*, not their instances.
|
||||||
|
---@type table<vim.lsp.capability.Name, vim.lsp.Capability>
|
||||||
|
local all_capabilities = {}
|
||||||
|
|
||||||
|
-- Abstract base class (not instantiable directly).
|
||||||
|
-- For each buffer that has at least one supported client attached,
|
||||||
|
-- exactly one instance of each concrete subclass is created.
|
||||||
|
-- That instance is destroyed once all supporting clients detach from the buffer.
|
||||||
---@class vim.lsp.Capability
|
---@class vim.lsp.Capability
|
||||||
---
|
---
|
||||||
|
--- Static field as the identifier of the LSP capability it supports.
|
||||||
|
---@field name vim.lsp.capability.Name
|
||||||
|
---
|
||||||
|
--- Static field records the method this capability requires.
|
||||||
|
---@field method vim.lsp.protocol.Method.ClientToServer
|
||||||
|
---
|
||||||
--- Static field for retrieving the instance associated with a specific `bufnr`.
|
--- Static field for retrieving the instance associated with a specific `bufnr`.
|
||||||
---
|
---
|
||||||
--- Index inthe form of `bufnr` -> `capability`
|
--- Index in the form of `bufnr` -> `capability`
|
||||||
---@field active table<integer, vim.lsp.Capability?>
|
---@field active table<integer, vim.lsp.Capability?>
|
||||||
---
|
---
|
||||||
--- The LSP feature it supports.
|
|
||||||
---@field name string
|
|
||||||
---
|
|
||||||
--- Buffer number it associated with.
|
--- Buffer number it associated with.
|
||||||
---@field bufnr integer
|
---@field bufnr integer
|
||||||
---
|
---
|
||||||
@@ -33,27 +47,21 @@ function M:new(bufnr)
|
|||||||
-- `Class` may be a subtype of `Capability`, as it supports inheritance.
|
-- `Class` may be a subtype of `Capability`, as it supports inheritance.
|
||||||
---@type vim.lsp.Capability
|
---@type vim.lsp.Capability
|
||||||
local Class = self
|
local Class = self
|
||||||
assert(Class.name and Class.active, 'Do not instantiate the abstract class')
|
if M == Class then
|
||||||
|
error('Do not instantiate the abstract class')
|
||||||
|
elseif all_capabilities[Class.name] and all_capabilities[Class.name] ~= Class then
|
||||||
|
error('Duplicated capability name')
|
||||||
|
else
|
||||||
|
all_capabilities[Class.name] = Class
|
||||||
|
end
|
||||||
|
|
||||||
---@type vim.lsp.Capability
|
---@type vim.lsp.Capability
|
||||||
self = setmetatable({}, Class)
|
self = setmetatable({}, Class)
|
||||||
self.bufnr = bufnr
|
self.bufnr = bufnr
|
||||||
self.augroup = api.nvim_create_augroup(
|
self.augroup = api.nvim_create_augroup(string.format('nvim.lsp.%s:%s', self.name, bufnr), {
|
||||||
string.format('nvim.lsp.%s:%s', self.name:gsub('%s+', '_'):lower(), bufnr),
|
clear = true,
|
||||||
{ clear = true }
|
|
||||||
)
|
|
||||||
self.client_state = {}
|
|
||||||
|
|
||||||
api.nvim_create_autocmd('LspDetach', {
|
|
||||||
group = self.augroup,
|
|
||||||
buffer = bufnr,
|
|
||||||
callback = function(args)
|
|
||||||
self:on_detach(args.data.client_id)
|
|
||||||
if next(self.client_state) == nil then
|
|
||||||
self:destroy()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
})
|
||||||
|
self.client_state = {}
|
||||||
|
|
||||||
Class.active[bufnr] = self
|
Class.active[bufnr] = self
|
||||||
return self
|
return self
|
||||||
@@ -69,9 +77,137 @@ function M:destroy()
|
|||||||
self.active[self.bufnr] = nil
|
self.active[self.bufnr] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Callback invoked when an LSP client attaches.
|
||||||
|
--- Use it to initialize per-client state (empty table, new namespaces, etc.),
|
||||||
|
--- or issue requests as needed.
|
||||||
|
---@param client_id integer
|
||||||
|
function M:on_attach(client_id)
|
||||||
|
self.client_state[client_id] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Callback invoked when an LSP client detaches.
|
||||||
|
--- Use it to clear per-client state (cached data, extmarks, etc.).
|
||||||
---@param client_id integer
|
---@param client_id integer
|
||||||
function M:on_detach(client_id)
|
function M:on_detach(client_id)
|
||||||
self.client_state[client_id] = nil
|
self.client_state[client_id] = nil
|
||||||
end
|
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)
|
||||||
|
|
||||||
|
-- Attach or detach the client and its capability
|
||||||
|
-- based on the user’s latest marker value.
|
||||||
|
for _, it_client in ipairs(client and { client } or vim.lsp.get_clients()) do
|
||||||
|
for _, it_bufnr in
|
||||||
|
ipairs(
|
||||||
|
bufnr and { it_client.attached_buffers[bufnr] and bufnr }
|
||||||
|
or vim.lsp.get_buffers_by_client_id(it_client.id)
|
||||||
|
)
|
||||||
|
do
|
||||||
|
if enable ~= M.is_enabled(name, { bufnr = it_bufnr, client_id = it_client.id }) then
|
||||||
|
local Capability = all_capabilities[name]
|
||||||
|
|
||||||
|
if enable then
|
||||||
|
if it_client:supports_method(Capability.method) then
|
||||||
|
local capability = Capability.active[bufnr] or Capability:new(it_bufnr)
|
||||||
|
if not capability.client_state[it_client.id] then
|
||||||
|
capability:on_attach(it_client.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local capability = Capability.active[it_bufnr]
|
||||||
|
if capability then
|
||||||
|
capability:on_detach(it_client.id)
|
||||||
|
if not next(capability.client_state) then
|
||||||
|
capability:destroy()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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
|
return M
|
||||||
|
|||||||
@@ -35,9 +35,14 @@ local Capability = require('vim.lsp._capability')
|
|||||||
---
|
---
|
||||||
--- Index in the form of start_row -> collapsed_text
|
--- Index in the form of start_row -> collapsed_text
|
||||||
---@field row_text table<integer, string?>
|
---@field row_text table<integer, string?>
|
||||||
local State = { name = 'Folding Range', active = {} }
|
local State = {
|
||||||
|
name = 'folding_range',
|
||||||
|
method = ms.textDocument_foldingRange,
|
||||||
|
active = {},
|
||||||
|
}
|
||||||
State.__index = State
|
State.__index = State
|
||||||
setmetatable(State, Capability)
|
setmetatable(State, Capability)
|
||||||
|
Capability.all[State.name] = State
|
||||||
|
|
||||||
--- Re-evaluate the cached foldinfo in the buffer.
|
--- Re-evaluate the cached foldinfo in the buffer.
|
||||||
function State:evaluate()
|
function State:evaluate()
|
||||||
@@ -87,9 +92,6 @@ end
|
|||||||
--- Force `foldexpr()` to be re-evaluated, without opening folds.
|
--- Force `foldexpr()` to be re-evaluated, without opening folds.
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
local function foldupdate(bufnr)
|
local function foldupdate(bufnr)
|
||||||
if not api.nvim_buf_is_loaded(bufnr) or not vim.b[bufnr]._lsp_enable_folding_range then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do
|
for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do
|
||||||
local wininfo = vim.fn.getwininfo(winid)[1]
|
local wininfo = vim.fn.getwininfo(winid)[1]
|
||||||
if wininfo and wininfo.tabnr == vim.fn.tabpagenr() then
|
if wininfo and wininfo.tabnr == vim.fn.tabpagenr() then
|
||||||
@@ -159,10 +161,6 @@ end
|
|||||||
--- `foldupdate()` is scheduled once after the request is completed.
|
--- `foldupdate()` is scheduled once after the request is completed.
|
||||||
---@param client? vim.lsp.Client The client whose server supports `foldingRange`.
|
---@param client? vim.lsp.Client The client whose server supports `foldingRange`.
|
||||||
function State:refresh(client)
|
function State:refresh(client)
|
||||||
if not vim.b._lsp_enable_folding_range then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type lsp.FoldingRangeParams
|
---@type lsp.FoldingRangeParams
|
||||||
local params = { textDocument = util.make_text_document_params(self.bufnr) }
|
local params = { textDocument = util.make_text_document_params(self.bufnr) }
|
||||||
|
|
||||||
@@ -252,7 +250,7 @@ function State:new(bufnr)
|
|||||||
pattern = 'foldexpr',
|
pattern = 'foldexpr',
|
||||||
callback = function()
|
callback = function()
|
||||||
if vim.v.option_type == 'global' or vim.api.nvim_get_current_buf() == bufnr then
|
if vim.v.option_type == 'global' or vim.api.nvim_get_current_buf() == bufnr then
|
||||||
vim.b[bufnr]._lsp_enable_folding_range = nil
|
vim.lsp._capability.enable('folding_range', false, { bufnr = bufnr })
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
@@ -265,6 +263,12 @@ function State:destroy()
|
|||||||
State.active[self.bufnr] = nil
|
State.active[self.bufnr] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param client_id integer
|
||||||
|
function State:on_attach(client_id)
|
||||||
|
self.client_state = {}
|
||||||
|
self:refresh(vim.lsp.get_client_by_id(client_id))
|
||||||
|
end
|
||||||
|
|
||||||
---@params client_id integer
|
---@params client_id integer
|
||||||
function State:on_detach(client_id)
|
function State:on_detach(client_id)
|
||||||
self.client_state[client_id] = nil
|
self.client_state[client_id] = nil
|
||||||
@@ -272,17 +276,6 @@ function State:on_detach(client_id)
|
|||||||
foldupdate(self.bufnr)
|
foldupdate(self.bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
|
||||||
---@param client_id? integer
|
|
||||||
function M._setup(bufnr, client_id)
|
|
||||||
local state = State.active[bufnr]
|
|
||||||
if not state then
|
|
||||||
state = State:new(bufnr)
|
|
||||||
end
|
|
||||||
|
|
||||||
state:refresh(client_id and vim.lsp.get_client_by_id(client_id))
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param kind lsp.FoldingRangeKind
|
---@param kind lsp.FoldingRangeKind
|
||||||
---@param winid integer
|
---@param winid integer
|
||||||
function State:foldclose(kind, winid)
|
function State:foldclose(kind, winid)
|
||||||
@@ -348,14 +341,14 @@ end
|
|||||||
---@return string level
|
---@return string level
|
||||||
function M.foldexpr(lnum)
|
function M.foldexpr(lnum)
|
||||||
local bufnr = api.nvim_get_current_buf()
|
local bufnr = api.nvim_get_current_buf()
|
||||||
local state = State.active[bufnr]
|
if not vim.lsp._capability.is_enabled('folding_range', { bufnr = bufnr }) then
|
||||||
if not vim.b[bufnr]._lsp_enable_folding_range then
|
-- `foldexpr` lead to a textlock, so any further operations need to be scheduled.
|
||||||
vim.b[bufnr]._lsp_enable_folding_range = true
|
vim.schedule(function()
|
||||||
if state then
|
vim.lsp._capability.enable('folding_range', true, { bufnr = bufnr })
|
||||||
state:refresh()
|
end)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local state = State.active[bufnr]
|
||||||
if not state then
|
if not state then
|
||||||
return '0'
|
return '0'
|
||||||
end
|
end
|
||||||
@@ -364,6 +357,4 @@ function M.foldexpr(lnum)
|
|||||||
return level and (level[2] or '') .. (level[1] or '0') or '0'
|
return level and (level[2] or '') .. (level[1] or '0') or '0'
|
||||||
end
|
end
|
||||||
|
|
||||||
M.__FoldEvaluator = State
|
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -209,8 +209,7 @@ local all_clients = {}
|
|||||||
--- See [vim.lsp.ClientConfig].
|
--- See [vim.lsp.ClientConfig].
|
||||||
--- @field workspace_folders lsp.WorkspaceFolder[]?
|
--- @field workspace_folders lsp.WorkspaceFolder[]?
|
||||||
---
|
---
|
||||||
--- Whether linked editing ranges are enabled for this client.
|
--- @field _enabled_capabilities table<vim.lsp.capability.Name, boolean?>
|
||||||
--- @field _linked_editing_enabled boolean?
|
|
||||||
---
|
---
|
||||||
--- Track this so that we can escalate automatically if we've already tried a
|
--- Track this so that we can escalate automatically if we've already tried a
|
||||||
--- graceful shutdown
|
--- graceful shutdown
|
||||||
@@ -436,6 +435,9 @@ function Client.create(config)
|
|||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@type table <vim.lsp.capability.Name, boolean?>
|
||||||
|
self._enabled_capabilities = {}
|
||||||
|
|
||||||
--- @type table<string|integer, string> title of unfinished progress sequences by token
|
--- @type table<string|integer, string> title of unfinished progress sequences by token
|
||||||
self.progress.pending = {}
|
self.progress.pending = {}
|
||||||
|
|
||||||
@@ -509,6 +511,10 @@ function Client:initialize()
|
|||||||
root_path = vim.uri_to_fname(root_uri)
|
root_path = vim.uri_to_fname(root_uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- HACK: Capability modules must be loaded
|
||||||
|
require('vim.lsp.semantic_tokens')
|
||||||
|
require('vim.lsp._folding_range')
|
||||||
|
|
||||||
local init_params = {
|
local init_params = {
|
||||||
-- The process Id of the parent process that started the server. Is null if
|
-- The process Id of the parent process that started the server. Is null if
|
||||||
-- the process has not been started by another process. If the parent
|
-- the process has not been started by another process. If the parent
|
||||||
@@ -1084,16 +1090,21 @@ function Client:on_attach(bufnr)
|
|||||||
})
|
})
|
||||||
|
|
||||||
self:_run_callbacks(self._on_attach_cbs, lsp.client_errors.ON_ATTACH_ERROR, self, bufnr)
|
self:_run_callbacks(self._on_attach_cbs, lsp.client_errors.ON_ATTACH_ERROR, self, bufnr)
|
||||||
|
-- schedule the initialization of capabilities to give the above
|
||||||
-- schedule the initialization of semantic tokens to give the above
|
|
||||||
-- on_attach and LspAttach callbacks the ability to schedule wrap the
|
-- on_attach and LspAttach callbacks the ability to schedule wrap the
|
||||||
-- opt-out (deleting the semanticTokensProvider from capabilities)
|
-- opt-out (deleting the semanticTokensProvider from capabilities)
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
if vim.tbl_get(self.server_capabilities, 'semanticTokensProvider', 'full') then
|
for _, Capability in pairs(vim.lsp._capability.all) do
|
||||||
lsp.semantic_tokens._start(bufnr, self.id)
|
if
|
||||||
|
self:supports_method(Capability.method)
|
||||||
|
and vim.lsp._capability.is_enabled(Capability.name, {
|
||||||
|
bufnr = bufnr,
|
||||||
|
client_id = self.id,
|
||||||
|
})
|
||||||
|
then
|
||||||
|
local capability = Capability.active[bufnr] or Capability:new(bufnr)
|
||||||
|
capability:on_attach(self.id)
|
||||||
end
|
end
|
||||||
if vim.tbl_get(self.server_capabilities, 'foldingRangeProvider') then
|
|
||||||
lsp._folding_range._setup(bufnr)
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -1207,6 +1218,24 @@ function Client:_on_detach(bufnr)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for _, Capability in pairs(vim.lsp._capability.all) do
|
||||||
|
if
|
||||||
|
self:supports_method(Capability.method)
|
||||||
|
and vim.lsp._capability.is_enabled(Capability.name, {
|
||||||
|
bufnr = bufnr,
|
||||||
|
client_id = self.id,
|
||||||
|
})
|
||||||
|
then
|
||||||
|
local capability = Capability.active[bufnr]
|
||||||
|
if capability then
|
||||||
|
capability:on_detach(self.id)
|
||||||
|
if next(capability.client_state) == nil then
|
||||||
|
capability:destroy()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
changetracking.reset_buf(self, bufnr)
|
changetracking.reset_buf(self, bufnr)
|
||||||
|
|
||||||
if self:supports_method(ms.textDocument_didClose) then
|
if self:supports_method(ms.textDocument_didClose) then
|
||||||
|
|||||||
@@ -30,15 +30,10 @@ end
|
|||||||
|
|
||||||
local function check_active_features()
|
local function check_active_features()
|
||||||
vim.health.start('vim.lsp: Active Features')
|
vim.health.start('vim.lsp: Active Features')
|
||||||
---@type vim.lsp.Capability[]
|
for _, Capability in pairs(vim.lsp._capability.all) do
|
||||||
local features = {
|
|
||||||
require('vim.lsp.semantic_tokens').__STHighlighter,
|
|
||||||
require('vim.lsp._folding_range').__FoldEvaluator,
|
|
||||||
}
|
|
||||||
for _, feature in ipairs(features) do
|
|
||||||
---@type string[]
|
---@type string[]
|
||||||
local buf_infos = {}
|
local buf_infos = {}
|
||||||
for bufnr, instance in pairs(feature.active) do
|
for bufnr, instance in pairs(Capability.active) do
|
||||||
local client_info = vim
|
local client_info = vim
|
||||||
.iter(pairs(instance.client_state))
|
.iter(pairs(instance.client_state))
|
||||||
:map(function(client_id)
|
:map(function(client_id)
|
||||||
@@ -58,7 +53,7 @@ local function check_active_features()
|
|||||||
end
|
end
|
||||||
|
|
||||||
report_info(table.concat({
|
report_info(table.concat({
|
||||||
feature.name,
|
Capability.name,
|
||||||
'- Active buffers:',
|
'- Active buffers:',
|
||||||
string.format(table.concat(buf_infos, '\n')),
|
string.format(table.concat(buf_infos, '\n')),
|
||||||
}, '\n'))
|
}, '\n'))
|
||||||
|
|||||||
@@ -268,7 +268,10 @@ api.nvim_create_autocmd('LspAttach', {
|
|||||||
desc = 'Enable linked editing ranges for all buffers this client attaches to, if enabled',
|
desc = 'Enable linked editing ranges for all buffers this client attaches to, if enabled',
|
||||||
callback = function(ev)
|
callback = function(ev)
|
||||||
local client = assert(lsp.get_client_by_id(ev.data.client_id))
|
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
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -286,7 +289,7 @@ local function toggle_linked_editing_for_client(enable, client)
|
|||||||
handler(bufnr, client)
|
handler(bufnr, client)
|
||||||
end
|
end
|
||||||
|
|
||||||
client._linked_editing_enabled = enable
|
client._enabled_capabilities['linked_editing_range'] = enable
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param enable boolean
|
---@param enable boolean
|
||||||
|
|||||||
@@ -41,9 +41,14 @@ local M = {}
|
|||||||
---@field debounce integer milliseconds to debounce requests for new tokens
|
---@field debounce integer milliseconds to debounce requests for new tokens
|
||||||
---@field timer table uv_timer for debouncing requests for new tokens
|
---@field timer table uv_timer for debouncing requests for new tokens
|
||||||
---@field client_state table<integer, STClientState>
|
---@field client_state table<integer, STClientState>
|
||||||
local STHighlighter = { name = 'Semantic Tokens', active = {} }
|
local STHighlighter = {
|
||||||
|
name = 'semantic_tokens',
|
||||||
|
method = ms.textDocument_semanticTokens_full,
|
||||||
|
active = {},
|
||||||
|
}
|
||||||
STHighlighter.__index = STHighlighter
|
STHighlighter.__index = STHighlighter
|
||||||
setmetatable(STHighlighter, Capability)
|
setmetatable(STHighlighter, Capability)
|
||||||
|
Capability.all[STHighlighter.name] = STHighlighter
|
||||||
|
|
||||||
--- Extracts modifier strings from the encoded number in the token array
|
--- Extracts modifier strings from the encoded number in the token array
|
||||||
---
|
---
|
||||||
@@ -156,6 +161,7 @@ end
|
|||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return STHighlighter
|
---@return STHighlighter
|
||||||
function STHighlighter:new(bufnr)
|
function STHighlighter:new(bufnr)
|
||||||
|
self.debounce = 200
|
||||||
self = Capability.new(self, bufnr)
|
self = Capability.new(self, bufnr)
|
||||||
|
|
||||||
api.nvim_buf_attach(bufnr, false, {
|
api.nvim_buf_attach(bufnr, false, {
|
||||||
@@ -164,13 +170,11 @@ function STHighlighter:new(bufnr)
|
|||||||
if not highlighter then
|
if not highlighter then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
if M.is_enabled({ bufnr = buf }) then
|
|
||||||
highlighter:on_change()
|
highlighter:on_change()
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
on_reload = function(_, buf)
|
on_reload = function(_, buf)
|
||||||
local highlighter = STHighlighter.active[buf]
|
local highlighter = STHighlighter.active[buf]
|
||||||
if highlighter and M.is_enabled({ bufnr = bufnr }) then
|
if highlighter then
|
||||||
highlighter:reset()
|
highlighter:reset()
|
||||||
highlighter:send_request()
|
highlighter:send_request()
|
||||||
end
|
end
|
||||||
@@ -181,9 +185,7 @@ function STHighlighter:new(bufnr)
|
|||||||
buffer = self.bufnr,
|
buffer = self.bufnr,
|
||||||
group = self.augroup,
|
group = self.augroup,
|
||||||
callback = function()
|
callback = function()
|
||||||
if M.is_enabled({ bufnr = bufnr }) then
|
|
||||||
self:send_request()
|
self:send_request()
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -201,6 +203,7 @@ function STHighlighter:on_attach(client_id)
|
|||||||
}
|
}
|
||||||
self.client_state[client_id] = state
|
self.client_state[client_id] = state
|
||||||
end
|
end
|
||||||
|
self:send_request()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@package
|
---@package
|
||||||
@@ -581,9 +584,6 @@ function M._start(bufnr, client_id, debounce)
|
|||||||
end
|
end
|
||||||
|
|
||||||
highlighter:on_attach(client_id)
|
highlighter:on_attach(client_id)
|
||||||
if M.is_enabled({ bufnr = bufnr }) then
|
|
||||||
highlighter:send_request()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Start the semantic token highlighting engine for the given buffer with the
|
--- Start the semantic token highlighting engine for the given buffer with the
|
||||||
@@ -670,9 +670,9 @@ function M.stop(bufnr, client_id)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Query whether semantic tokens is enabled in the {filter}ed scope
|
--- 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)
|
function M.is_enabled(filter)
|
||||||
return util._is_enabled('semantic_tokens', filter)
|
return vim.lsp._capability.is_enabled('semantic_tokens', filter)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Enables or disables semantic tokens for the {filter}ed scope.
|
--- Enables or disables semantic tokens for the {filter}ed scope.
|
||||||
@@ -684,20 +684,9 @@ end
|
|||||||
--- ```
|
--- ```
|
||||||
---
|
---
|
||||||
---@param enable? boolean true/nil to enable, false to disable
|
---@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)
|
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]
|
|
||||||
if highlighter then
|
|
||||||
if M.is_enabled({ bufnr = bufnr }) then
|
|
||||||
highlighter:send_request()
|
|
||||||
else
|
|
||||||
highlighter:reset()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @nodoc
|
--- @nodoc
|
||||||
@@ -779,7 +768,7 @@ function M.force_refresh(bufnr)
|
|||||||
|
|
||||||
for _, buffer in ipairs(buffers) do
|
for _, buffer in ipairs(buffers) do
|
||||||
local highlighter = STHighlighter.active[buffer]
|
local highlighter = STHighlighter.active[buffer]
|
||||||
if highlighter and M.is_enabled({ bufnr = bufnr }) then
|
if highlighter then
|
||||||
highlighter:reset()
|
highlighter:reset()
|
||||||
highlighter:send_request()
|
highlighter:send_request()
|
||||||
end
|
end
|
||||||
@@ -863,6 +852,6 @@ api.nvim_set_decoration_provider(namespace, {
|
|||||||
M.__STHighlighter = STHighlighter
|
M.__STHighlighter = STHighlighter
|
||||||
|
|
||||||
-- Semantic tokens is enabled by default
|
-- Semantic tokens is enabled by default
|
||||||
util._enable('semantic_tokens', true)
|
vim.lsp._capability.enable('semantic_tokens', true)
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -2331,85 +2331,6 @@ function M._cancel_requests(filter)
|
|||||||
end
|
end
|
||||||
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
|
M._get_line_byte_from_position = get_line_byte_from_position
|
||||||
|
|
||||||
---@nodoc
|
---@nodoc
|
||||||
|
|||||||
@@ -135,25 +135,25 @@ static int foldLevel(linenr_T lnum)
|
|||||||
command([[split]])
|
command([[split]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('controls the value of `b:_lsp_enable_folding_range`', function()
|
it('controls whether folding range is enabled', function()
|
||||||
eq(
|
eq(
|
||||||
true,
|
true,
|
||||||
exec_lua(function()
|
exec_lua(function()
|
||||||
return vim.b._lsp_enable_folding_range
|
return vim.lsp._capability.is_enabled('folding_range', { bufnr = 0 })
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
command [[setlocal foldexpr=]]
|
command [[setlocal foldexpr=]]
|
||||||
eq(
|
eq(
|
||||||
nil,
|
false,
|
||||||
exec_lua(function()
|
exec_lua(function()
|
||||||
return vim.b._lsp_enable_folding_range
|
return vim.lsp._capability.is_enabled('folding_range', { bufnr = 0 })
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
command([[set foldexpr=v:lua.vim.lsp.foldexpr()]])
|
command([[set foldexpr=v:lua.vim.lsp.foldexpr()]])
|
||||||
eq(
|
eq(
|
||||||
true,
|
true,
|
||||||
exec_lua(function()
|
exec_lua(function()
|
||||||
return vim.b._lsp_enable_folding_range
|
return vim.lsp._capability.is_enabled('folding_range', { bufnr = 0 })
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user