_capability.lua (6988B)
1 local api = vim.api 2 3 ---@alias vim.lsp.capability.Name 4 ---| 'codelens' 5 ---| 'semantic_tokens' 6 ---| 'folding_range' 7 ---| 'linked_editing_range' 8 ---| 'inline_completion' 9 10 --- Tracks all supported capabilities, all of which derive from `vim.lsp.Capability`. 11 --- Returns capability *prototypes*, not their instances. 12 ---@type table<vim.lsp.capability.Name, vim.lsp.Capability> 13 local all_capabilities = {} 14 15 -- Abstract base class (not instantiable directly). 16 -- For each buffer that has at least one supported client attached, 17 -- exactly one instance of each concrete subclass is created. 18 -- That instance is destroyed once all supporting clients detach from the buffer. 19 ---@class vim.lsp.Capability 20 --- 21 --- Static field as the identifier of the LSP capability it supports. 22 ---@field name vim.lsp.capability.Name 23 --- 24 --- Static field records the method this capability requires. 25 ---@field method vim.lsp.protocol.Method.ClientToServer | vim.lsp.protocol.Method.Registration 26 --- 27 --- Static field for retrieving the instance associated with a specific `bufnr`. 28 --- 29 --- Index in the form of `bufnr` -> `capability` 30 ---@field active table<integer, vim.lsp.Capability?> 31 --- 32 --- Buffer number it associated with. 33 ---@field bufnr integer 34 --- 35 --- The augroup owned by this instance, which will be cleared upon destruction. 36 ---@field augroup integer 37 --- 38 --- Per-client state data, scoped to the lifetime of the attached client. 39 ---@field client_state table<integer, table> 40 local M = {} 41 M.__index = M 42 43 ---@generic T : vim.lsp.Capability 44 ---@param self T 45 ---@param bufnr integer 46 ---@return T 47 function M:new(bufnr) 48 -- `self` in the `new()` function refers to the concrete type (i.e., the metatable). 49 -- `Class` may be a subtype of `Capability`, as it supports inheritance. 50 ---@type vim.lsp.Capability 51 local Class = self 52 if M == Class then 53 error('Do not instantiate the abstract class') 54 elseif all_capabilities[Class.name] and all_capabilities[Class.name] ~= Class then 55 error('Duplicated capability name') 56 else 57 all_capabilities[Class.name] = Class 58 end 59 60 ---@type vim.lsp.Capability 61 self = setmetatable({}, Class) 62 self.bufnr = bufnr 63 self.augroup = api.nvim_create_augroup(string.format('nvim.lsp.%s:%s', self.name, bufnr), { 64 clear = true, 65 }) 66 self.client_state = {} 67 68 Class.active[bufnr] = self 69 return self 70 end 71 72 function M:destroy() 73 -- In case the function is called before all the clients detached. 74 for client_id, _ in pairs(self.client_state) do 75 self:on_detach(client_id) 76 end 77 78 api.nvim_del_augroup_by_id(self.augroup) 79 self.active[self.bufnr] = nil 80 end 81 82 --- Callback invoked when an LSP client attaches. 83 --- Use it to initialize per-client state (empty table, new namespaces, etc.), 84 --- or issue requests as needed. 85 ---@param client_id integer 86 function M:on_attach(client_id) 87 self.client_state[client_id] = {} 88 end 89 90 --- Callback invoked when an LSP client detaches. 91 --- Use it to clear per-client state (cached data, extmarks, etc.). 92 ---@param client_id integer 93 function M:on_detach(client_id) 94 self.client_state[client_id] = nil 95 end 96 97 ---@param name vim.lsp.capability.Name 98 local function make_enable_var(name) 99 return ('_lsp_enabled_%s'):format(name) 100 end 101 102 --- Optional filters |kwargs|, 103 ---@class vim.lsp.capability.enable.Filter 104 ---@inlinedoc 105 --- 106 --- Buffer number, or 0 for current buffer, or nil for all. 107 --- (default: all) 108 ---@field bufnr? integer 109 --- 110 --- Client ID, or nil for all. 111 --- (default: all) 112 ---@field client_id? integer 113 114 ---@param name vim.lsp.capability.Name 115 ---@param enable? boolean 116 ---@param filter? vim.lsp.capability.enable.Filter 117 function M.enable(name, enable, filter) 118 vim.validate('name', name, 'string') 119 vim.validate('enable', enable, 'boolean', true) 120 vim.validate('filter', filter, 'table', true) 121 122 enable = enable == nil or enable 123 filter = filter or {} 124 local bufnr = filter.bufnr and vim._resolve_bufnr(filter.bufnr) 125 local client_id = filter.client_id 126 assert(not (bufnr and client_id), '`bufnr` and `client_id` are mutually exclusive.') 127 128 local var = make_enable_var(name) 129 local client = client_id and vim.lsp.get_client_by_id(client_id) 130 131 -- Attach or detach the client and its capability 132 -- based on the user’s latest marker value. 133 for _, it_client in ipairs(client and { client } or vim.lsp.get_clients()) do 134 for _, it_bufnr in 135 ipairs( 136 bufnr and { it_client.attached_buffers[bufnr] and bufnr } 137 or vim.tbl_keys(it_client.attached_buffers) 138 or {} 139 ) 140 do 141 if enable ~= M.is_enabled(name, { bufnr = it_bufnr, client_id = it_client.id }) then 142 local Capability = all_capabilities[name] 143 144 if enable then 145 if it_client:supports_method(Capability.method) then 146 local capability = Capability.active[bufnr] or Capability:new(it_bufnr) 147 if not capability.client_state[it_client.id] then 148 capability:on_attach(it_client.id) 149 end 150 end 151 else 152 local capability = Capability.active[it_bufnr] 153 if capability then 154 capability:on_detach(it_client.id) 155 if not next(capability.client_state) then 156 capability:destroy() 157 end 158 end 159 end 160 end 161 end 162 end 163 164 -- Updates the marker value. 165 -- If local marker matches the global marker, set it to nil 166 -- so that `is_enable` falls back to the global marker. 167 if client then 168 if enable == vim.g[var] then 169 client._enabled_capabilities[name] = nil 170 else 171 client._enabled_capabilities[name] = enable 172 end 173 elseif bufnr then 174 if enable == vim.g[var] then 175 vim.b[bufnr][var] = nil 176 else 177 vim.b[bufnr][var] = enable 178 end 179 else 180 vim.g[var] = enable 181 for _, it_bufnr in ipairs(api.nvim_list_bufs()) do 182 if api.nvim_buf_is_loaded(it_bufnr) and vim.b[it_bufnr][var] == enable then 183 vim.b[it_bufnr][var] = nil 184 end 185 end 186 for _, it_client in ipairs(vim.lsp.get_clients()) do 187 if it_client._enabled_capabilities[name] == enable then 188 it_client._enabled_capabilities[name] = nil 189 end 190 end 191 end 192 end 193 194 ---@param name vim.lsp.capability.Name 195 ---@param filter? vim.lsp.capability.enable.Filter 196 function M.is_enabled(name, filter) 197 vim.validate('name', name, 'string') 198 vim.validate('filter', filter, 'table', true) 199 200 filter = filter or {} 201 local bufnr = filter.bufnr and vim._resolve_bufnr(filter.bufnr) 202 local client_id = filter.client_id 203 204 local var = make_enable_var(name) 205 local client = client_id and vim.lsp.get_client_by_id(client_id) 206 207 -- As a fallback when not explicitly enabled or disabled: 208 -- Clients are treated as "enabled" since their capabilities can control behavior. 209 -- Buffers are treated as "disabled" to allow users to enable them as needed. 210 return vim.F.if_nil(client and client._enabled_capabilities[name], vim.g[var], true) 211 and vim.F.if_nil(bufnr and vim.b[bufnr][var], vim.g[var], false) 212 end 213 214 M.all = all_capabilities 215 216 return M