Files
neovim/runtime/lua/vim/lsp/_capability.lua
Yi Ming 8d5452c46d refactor(lsp): stateful data abstraction, vim.lsp.Capability #34639
Problem:
Closes #31453

Solution:
Introduce `vim.lsp.Capability`, which may serve as the base class for
all LSP features that require caching data. it
- was created if there is at least one client that supports the specific method;
- was destroyed if all clients that support the method were detached.

- Apply the refactor for `folding_range.lua` and `semantic_tokens.lua`.
- Show active features in :checkhealth.

Future:
I found that these features that are expected to be refactored by
`vim.lsp.Capability` have one characteristic in common: they all send
LSP requests once the document is modified. The following code is
different, but they are all for this purpose.

- semantic tokens:
fb8dba413f/runtime/lua/vim/lsp/semantic_tokens.lua (L192-L198)
- inlay hints, folding ranges, document color
fb8dba413f/runtime/lua/vim/lsp/inlay_hint.lua (L250-L266)

I think I can sum up this characteristic as the need to keep certain
data synchronized with the latest version computed by the server.
I believe we can handle this at the `vim.lsp.Capability` level, and
I think it will be very useful.

Therefore, my next step is to implement LSP request sending and data
synchronization on `vim.lsp.Capability`, rather than limiting it to the
current create/destroy data approach.
2025-07-07 03:51:30 +00:00

78 lines
2.1 KiB
Lua

local api = vim.api
--- `vim.lsp.Capability` is expected to be created one-to-one with a buffer
--- when there is at least one supported client attached to that buffer,
--- and will be destroyed when all supporting clients are detached.
---@class vim.lsp.Capability
---
--- Static field for retrieving the instance associated with a specific `bufnr`.
---
--- Index inthe form of `bufnr` -> `capability`
---@field active table<integer, vim.lsp.Capability?>
---
--- The LSP feature it supports.
---@field name string
---
--- Buffer number it associated with.
---@field bufnr integer
---
--- The augroup owned by this instance, which will be cleared upon destruction.
---@field augroup integer
---
--- Per-client state data, scoped to the lifetime of the attached client.
---@field client_state table<integer, table>
local M = {}
M.__index = M
---@generic T : vim.lsp.Capability
---@param self T
---@param bufnr integer
---@return T
function M:new(bufnr)
-- `self` in the `new()` function refers to the concrete type (i.e., the metatable).
-- `Class` may be a subtype of `Capability`, as it supports inheritance.
---@type vim.lsp.Capability
local Class = self
assert(Class.name and Class.active, 'Do not instantiate the abstract class')
---@type vim.lsp.Capability
self = setmetatable({}, Class)
self.bufnr = bufnr
self.augroup = api.nvim_create_augroup(
string.format('nvim.lsp.%s:%s', self.name:gsub('%s+', '_'):lower(), bufnr),
{ 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,
})
Class.active[bufnr] = self
return self
end
function M:destroy()
-- In case the function is called before all the clients detached.
for client_id, _ in pairs(self.client_state) do
self:on_detach(client_id)
end
api.nvim_del_augroup_by_id(self.augroup)
self.active[self.bufnr] = nil
end
---@param client_id integer
function M:on_detach(client_id)
self.client_state[client_id] = nil
end
return M