aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/lsp.lua')
-rw-r--r--runtime/lua/vim/lsp.lua250
1 files changed, 244 insertions, 6 deletions
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index ebdc050405..596e1b609b 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -316,6 +316,240 @@ local function create_and_initialize_client(config)
return client.id, nil
end
+--- @class vim.lsp.Config : vim.lsp.ClientConfig
+---
+--- See `cmd` in [vim.lsp.ClientConfig].
+--- @field cmd? string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
+---
+--- Filetypes the client will attach to, if activated by `vim.lsp.enable()`.
+--- If not provided, then the client will attach to all filetypes.
+--- @field filetypes? string[]
+---
+--- Directory markers (.e.g. '.git/') where the LSP server will base its workspaceFolders,
+--- rootUri, and rootPath on initialization. Unused if `root_dir` is provided.
+--- @field root_markers? string[]
+---
+--- Predicate used to decide if a client should be re-used. Used on all
+--- running clients. The default implementation re-uses a client if name and
+--- root_dir matches.
+--- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean
+
+--- Update the configuration for an LSP client.
+---
+--- Use name '*' to set default configuration for all clients.
+---
+--- Can also be table-assigned to redefine the configuration for a client.
+---
+--- Examples:
+---
+--- - Add a root marker for all clients:
+--- ```lua
+--- vim.lsp.config('*', {
+--- root_markers = { '.git' },
+--- })
+--- ```
+--- - Add additional capabilities to all clients:
+--- ```lua
+--- vim.lsp.config('*', {
+--- capabilities = {
+--- textDocument = {
+--- semanticTokens = {
+--- multilineTokenSupport = true,
+--- }
+--- }
+--- }
+--- })
+--- ```
+--- - (Re-)define the configuration for clangd:
+--- ```lua
+--- vim.lsp.config.clangd = {
+--- cmd = {
+--- 'clangd',
+--- '--clang-tidy',
+--- '--background-index',
+--- '--offset-encoding=utf-8',
+--- },
+--- root_markers = { '.clangd', 'compile_commands.json' },
+--- filetypes = { 'c', 'cpp' },
+--- }
+--- ```
+--- - Get configuration for luals:
+--- ```lua
+--- local cfg = vim.lsp.config.luals
+--- ```
+---
+--- @param name string
+--- @param cfg vim.lsp.Config
+--- @diagnostic disable-next-line:assign-type-mismatch
+function lsp.config(name, cfg)
+ local _, _ = name, cfg -- ignore unused
+ -- dummy proto for docs
+end
+
+lsp._enabled_configs = {} --- @type table<string,{resolved_config:vim.lsp.Config?}>
+
+--- If a config in vim.lsp.config() is accessed then the resolved config becomes invalid.
+--- @param name string
+local function invalidate_enabled_config(name)
+ if name == '*' then
+ for _, v in pairs(lsp._enabled_configs) do
+ v.resolved_config = nil
+ end
+ elseif lsp._enabled_configs[name] then
+ lsp._enabled_configs[name].resolved_config = nil
+ end
+end
+
+--- @nodoc
+--- @class vim.lsp.config
+--- @field [string] vim.lsp.Config
+--- @field package _configs table<string,vim.lsp.Config>
+lsp.config = setmetatable({ _configs = {} }, {
+ --- @param self vim.lsp.config
+ --- @param name string
+ --- @return vim.lsp.Config
+ __index = function(self, name)
+ validate('name', name, 'string')
+ invalidate_enabled_config(name)
+ self._configs[name] = self._configs[name] or {}
+ return self._configs[name]
+ end,
+
+ --- @param self vim.lsp.config
+ --- @param name string
+ --- @param cfg vim.lsp.Config
+ __newindex = function(self, name, cfg)
+ validate('name', name, 'string')
+ validate('cfg', cfg, 'table')
+ invalidate_enabled_config(name)
+ self._configs[name] = cfg
+ end,
+
+ --- @param self vim.lsp.config
+ --- @param name string
+ --- @param cfg vim.lsp.Config
+ __call = function(self, name, cfg)
+ validate('name', name, 'string')
+ validate('cfg', cfg, 'table')
+ invalidate_enabled_config(name)
+ self[name] = vim.tbl_deep_extend('force', self._configs[name] or {}, cfg)
+ end,
+})
+
+--- @private
+--- @param name string
+--- @return vim.lsp.Config
+function lsp._resolve_config(name)
+ local econfig = lsp._enabled_configs[name] or {}
+
+ if not econfig.resolved_config then
+ -- Resolve configs from lsp/*.lua
+ -- Calls to vim.lsp.config in lsp/* have a lower precedence than calls from other sites.
+ local orig_configs = lsp.config._configs
+ lsp.config._configs = {}
+ pcall(vim.cmd.runtime, { ('lsp/%s.lua'):format(name), bang = true })
+ local rtp_configs = lsp.config._configs
+ lsp.config._configs = orig_configs
+
+ local config = vim.tbl_deep_extend(
+ 'force',
+ lsp.config._configs['*'] or {},
+ rtp_configs[name] or {},
+ lsp.config._configs[name] or {}
+ )
+
+ config.name = name
+
+ validate('cmd', config.cmd, { 'function', 'table' })
+ validate('cmd', config.reuse_client, 'function', true)
+ -- All other fields are validated in client.create
+
+ econfig.resolved_config = config
+ end
+
+ return assert(econfig.resolved_config)
+end
+
+local lsp_enable_autocmd_id --- @type integer?
+
+--- @param bufnr integer
+local function lsp_enable_callback(bufnr)
+ -- Only ever attach to buffers that represent an actual file.
+ if vim.bo[bufnr].buftype ~= '' then
+ return
+ end
+
+ --- @param config vim.lsp.Config
+ local function can_start(config)
+ if config.filetypes and not vim.tbl_contains(config.filetypes, vim.bo[bufnr].filetype) then
+ return false
+ elseif type(config.cmd) == 'table' and vim.fn.executable(config.cmd[1]) == 0 then
+ return false
+ end
+
+ return true
+ end
+
+ for name in vim.spairs(lsp._enabled_configs) do
+ local config = lsp._resolve_config(name)
+
+ if can_start(config) then
+ -- Deepcopy config so changes done in the client
+ -- do not propagate back to the enabled configs.
+ config = vim.deepcopy(config)
+
+ vim.lsp.start(config, {
+ bufnr = bufnr,
+ reuse_client = config.reuse_client,
+ _root_markers = config.root_markers,
+ })
+ end
+ end
+end
+
+--- Enable an LSP server to automatically start when opening a buffer.
+---
+--- Uses configuration defined with `vim.lsp.config`.
+---
+--- Examples:
+---
+--- ```lua
+--- vim.lsp.enable('clangd')
+---
+--- vim.lsp.enable({'luals', 'pyright'})
+--- ```
+---
+--- @param name string|string[] Name(s) of client(s) to enable.
+--- @param enable? boolean `true|nil` to enable, `false` to disable.
+function lsp.enable(name, enable)
+ validate('name', name, { 'string', 'table' })
+
+ local names = vim._ensure_list(name) --[[@as string[] ]]
+ for _, nm in ipairs(names) do
+ if nm == '*' then
+ error('Invalid name')
+ end
+ lsp._enabled_configs[nm] = enable == false and nil or {}
+ end
+
+ if not next(lsp._enabled_configs) then
+ if lsp_enable_autocmd_id then
+ api.nvim_del_autocmd(lsp_enable_autocmd_id)
+ lsp_enable_autocmd_id = nil
+ end
+ return
+ end
+
+ -- Only ever create autocmd once to reuse computation of config merging.
+ lsp_enable_autocmd_id = lsp_enable_autocmd_id
+ or api.nvim_create_autocmd('FileType', {
+ group = api.nvim_create_augroup('nvim.lsp.enable', {}),
+ callback = function(args)
+ lsp_enable_callback(args.buf)
+ end,
+ })
+end
+
--- @class vim.lsp.start.Opts
--- @inlinedoc
---
@@ -334,6 +568,8 @@ end
---
--- Suppress error reporting if the LSP server fails to start (default false).
--- @field silent? boolean
+---
+--- @field package _root_markers? string[]
--- Create a new LSP client and start a language server or reuses an already
--- running client if one is found matching `name` and `root_dir`.
@@ -379,6 +615,11 @@ function lsp.start(config, opts)
local reuse_client = opts.reuse_client or reuse_client_default
local bufnr = vim._resolve_bufnr(opts.bufnr)
+ if not config.root_dir and opts._root_markers then
+ config = vim.deepcopy(config)
+ config.root_dir = vim.fs.root(bufnr, opts._root_markers)
+ end
+
for _, client in pairs(all_clients) do
if reuse_client(client, config) then
if opts.attach == false then
@@ -387,9 +628,8 @@ function lsp.start(config, opts)
if lsp.buf_attach_client(bufnr, client.id) then
return client.id
- else
- return nil
end
+ return
end
end
@@ -398,7 +638,7 @@ function lsp.start(config, opts)
if not opts.silent then
vim.notify(err, vim.log.levels.WARN)
end
- return nil
+ return
end
if opts.attach == false then
@@ -408,8 +648,6 @@ function lsp.start(config, opts)
if client_id and lsp.buf_attach_client(bufnr, client_id) then
return client_id
end
-
- return nil
end
--- Consumes the latest progress messages from all clients and formats them as a string.
@@ -1275,7 +1513,7 @@ end
--- and the value is a function which is called if any LSP action
--- (code action, code lenses, ...) triggers the command.
---
---- If a LSP response contains a command for which no matching entry is
+--- If an LSP response contains a command for which no matching entry is
--- available in this registry, the command will be executed via the LSP server
--- using `workspace/executeCommand`.
---