mirror of
https://github.com/neovim/neovim.git
synced 2025-11-17 15:51:32 +00:00
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 colorfb8dba413f/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:
@@ -195,6 +195,7 @@ LSP
|
|||||||
• The function form of `cmd` in a vim.lsp.Config or vim.lsp.ClientConfig
|
• The function form of `cmd` in a vim.lsp.Config or vim.lsp.ClientConfig
|
||||||
receives the resolved config as the second arg: `cmd(dispatchers, config)`.
|
receives the resolved config as the second arg: `cmd(dispatchers, config)`.
|
||||||
• Support for annotated text edits.
|
• Support for annotated text edits.
|
||||||
|
• `:checkhealth vim.lsp` is now available to check which buffers the active LSP features are attached to.
|
||||||
|
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ local api = vim.api
|
|||||||
local validate = vim.validate
|
local validate = vim.validate
|
||||||
|
|
||||||
local lsp = vim._defer_require('vim.lsp', {
|
local lsp = vim._defer_require('vim.lsp', {
|
||||||
|
_capability = ..., --- @module 'vim.lsp._capability'
|
||||||
_changetracking = ..., --- @module 'vim.lsp._changetracking'
|
_changetracking = ..., --- @module 'vim.lsp._changetracking'
|
||||||
_folding_range = ..., --- @module 'vim.lsp._folding_range'
|
_folding_range = ..., --- @module 'vim.lsp._folding_range'
|
||||||
_snippet_grammar = ..., --- @module 'vim.lsp._snippet_grammar'
|
_snippet_grammar = ..., --- @module 'vim.lsp._snippet_grammar'
|
||||||
|
|||||||
77
runtime/lua/vim/lsp/_capability.lua
Normal file
77
runtime/lua/vim/lsp/_capability.lua
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
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
|
||||||
@@ -12,18 +12,20 @@ local supported_fold_kinds = {
|
|||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@class (private) vim.lsp.folding_range.State
|
local Capability = require('vim.lsp._capability')
|
||||||
|
|
||||||
|
---@class (private) vim.lsp.folding_range.State : vim.lsp.Capability
|
||||||
---
|
---
|
||||||
---@field active table<integer, vim.lsp.folding_range.State?>
|
---@field active table<integer, vim.lsp.folding_range.State?>
|
||||||
---@field bufnr integer
|
---
|
||||||
---@field augroup integer
|
--- `TextDocument` version this `state` corresponds to.
|
||||||
---@field version? integer
|
---@field version? integer
|
||||||
---
|
---
|
||||||
--- Never use this directly, `renew()` the cached foldinfo
|
--- Never use this directly, `evaluate()` the cached foldinfo
|
||||||
--- then use on demand via `row_*` fields.
|
--- then use on demand via `row_*` fields.
|
||||||
---
|
---
|
||||||
--- Index In the form of client_id -> ranges
|
--- Index In the form of client_id -> ranges
|
||||||
---@field client_ranges table<integer, lsp.FoldingRange[]?>
|
---@field client_state table<integer, lsp.FoldingRange[]?>
|
||||||
---
|
---
|
||||||
--- Index in the form of row -> [foldlevel, mark]
|
--- Index in the form of row -> [foldlevel, mark]
|
||||||
---@field row_level table<integer, [integer, ">" | "<"?]?>
|
---@field row_level table<integer, [integer, ">" | "<"?]?>
|
||||||
@@ -33,10 +35,12 @@ local M = {}
|
|||||||
---
|
---
|
||||||
--- 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 = { active = {} }
|
local State = { name = 'Folding Range', active = {} }
|
||||||
|
State.__index = State
|
||||||
|
setmetatable(State, Capability)
|
||||||
|
|
||||||
--- Renew the cached foldinfo in the buffer.
|
--- Re-evaluate the cached foldinfo in the buffer.
|
||||||
function State:renew()
|
function State:evaluate()
|
||||||
---@type table<integer, [integer, ">" | "<"?]?>
|
---@type table<integer, [integer, ">" | "<"?]?>
|
||||||
local row_level = {}
|
local row_level = {}
|
||||||
---@type table<integer, table<lsp.FoldingRangeKind, true?>?>>
|
---@type table<integer, table<lsp.FoldingRangeKind, true?>?>>
|
||||||
@@ -44,7 +48,7 @@ function State:renew()
|
|||||||
---@type table<integer, string?>
|
---@type table<integer, string?>
|
||||||
local row_text = {}
|
local row_text = {}
|
||||||
|
|
||||||
for client_id, ranges in pairs(self.client_ranges) do
|
for client_id, ranges in pairs(self.client_state) do
|
||||||
for _, range in ipairs(ranges) do
|
for _, range in ipairs(ranges) do
|
||||||
local start_row = range.startLine
|
local start_row = range.startLine
|
||||||
local end_row = range.endLine
|
local end_row = range.endLine
|
||||||
@@ -83,6 +87,9 @@ 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_folding_range_enabled 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
|
||||||
@@ -127,12 +134,12 @@ function State:multi_handler(results, ctx)
|
|||||||
if result.err then
|
if result.err then
|
||||||
log.error(result.err)
|
log.error(result.err)
|
||||||
else
|
else
|
||||||
self.client_ranges[client_id] = result.result
|
self.client_state[client_id] = result.result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.version = ctx.version
|
self.version = ctx.version
|
||||||
|
|
||||||
self:renew()
|
self:evaluate()
|
||||||
if api.nvim_get_mode().mode:match('^i') then
|
if api.nvim_get_mode().mode:match('^i') then
|
||||||
-- `foldUpdate()` is guarded in insert mode.
|
-- `foldUpdate()` is guarded in insert mode.
|
||||||
schedule_foldupdate(self.bufnr)
|
schedule_foldupdate(self.bufnr)
|
||||||
@@ -151,7 +158,11 @@ end
|
|||||||
--- Request `textDocument/foldingRange` from the server.
|
--- Request `textDocument/foldingRange` from the server.
|
||||||
--- `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:request(client)
|
function State:refresh(client)
|
||||||
|
if not vim.b._lsp_folding_range_enabled 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) }
|
||||||
|
|
||||||
@@ -174,7 +185,6 @@ function State:request(client)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function State:reset()
|
function State:reset()
|
||||||
self.client_ranges = {}
|
|
||||||
self.row_level = {}
|
self.row_level = {}
|
||||||
self.row_kinds = {}
|
self.row_kinds = {}
|
||||||
self.row_text = {}
|
self.row_text = {}
|
||||||
@@ -183,34 +193,17 @@ end
|
|||||||
--- Initialize `state` and event hooks, then request folding ranges.
|
--- Initialize `state` and event hooks, then request folding ranges.
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return vim.lsp.folding_range.State
|
---@return vim.lsp.folding_range.State
|
||||||
function State.new(bufnr)
|
function State:new(bufnr)
|
||||||
local self = setmetatable({}, { __index = State })
|
self = Capability.new(self, bufnr)
|
||||||
self.bufnr = bufnr
|
|
||||||
self.augroup = api.nvim_create_augroup('nvim.lsp.folding_range:' .. bufnr, { clear = true })
|
|
||||||
self:reset()
|
self:reset()
|
||||||
|
|
||||||
State.active[bufnr] = self
|
|
||||||
|
|
||||||
api.nvim_buf_attach(bufnr, false, {
|
api.nvim_buf_attach(bufnr, false, {
|
||||||
-- `on_detach` also runs on buffer reload (`:e`).
|
|
||||||
-- Ensure `state` and hooks are cleared to avoid duplication or leftover states.
|
|
||||||
on_detach = function()
|
|
||||||
util._cancel_requests({
|
|
||||||
bufnr = bufnr,
|
|
||||||
method = ms.textDocument_foldingRange,
|
|
||||||
type = 'pending',
|
|
||||||
})
|
|
||||||
local state = State.active[bufnr]
|
|
||||||
if state then
|
|
||||||
state:destroy()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
-- Reset `bufstate` and request folding ranges.
|
-- Reset `bufstate` and request folding ranges.
|
||||||
on_reload = function()
|
on_reload = function()
|
||||||
local state = State.active[bufnr]
|
local state = State.active[bufnr]
|
||||||
if state then
|
if state then
|
||||||
state:reset()
|
state:reset()
|
||||||
state:request()
|
state:refresh()
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
--- Sync changed rows with their previous foldlevels before applying new ones.
|
--- Sync changed rows with their previous foldlevels before applying new ones.
|
||||||
@@ -238,44 +231,6 @@ function State.new(bufnr)
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
api.nvim_create_autocmd('LspDetach', {
|
|
||||||
group = self.augroup,
|
|
||||||
buffer = bufnr,
|
|
||||||
callback = function(args)
|
|
||||||
if not api.nvim_buf_is_loaded(bufnr) then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type integer
|
|
||||||
local client_id = args.data.client_id
|
|
||||||
self.client_ranges[client_id] = nil
|
|
||||||
|
|
||||||
---@type vim.lsp.Client[]
|
|
||||||
local clients = vim
|
|
||||||
.iter(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange }))
|
|
||||||
---@param client vim.lsp.Client
|
|
||||||
:filter(function(client)
|
|
||||||
return client.id ~= client_id
|
|
||||||
end)
|
|
||||||
:totable()
|
|
||||||
if #clients == 0 then
|
|
||||||
self:reset()
|
|
||||||
end
|
|
||||||
|
|
||||||
self:renew()
|
|
||||||
foldupdate(bufnr)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
api.nvim_create_autocmd('LspAttach', {
|
|
||||||
group = self.augroup,
|
|
||||||
buffer = bufnr,
|
|
||||||
callback = function(args)
|
|
||||||
local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
|
|
||||||
if client:supports_method(vim.lsp.protocol.Methods.textDocument_foldingRange, bufnr) then
|
|
||||||
self:request(client)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
api.nvim_create_autocmd('LspNotify', {
|
api.nvim_create_autocmd('LspNotify', {
|
||||||
group = self.augroup,
|
group = self.augroup,
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
@@ -288,7 +243,16 @@ function State.new(bufnr)
|
|||||||
or args.data.method == ms.textDocument_didOpen
|
or args.data.method == ms.textDocument_didOpen
|
||||||
)
|
)
|
||||||
then
|
then
|
||||||
self:request(client)
|
self:refresh(client)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
api.nvim_create_autocmd('OptionSet', {
|
||||||
|
group = self.augroup,
|
||||||
|
pattern = 'foldexpr',
|
||||||
|
callback = function()
|
||||||
|
if vim.v.option_type == 'global' or vim.api.nvim_get_current_buf() == bufnr then
|
||||||
|
vim.b[bufnr]._lsp_folding_range_enabled = nil
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
@@ -301,18 +265,22 @@ function State:destroy()
|
|||||||
State.active[self.bufnr] = nil
|
State.active[self.bufnr] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function setup(bufnr)
|
---@params client_id integer
|
||||||
if not api.nvim_buf_is_loaded(bufnr) then
|
function State:on_detach(client_id)
|
||||||
return
|
self.client_state[client_id] = nil
|
||||||
end
|
self:evaluate()
|
||||||
|
foldupdate(self.bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
---@param client_id? integer
|
||||||
|
function M._setup(bufnr, client_id)
|
||||||
local state = State.active[bufnr]
|
local state = State.active[bufnr]
|
||||||
if not state then
|
if not state then
|
||||||
state = State.new(bufnr)
|
state = State:new(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
state:request()
|
state:refresh(client_id and vim.lsp.get_client_by_id(client_id))
|
||||||
return state
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param kind lsp.FoldingRangeKind
|
---@param kind lsp.FoldingRangeKind
|
||||||
@@ -344,11 +312,11 @@ function M.foldclose(kind, winid)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Schedule `foldclose()` if the buffer is not up-to-date.
|
||||||
if state.version == util.buf_versions[bufnr] then
|
if state.version == util.buf_versions[bufnr] then
|
||||||
state:foldclose(kind, winid)
|
state:foldclose(kind, winid)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- Schedule `foldclose()` if the buffer is not up-to-date.
|
|
||||||
|
|
||||||
if not next(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange })) then
|
if not next(vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_foldingRange })) then
|
||||||
return
|
return
|
||||||
@@ -380,14 +348,22 @@ 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] or setup(bufnr)
|
local state = State.active[bufnr]
|
||||||
|
if not vim.b[bufnr]._lsp_folding_range_enabled then
|
||||||
|
vim.b[bufnr]._lsp_folding_range_enabled = true
|
||||||
|
if state then
|
||||||
|
state:refresh()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if not state then
|
if not state then
|
||||||
return '0'
|
return '0'
|
||||||
end
|
end
|
||||||
|
|
||||||
local row = (lnum or vim.v.lnum) - 1
|
local row = (lnum or vim.v.lnum) - 1
|
||||||
local level = state.row_level[row]
|
local level = state.row_level[row]
|
||||||
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
|
||||||
|
|||||||
@@ -1082,6 +1082,9 @@ function Client:on_attach(bufnr)
|
|||||||
if vim.tbl_get(self.server_capabilities, 'semanticTokensProvider', 'full') then
|
if vim.tbl_get(self.server_capabilities, 'semanticTokensProvider', 'full') then
|
||||||
lsp.semantic_tokens.start(bufnr, self.id)
|
lsp.semantic_tokens.start(bufnr, self.id)
|
||||||
end
|
end
|
||||||
|
if vim.tbl_get(self.server_capabilities, 'foldingRangeProvider') then
|
||||||
|
lsp._folding_range._setup(bufnr)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
self.attached_buffers[bufnr] = true
|
self.attached_buffers[bufnr] = true
|
||||||
|
|||||||
@@ -28,6 +28,43 @@ local function check_log()
|
|||||||
report_fn(string.format('Log size: %d KB', log_size / 1000))
|
report_fn(string.format('Log size: %d KB', log_size / 1000))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function check_active_features()
|
||||||
|
vim.health.start('vim.lsp: Active Features')
|
||||||
|
---@type vim.lsp.Capability[]
|
||||||
|
local features = {
|
||||||
|
require('vim.lsp.semantic_tokens').__STHighlighter,
|
||||||
|
require('vim.lsp._folding_range').__FoldEvaluator,
|
||||||
|
}
|
||||||
|
for _, feature in ipairs(features) do
|
||||||
|
---@type string[]
|
||||||
|
local buf_infos = {}
|
||||||
|
for bufnr, instance in pairs(feature.active) do
|
||||||
|
local client_info = vim
|
||||||
|
.iter(pairs(instance.client_state))
|
||||||
|
:map(function(client_id)
|
||||||
|
local client = vim.lsp.get_client_by_id(client_id)
|
||||||
|
if client then
|
||||||
|
return string.format('%s (id: %d)', client.name, client.id)
|
||||||
|
else
|
||||||
|
return string.format('unknow (id: %d)', client_id)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:join(', ')
|
||||||
|
if client_info == '' then
|
||||||
|
client_info = 'No supported client attached'
|
||||||
|
end
|
||||||
|
|
||||||
|
buf_infos[#buf_infos + 1] = string.format(' [%d]: %s', bufnr, client_info)
|
||||||
|
end
|
||||||
|
|
||||||
|
report_info(table.concat({
|
||||||
|
feature.name,
|
||||||
|
'- Active buffers:',
|
||||||
|
string.format(table.concat(buf_infos, '\n')),
|
||||||
|
}, '\n'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- @param f function
|
--- @param f function
|
||||||
--- @return string
|
--- @return string
|
||||||
local function func_tostring(f)
|
local function func_tostring(f)
|
||||||
@@ -223,6 +260,7 @@ end
|
|||||||
--- Performs a healthcheck for LSP
|
--- Performs a healthcheck for LSP
|
||||||
function M.check()
|
function M.check()
|
||||||
check_log()
|
check_log()
|
||||||
|
check_active_features()
|
||||||
check_active_clients()
|
check_active_clients()
|
||||||
check_enabled_configs()
|
check_enabled_configs()
|
||||||
check_watcher()
|
check_watcher()
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ local util = require('vim.lsp.util')
|
|||||||
local Range = require('vim.treesitter._range')
|
local Range = require('vim.treesitter._range')
|
||||||
local uv = vim.uv
|
local uv = vim.uv
|
||||||
|
|
||||||
|
local Capability = require('vim.lsp._capability')
|
||||||
|
|
||||||
--- @class (private) STTokenRange
|
--- @class (private) STTokenRange
|
||||||
--- @field line integer line number 0-based
|
--- @field line integer line number 0-based
|
||||||
--- @field start_col integer start column 0-based
|
--- @field start_col integer start column 0-based
|
||||||
@@ -30,14 +32,16 @@ local uv = vim.uv
|
|||||||
--- @field active_request STActiveRequest
|
--- @field active_request STActiveRequest
|
||||||
--- @field current_result STCurrentResult
|
--- @field current_result STCurrentResult
|
||||||
|
|
||||||
---@class (private) STHighlighter
|
---@class (private) STHighlighter : vim.lsp.Capability
|
||||||
---@field active table<integer, STHighlighter>
|
---@field active table<integer, STHighlighter>
|
||||||
---@field bufnr integer
|
---@field bufnr integer
|
||||||
---@field augroup integer augroup for buffer events
|
---@field augroup integer augroup for buffer events
|
||||||
---@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 = { active = {} }
|
local STHighlighter = { name = 'Semantic Tokens', active = {} }
|
||||||
|
STHighlighter.__index = STHighlighter
|
||||||
|
setmetatable(STHighlighter, Capability)
|
||||||
|
|
||||||
--- Do a binary search of the tokens in the half-open range [lo, hi).
|
--- Do a binary search of the tokens in the half-open range [lo, hi).
|
||||||
---
|
---
|
||||||
@@ -179,14 +183,8 @@ end
|
|||||||
---@private
|
---@private
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return STHighlighter
|
---@return STHighlighter
|
||||||
function STHighlighter.new(bufnr)
|
function STHighlighter:new(bufnr)
|
||||||
local self = setmetatable({}, { __index = STHighlighter })
|
self = Capability.new(self, bufnr)
|
||||||
|
|
||||||
self.bufnr = bufnr
|
|
||||||
self.augroup = api.nvim_create_augroup('nvim.lsp.semantic_tokens:' .. bufnr, { clear = true })
|
|
||||||
self.client_state = {}
|
|
||||||
|
|
||||||
STHighlighter.active[bufnr] = self
|
|
||||||
|
|
||||||
api.nvim_buf_attach(bufnr, false, {
|
api.nvim_buf_attach(bufnr, false, {
|
||||||
on_lines = function(_, buf)
|
on_lines = function(_, buf)
|
||||||
@@ -213,32 +211,11 @@ function STHighlighter.new(bufnr)
|
|||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
api.nvim_create_autocmd('LspDetach', {
|
|
||||||
buffer = self.bufnr,
|
|
||||||
group = self.augroup,
|
|
||||||
callback = function(args)
|
|
||||||
self:detach(args.data.client_id)
|
|
||||||
if vim.tbl_isempty(self.client_state) then
|
|
||||||
self:destroy()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
---@package
|
---@package
|
||||||
function STHighlighter:destroy()
|
function STHighlighter:on_attach(client_id)
|
||||||
for client_id, _ in pairs(self.client_state) do
|
|
||||||
self:detach(client_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
api.nvim_del_augroup_by_id(self.augroup)
|
|
||||||
STHighlighter.active[self.bufnr] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
---@package
|
|
||||||
function STHighlighter:attach(client_id)
|
|
||||||
local state = self.client_state[client_id]
|
local state = self.client_state[client_id]
|
||||||
if not state then
|
if not state then
|
||||||
state = {
|
state = {
|
||||||
@@ -251,7 +228,7 @@ function STHighlighter:attach(client_id)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@package
|
---@package
|
||||||
function STHighlighter:detach(client_id)
|
function STHighlighter:on_detach(client_id)
|
||||||
local state = self.client_state[client_id]
|
local state = self.client_state[client_id]
|
||||||
if state then
|
if state then
|
||||||
--TODO: delete namespace if/when that becomes possible
|
--TODO: delete namespace if/when that becomes possible
|
||||||
@@ -657,13 +634,13 @@ function M.start(bufnr, client_id, opts)
|
|||||||
local highlighter = STHighlighter.active[bufnr]
|
local highlighter = STHighlighter.active[bufnr]
|
||||||
|
|
||||||
if not highlighter then
|
if not highlighter then
|
||||||
highlighter = STHighlighter.new(bufnr)
|
highlighter = STHighlighter:new(bufnr)
|
||||||
highlighter.debounce = opts.debounce or 200
|
highlighter.debounce = opts.debounce or 200
|
||||||
else
|
else
|
||||||
highlighter.debounce = math.max(highlighter.debounce, opts.debounce or 200)
|
highlighter.debounce = math.max(highlighter.debounce, opts.debounce or 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
highlighter:attach(client_id)
|
highlighter:on_attach(client_id)
|
||||||
highlighter:send_request()
|
highlighter:send_request()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -687,7 +664,7 @@ function M.stop(bufnr, client_id)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
highlighter:detach(client_id)
|
highlighter:on_detach(client_id)
|
||||||
|
|
||||||
if vim.tbl_isempty(highlighter.client_state) then
|
if vim.tbl_isempty(highlighter.client_state) then
|
||||||
highlighter:destroy()
|
highlighter:destroy()
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ local Screen = require('test.functional.ui.screen')
|
|||||||
local t_lsp = require('test.functional.plugin.lsp.testutil')
|
local t_lsp = require('test.functional.plugin.lsp.testutil')
|
||||||
|
|
||||||
local eq = t.eq
|
local eq = t.eq
|
||||||
local tempname = t.tmpname
|
|
||||||
|
|
||||||
local clear_notrace = t_lsp.clear_notrace
|
local clear_notrace = t_lsp.clear_notrace
|
||||||
local create_server_definition = t_lsp.create_server_definition
|
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 })
|
api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
|
||||||
end)
|
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()
|
describe('expr()', function()
|
||||||
--- @type test.functional.ui.screen
|
--- @type test.functional.ui.screen
|
||||||
local screen
|
local screen
|
||||||
@@ -182,6 +135,29 @@ static int foldLevel(linenr_T lnum)
|
|||||||
command([[split]])
|
command([[split]])
|
||||||
end)
|
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()
|
it('can compute fold levels', function()
|
||||||
---@type table<integer, string>
|
---@type table<integer, string>
|
||||||
local foldlevels = {}
|
local foldlevels = {}
|
||||||
|
|||||||
Reference in New Issue
Block a user