neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

_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