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.
This commit is contained in:
Yi Ming
2025-07-07 11:51:30 +08:00
committed by GitHub
parent 55e3a75217
commit 8d5452c46d
8 changed files with 214 additions and 165 deletions

View File

@ -4,7 +4,6 @@ local Screen = require('test.functional.ui.screen')
local t_lsp = require('test.functional.plugin.lsp.testutil')
local eq = t.eq
local tempname = t.tmpname
local clear_notrace = t_lsp.clear_notrace
local create_server_definition = t_lsp.create_server_definition
@ -121,52 +120,6 @@ static int foldLevel(linenr_T lnum)
api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
end)
describe('setup()', function()
---@type integer
local bufnr_set_expr
---@type integer
local bufnr_never_set_expr
local function buf_autocmd_num(bufnr_to_check)
return exec_lua(function()
return #vim.api.nvim_get_autocmds({ buffer = bufnr_to_check, event = 'LspNotify' })
end)
end
before_each(function()
command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]])
exec_lua(function()
bufnr_set_expr = vim.api.nvim_create_buf(true, false)
vim.api.nvim_set_current_buf(bufnr_set_expr)
end)
insert(text)
command('write ' .. tempname(false))
command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]])
exec_lua(function()
bufnr_never_set_expr = vim.api.nvim_create_buf(true, false)
vim.api.nvim_set_current_buf(bufnr_never_set_expr)
end)
insert(text)
api.nvim_win_set_buf(0, bufnr_set_expr)
end)
it('only create event hooks where foldexpr has been set', function()
eq(1, buf_autocmd_num(bufnr))
eq(1, buf_autocmd_num(bufnr_set_expr))
eq(0, buf_autocmd_num(bufnr_never_set_expr))
end)
it('does not create duplicate event hooks after reloaded', function()
command('edit')
eq(1, buf_autocmd_num(bufnr_set_expr))
end)
it('cleans up event hooks when buffer is unloaded', function()
command('bdelete')
eq(0, buf_autocmd_num(bufnr_set_expr))
end)
end)
describe('expr()', function()
--- @type test.functional.ui.screen
local screen
@ -182,6 +135,29 @@ static int foldLevel(linenr_T lnum)
command([[split]])
end)
it('controls the value of `b:_lsp_folding_range_enabled`', function()
eq(
true,
exec_lua(function()
return vim.b._lsp_folding_range_enabled
end)
)
command [[setlocal foldexpr=]]
eq(
nil,
exec_lua(function()
return vim.b._lsp_folding_range_enabled
end)
)
command([[set foldexpr=v:lua.vim.lsp.foldexpr()]])
eq(
true,
exec_lua(function()
return vim.b._lsp_folding_range_enabled
end)
)
end)
it('can compute fold levels', function()
---@type table<integer, string>
local foldlevels = {}