aboutsummaryrefslogtreecommitdiffstats
path: root/lua/lspconfig
diff options
context:
space:
mode:
authorLewis Russell <lewis6991@gmail.com>2023-08-22 14:21:06 +0100
committerGitHub <noreply@github.com>2023-08-22 14:21:06 +0100
commit93c6826b16217eaef568ca5c224ea5d0c12bbb82 (patch)
treea07ad86c520b63ab7843a28bb48a6b786fe1c1f7 /lua/lspconfig
parentfix(rust_analyzer): use attached buffer client send request (#2738) (diff)
parentrefactor: move async_run_command() (diff)
downloadnvim-lspconfig-93c6826b16217eaef568ca5c224ea5d0c12bbb82.tar
nvim-lspconfig-93c6826b16217eaef568ca5c224ea5d0c12bbb82.tar.gz
nvim-lspconfig-93c6826b16217eaef568ca5c224ea5d0c12bbb82.tar.bz2
nvim-lspconfig-93c6826b16217eaef568ca5c224ea5d0c12bbb82.tar.lz
nvim-lspconfig-93c6826b16217eaef568ca5c224ea5d0c12bbb82.tar.xz
nvim-lspconfig-93c6826b16217eaef568ca5c224ea5d0c12bbb82.tar.zst
nvim-lspconfig-93c6826b16217eaef568ca5c224ea5d0c12bbb82.zip
Merge pull request #2775 from lewis6991/refactor/manager
refactor: move manager to separate module
Diffstat (limited to 'lua/lspconfig')
-rw-r--r--lua/lspconfig/async.lua58
-rw-r--r--lua/lspconfig/configs.lua122
-rw-r--r--lua/lspconfig/manager.lua297
-rw-r--r--lua/lspconfig/server_configurations/gopls.lua3
-rw-r--r--lua/lspconfig/server_configurations/rust_analyzer.lua3
-rw-r--r--lua/lspconfig/util.lua225
6 files changed, 397 insertions, 311 deletions
diff --git a/lua/lspconfig/async.lua b/lua/lspconfig/async.lua
new file mode 100644
index 00000000..3421ccc5
--- /dev/null
+++ b/lua/lspconfig/async.lua
@@ -0,0 +1,58 @@
+local M = {}
+
+function M.run(func)
+ coroutine.resume(coroutine.create(function()
+ local status, err = pcall(func)
+ if not status then
+ vim.notify(('[lspconfig] unhandled error: %s'):format(tostring(err)), vim.log.levels.WARN)
+ end
+ end))
+end
+
+--- @param cmd string|string[]
+--- @return string[]?
+function M.run_command(cmd)
+ local co = assert(coroutine.running())
+
+ local stdout = {}
+ local stderr = {}
+ local jobid = vim.fn.jobstart(cmd, {
+ on_stdout = function(_, data, _)
+ data = table.concat(data, '\n')
+ if #data > 0 then
+ stdout[#stdout + 1] = data
+ end
+ end,
+ on_stderr = function(_, data, _)
+ stderr[#stderr + 1] = table.concat(data, '\n')
+ end,
+ on_exit = function()
+ coroutine.resume(co)
+ end,
+ stdout_buffered = true,
+ stderr_buffered = true,
+ })
+
+ if jobid <= 0 then
+ vim.notify(('[lspconfig] cmd go failed:\n%s'):format(table.concat(stderr, '')), vim.log.levels.WARN)
+ return
+ end
+
+ coroutine.yield()
+ if next(stdout) == nil then
+ return nil
+ end
+ return stdout and stdout or nil
+end
+
+function M.reenter()
+ if vim.in_fast_event() then
+ local co = assert(coroutine.running())
+ vim.schedule(function()
+ coroutine.resume(co)
+ end)
+ coroutine.yield()
+ end
+end
+
+return M
diff --git a/lua/lspconfig/configs.lua b/lua/lspconfig/configs.lua
index e74570f1..532befd1 100644
--- a/lua/lspconfig/configs.lua
+++ b/lua/lspconfig/configs.lua
@@ -1,26 +1,28 @@
local util = require 'lspconfig.util'
+local async = require 'lspconfig.async'
local api, validate, lsp, uv, fn = vim.api, vim.validate, vim.lsp, vim.loop, vim.fn
local tbl_deep_extend = vim.tbl_deep_extend
local configs = {}
-local function reenter()
- if vim.in_fast_event() then
- local co = assert(coroutine.running())
- vim.schedule(function()
- coroutine.resume(co)
- end)
- coroutine.yield()
- end
-end
+--- @class lspconfig.Config : lsp.ClientConfig
+--- @field enabled? boolean
+--- @field single_file_support? boolean
+--- @field filetypes? string[]
+--- @field filetype? string
+--- @field on_new_config? function
+--- @field autostart? boolean
+--- @field package _on_attach? fun(client: lsp.Client, bufnr: integer)
-local function async_run(func)
- coroutine.resume(coroutine.create(function()
- local status, err = pcall(func)
- if not status then
- vim.notify(('[lspconfig] unhandled error: %s'):format(tostring(err)), vim.log.levels.WARN)
+--- @param cmd any
+local function sanitize_cmd(cmd)
+ if cmd and type(cmd) == 'table' and not vim.tbl_isempty(cmd) then
+ local original = cmd[1]
+ cmd[1] = vim.fn.exepath(cmd[1])
+ if #cmd[1] == 0 then
+ cmd[1] = original
end
- end))
+ end
end
function configs.__newindex(t, config_name, config_def)
@@ -60,6 +62,7 @@ function configs.__newindex(t, config_name, config_def)
-- Force this part.
default_config.name = config_name
+ --- @param user_config lspconfig.Config
function M.setup(user_config)
local lsp_group = api.nvim_create_augroup('lspconfig', { clear = false })
@@ -86,13 +89,7 @@ function configs.__newindex(t, config_name, config_def)
local config = tbl_deep_extend('keep', user_config, default_config)
- if config.cmd and type(config.cmd) == 'table' and not vim.tbl_isempty(config.cmd) then
- local original = config.cmd[1]
- config.cmd[1] = vim.fn.exepath(config.cmd[1])
- if #config.cmd[1] == 0 then
- config.cmd[1] = original
- end
- end
+ sanitize_cmd(config.cmd)
if util.on_setup then
pcall(util.on_setup, config, user_config)
@@ -104,7 +101,7 @@ function configs.__newindex(t, config_name, config_def)
api.nvim_create_autocmd(event_conf.event, {
pattern = event_conf.pattern or '*',
callback = function(opt)
- M.manager.try_add(opt.buf)
+ M.manager:try_add(opt.buf)
end,
group = lsp_group,
desc = string.format(
@@ -125,11 +122,11 @@ function configs.__newindex(t, config_name, config_def)
local pwd = uv.cwd()
- async_run(function()
+ async.run(function()
local root_dir
if get_root_dir then
root_dir = get_root_dir(util.path.sanitize(bufname), bufnr)
- reenter()
+ async.reenter()
if not api.nvim_buf_is_valid(bufnr) then
return
end
@@ -139,7 +136,7 @@ function configs.__newindex(t, config_name, config_def)
api.nvim_create_autocmd('BufReadPost', {
pattern = fn.fnameescape(root_dir) .. '/*',
callback = function(arg)
- M.manager.try_add_wrapper(arg.buf, root_dir)
+ M.manager:try_add_wrapper(arg.buf, root_dir)
end,
group = lsp_group,
desc = string.format(
@@ -154,7 +151,7 @@ function configs.__newindex(t, config_name, config_def)
if util.bufname_valid(buf_name) then
local buf_dir = util.path.sanitize(buf_name)
if buf_dir:sub(1, root_dir:len()) == root_dir then
- M.manager.try_add_wrapper(buf, root_dir)
+ M.manager:try_add_wrapper(buf, root_dir)
end
end
end
@@ -167,7 +164,7 @@ function configs.__newindex(t, config_name, config_def)
return
end
local pseudo_root = #bufname == 0 and pwd or util.path.dirname(util.path.sanitize(bufname))
- M.manager.add(pseudo_root, true, bufnr)
+ M.manager:add(pseudo_root, true, bufnr)
end
end)
end
@@ -182,7 +179,7 @@ function configs.__newindex(t, config_name, config_def)
-- In the case of a reload, close existing things.
local reload = false
if M.manager then
- for _, client in ipairs(M.manager.clients()) do
+ for _, client in ipairs(M.manager:clients()) do
client.stop(true)
end
reload = true
@@ -190,7 +187,7 @@ function configs.__newindex(t, config_name, config_def)
end
local make_config = function(root_dir)
- local new_config = tbl_deep_extend('keep', vim.empty_dict(), config)
+ local new_config = tbl_deep_extend('keep', vim.empty_dict(), config) --[[@as lspconfig.Config]]
new_config.capabilities = tbl_deep_extend('keep', new_config.capabilities, {
workspace = {
configuration = true,
@@ -257,69 +254,13 @@ function configs.__newindex(t, config_name, config_def)
return new_config
end
- local manager = util.server_per_root_dir_manager(function(root_dir)
- return make_config(root_dir)
- end)
-
- -- Try to attach the buffer `bufnr` to a client using this config, creating
- -- a new client if one doesn't already exist for `bufnr`.
- function manager.try_add(bufnr, project_root)
- bufnr = bufnr or api.nvim_get_current_buf()
-
- if api.nvim_buf_get_option(bufnr, 'buftype') == 'nofile' then
- return
- end
- local pwd = uv.cwd()
-
- local bufname = api.nvim_buf_get_name(bufnr)
- if #bufname == 0 and not config.single_file_support then
- return
- elseif #bufname ~= 0 then
- if not util.bufname_valid(bufname) then
- return
- end
- end
-
- if project_root then
- manager.add(project_root, false, bufnr)
- return
- end
-
- local buf_path = util.path.sanitize(bufname)
-
- async_run(function()
- local root_dir
- if get_root_dir then
- root_dir = get_root_dir(buf_path, bufnr)
- reenter()
- if not api.nvim_buf_is_valid(bufnr) then
- return
- end
- end
-
- if root_dir then
- manager.add(root_dir, false, bufnr)
- elseif config.single_file_support then
- local pseudo_root = #bufname == 0 and pwd or util.path.dirname(buf_path)
- manager.add(pseudo_root, true, bufnr)
- end
- end)
- end
-
- -- Check that the buffer `bufnr` has a valid filetype according to
- -- `config.filetypes`, then do `manager.try_add(bufnr)`.
- function manager.try_add_wrapper(bufnr, project_root)
- -- `config.filetypes = nil` means all filetypes are valid.
- if not config.filetypes or vim.tbl_contains(config.filetypes, vim.bo[bufnr].filetype) then
- manager.try_add(bufnr, project_root)
- end
- end
+ local manager = require('lspconfig.manager').new(config, make_config)
M.manager = manager
M.make_config = make_config
if reload and config.autostart ~= false then
for _, bufnr in ipairs(api.nvim_list_bufs()) do
- manager.try_add_wrapper(bufnr)
+ manager:try_add_wrapper(bufnr)
end
end
end
@@ -329,8 +270,9 @@ function configs.__newindex(t, config_name, config_def)
if not client then
return
end
- if client.config._on_attach then
- client.config._on_attach(client, bufnr)
+ local config = client.config --[[@as lspconfig.Config]]
+ if config._on_attach then
+ config._on_attach(client, bufnr)
end
if client.config.commands and not vim.tbl_isempty(client.config.commands) then
M.commands = vim.tbl_deep_extend('force', M.commands, client.config.commands)
diff --git a/lua/lspconfig/manager.lua b/lua/lspconfig/manager.lua
new file mode 100644
index 00000000..5d260b41
--- /dev/null
+++ b/lua/lspconfig/manager.lua
@@ -0,0 +1,297 @@
+local api = vim.api
+local lsp = vim.lsp
+local uv = vim.loop
+
+local async = require 'lspconfig.async'
+local util = require 'lspconfig.util'
+
+---@param client lsp.Client
+---@param root_dir string
+---@return boolean
+local function check_in_workspace(client, root_dir)
+ if not client.workspace_folders then
+ return false
+ end
+
+ for _, dir in ipairs(client.workspace_folders) do
+ if (root_dir .. '/'):sub(1, #dir.name + 1) == dir.name .. '/' then
+ return true
+ end
+ end
+
+ return false
+end
+
+--- @class lspconfig.Manager
+--- @field _clients table<string,table>
+--- @field config lspconfig.Config
+--- @field make_config fun(root_dir: string): lspconfig.Config
+local M = {}
+
+--- @param config lspconfig.Config
+--- @param make_config fun(root_dir: string): lspconfig.Config
+--- @return lspconfig.Manager
+function M.new(config, make_config)
+ return setmetatable({
+ _clients = {},
+ config = config,
+ make_config = make_config,
+ }, {
+ __index = M,
+ })
+end
+
+--- @private
+function M:_get_client_from_cache(root_dir, client_name)
+ local clients = self._clients
+ if vim.tbl_count(clients) == 0 then
+ return
+ end
+
+ if clients[root_dir] then
+ for _, id in pairs(clients[root_dir]) do
+ local client = lsp.get_client_by_id(id)
+ if client and client.name == client_name then
+ return client
+ end
+ end
+ end
+
+ local all_client_ids = {}
+ vim.tbl_map(function(val)
+ vim.list_extend(all_client_ids, { unpack(val) })
+ end, clients)
+
+ for _, id in ipairs(all_client_ids) do
+ local client = lsp.get_client_by_id(id)
+ if client and client.name == client_name then
+ return client
+ end
+ end
+end
+
+--- @private
+--- @param bufnr integer
+--- @param root string
+--- @param client_id integer
+function M:_attach_and_cache(bufnr, root, client_id)
+ local clients = self._clients
+ lsp.buf_attach_client(bufnr, client_id)
+ if not clients[root] then
+ clients[root] = {}
+ end
+ if not vim.tbl_contains(clients[root], client_id) then
+ clients[root][#clients[root] + 1] = client_id
+ end
+end
+
+--- @private
+--- @param bufnr integer
+--- @param root_dir string
+--- @param client lsp.Client
+function M:_register_workspace_folders(bufnr, root_dir, client)
+ local params = {
+ event = {
+ added = { { uri = vim.uri_from_fname(root_dir), name = root_dir } },
+ removed = {},
+ },
+ }
+ client.rpc.notify('workspace/didChangeWorkspaceFolders', params)
+ if not client.workspace_folders then
+ client.workspace_folders = {}
+ end
+ client.workspace_folders[#client.workspace_folders + 1] = params.event.added[1]
+ self:_attach_and_cache(bufnr, root_dir, client.id)
+end
+
+--- @private
+--- @param bufnr integer
+--- @param new_config lspconfig.Config
+--- @param root_dir string
+--- @param single_file boolean
+function M:_start_new_client(bufnr, new_config, root_dir, single_file)
+ -- do nothing if the client is not enabled
+ if new_config.enabled == false then
+ return
+ end
+ if not new_config.cmd then
+ vim.notify(
+ string.format(
+ '[lspconfig] cmd not defined for %q. Manually set cmd in the setup {} call according to server_configurations.md, see :help lspconfig-index.',
+ new_config.name
+ ),
+ vim.log.levels.ERROR
+ )
+ return
+ end
+
+ local clients = self._clients
+
+ new_config.on_exit = util.add_hook_before(new_config.on_exit, function()
+ for index, id in pairs(clients[root_dir]) do
+ local exist = assert(lsp.get_client_by_id(id))
+ if exist.name == new_config.name then
+ table.remove(clients[root_dir], index)
+ end
+ end
+ end)
+
+ -- Launch the server in the root directory used internally by lspconfig, if otherwise unset
+ -- also check that the path exist
+ if not new_config.cmd_cwd and uv.fs_realpath(root_dir) then
+ new_config.cmd_cwd = root_dir
+ end
+
+ -- Sending rootDirectory and workspaceFolders as null is not explicitly
+ -- codified in the spec. Certain servers crash if initialized with a NULL
+ -- root directory.
+ if single_file then
+ new_config.root_dir = nil
+ new_config.workspace_folders = nil
+ end
+ local client_id = lsp.start_client(new_config)
+ if not client_id then
+ return
+ end
+ self:_attach_and_cache(bufnr, root_dir, client_id)
+end
+
+--- @private
+--- @param bufnr integer
+--- @param new_config lspconfig.Config
+--- @param root_dir string
+--- @param client lsp.Client
+--- @param single_file boolean
+function M:_attach_or_spawn(bufnr, new_config, root_dir, client, single_file)
+ if check_in_workspace(client, root_dir) then
+ return self:_attach_and_cache(bufnr, root_dir, client.id)
+ end
+
+ local supported = vim.tbl_get(client, 'server_capabilities', 'workspace', 'workspaceFolders', 'supported')
+ if supported then
+ return self:_register_workspace_folders(bufnr, root_dir, client)
+ end
+ self:_start_new_client(bufnr, new_config, root_dir, single_file)
+end
+
+--- @private
+--- @param bufnr integer
+--- @param new_config lspconfig.Config
+--- @param root_dir string
+--- @param client lsp.Client
+--- @param single_file boolean
+function M:_attach_after_client_initialized(bufnr, new_config, root_dir, client, single_file)
+ local timer = assert(vim.loop.new_timer())
+ timer:start(
+ 0,
+ 10,
+ vim.schedule_wrap(function()
+ if client.initialized and client.server_capabilities and not timer:is_closing() then
+ self:_attach_or_spawn(bufnr, new_config, root_dir, client, single_file)
+ timer:stop()
+ timer:close()
+ end
+ end)
+ )
+end
+
+---@param root_dir string
+---@param single_file boolean
+---@param bufnr integer
+function M:add(root_dir, single_file, bufnr)
+ root_dir = util.path.sanitize(root_dir)
+ local new_config = self.make_config(root_dir)
+ local client = self:_get_client_from_cache(root_dir, new_config.name)
+
+ if not client then
+ return self:_start_new_client(bufnr, new_config, root_dir, single_file)
+ end
+
+ if self._clients[root_dir] or single_file then
+ lsp.buf_attach_client(bufnr, client.id)
+ return
+ end
+
+ -- make sure neovim had exchanged capabilities from language server
+ -- it's useful to check server support workspaceFolders or not
+ if client.initialized and client.server_capabilities then
+ self:_attach_or_spawn(bufnr, new_config, root_dir, client, single_file)
+ else
+ self:_attach_after_client_initialized(bufnr, new_config, root_dir, client, single_file)
+ end
+end
+
+--- @return lsp.Client[]
+function M:clients()
+ local res = {}
+ for _, client_ids in pairs(self._clients) do
+ for _, id in ipairs(client_ids) do
+ res[#res + 1] = lsp.get_client_by_id(id)
+ end
+ end
+ return res
+end
+
+--- Try to attach the buffer `bufnr` to a client using this config, creating
+--- a new client if one doesn't already exist for `bufnr`.
+--- @param bufnr integer
+--- @param project_root? string
+function M:try_add(bufnr, project_root)
+ bufnr = bufnr or api.nvim_get_current_buf()
+
+ if vim.bo[bufnr].buftype == 'nofile' then
+ return
+ end
+
+ local bufname = api.nvim_buf_get_name(bufnr)
+ if #bufname == 0 and not self.config.single_file_support then
+ return
+ end
+
+ if #bufname ~= 0 and not util.bufname_valid(bufname) then
+ return
+ end
+
+ if project_root then
+ self:add(project_root, false, bufnr)
+ return
+ end
+
+ local buf_path = util.path.sanitize(bufname)
+
+ local get_root_dir = self.config.root_dir
+
+ local pwd = assert(uv.cwd())
+
+ async.run(function()
+ local root_dir
+ if get_root_dir then
+ root_dir = get_root_dir(buf_path, bufnr)
+ async.reenter()
+ if not api.nvim_buf_is_valid(bufnr) then
+ return
+ end
+ end
+
+ if root_dir then
+ self:add(root_dir, false, bufnr)
+ elseif self.config.single_file_support then
+ local pseudo_root = #bufname == 0 and pwd or util.path.dirname(buf_path)
+ self:add(pseudo_root, true, bufnr)
+ end
+ end)
+end
+
+--- Check that the buffer `bufnr` has a valid filetype according to
+--- `config.filetypes`, then do `manager.try_add(bufnr)`.
+--- @param bufnr integer
+--- @param project_root? string
+function M:try_add_wrapper(bufnr, project_root)
+ local config = self.config
+ -- `config.filetypes = nil` means all filetypes are valid.
+ if not config.filetypes or vim.tbl_contains(config.filetypes, vim.bo[bufnr].filetype) then
+ self:try_add(bufnr, project_root)
+ end
+end
+
+return M
diff --git a/lua/lspconfig/server_configurations/gopls.lua b/lua/lspconfig/server_configurations/gopls.lua
index 139f63d8..f27033b7 100644
--- a/lua/lspconfig/server_configurations/gopls.lua
+++ b/lua/lspconfig/server_configurations/gopls.lua
@@ -1,4 +1,5 @@
local util = require 'lspconfig.util'
+local async = require 'lspconfig.async'
local mod_cache = nil
return {
@@ -8,7 +9,7 @@ return {
root_dir = function(fname)
-- see: https://github.com/neovim/nvim-lspconfig/issues/804
if not mod_cache then
- local result = util.async_run_command 'go env GOMODCACHE'
+ local result = async.run_command 'go env GOMODCACHE'
if result and result[1] then
mod_cache = vim.trim(result[1])
end
diff --git a/lua/lspconfig/server_configurations/rust_analyzer.lua b/lua/lspconfig/server_configurations/rust_analyzer.lua
index cdabbb39..6ed471d2 100644
--- a/lua/lspconfig/server_configurations/rust_analyzer.lua
+++ b/lua/lspconfig/server_configurations/rust_analyzer.lua
@@ -1,4 +1,5 @@
local util = require 'lspconfig.util'
+local async = require 'lspconfig.async'
local function reload_workspace(bufnr)
bufnr = util.validate_bufnr(bufnr)
@@ -54,7 +55,7 @@ return {
cmd[#cmd + 1] = util.path.join(cargo_crate_dir, 'Cargo.toml')
end
- local result = util.async_run_command(cmd)
+ local result = async.run_command(cmd)
local cargo_workspace_root
if result and result[1] then
diff --git a/lua/lspconfig/util.lua b/lua/lspconfig/util.lua
index f4e2ab78..98f9a323 100644
--- a/lua/lspconfig/util.lua
+++ b/lua/lspconfig/util.lua
@@ -137,6 +137,8 @@ M.path = (function()
end
end
+ --- @param path string
+ --- @return string?
local function dirname(path)
local strip_dir_pat = '/([^/]+)$'
local strip_sep_pat = '/$'
@@ -227,191 +229,6 @@ M.path = (function()
}
end)()
-local function check_in_workspace(client, root_dir)
- if not client.workspace_folders then
- return false
- end
-
- for _, dir in ipairs(client.workspace_folders) do
- if (root_dir .. '/'):sub(1, #dir.name + 1) == dir.name .. '/' then
- return true
- end
- end
-
- return false
-end
-
--- Returns a function(root_dir), which, when called with a root_dir it hasn't
--- seen before, will call make_config(root_dir) and start a new client.
-function M.server_per_root_dir_manager(make_config)
- -- a table store the root dir with clients in this dir
- local clients = {}
- local manager = {}
-
- function manager.add(root_dir, single_file, bufnr)
- root_dir = M.path.sanitize(root_dir)
-
- local function get_client_from_cache(client_name)
- if vim.tbl_count(clients) == 0 then
- return
- end
-
- if clients[root_dir] then
- for _, id in pairs(clients[root_dir]) do
- local client = lsp.get_client_by_id(id)
- if client and client.name == client_name then
- return client
- end
- end
- end
-
- local all_client_ids = {}
- vim.tbl_map(function(val)
- vim.list_extend(all_client_ids, { unpack(val) })
- end, clients)
-
- for _, id in ipairs(all_client_ids) do
- local client = lsp.get_client_by_id(id)
- if client and client.name == client_name then
- return client
- end
- end
- end
-
- local function attach_and_cache(root, client_id)
- lsp.buf_attach_client(bufnr, client_id)
- if not clients[root] then
- clients[root] = {}
- end
- if not vim.tbl_contains(clients[root], client_id) then
- clients[root][#clients[root] + 1] = client_id
- end
- end
-
- local new_config = make_config(root_dir)
-
- local function register_workspace_folders(client)
- local params = {
- event = {
- added = { { uri = vim.uri_from_fname(root_dir), name = root_dir } },
- removed = {},
- },
- }
- client.rpc.notify('workspace/didChangeWorkspaceFolders', params)
- if not client.workspace_folders then
- client.workspace_folders = {}
- end
- client.workspace_folders[#client.workspace_folders + 1] = params.event.added[1]
- attach_and_cache(root_dir, client.id)
- end
-
- local function start_new_client()
- -- do nothing if the client is not enabled
- if new_config.enabled == false then
- return
- end
- if not new_config.cmd then
- vim.notify(
- string.format(
- '[lspconfig] cmd not defined for %q. Manually set cmd in the setup {} call according to server_configurations.md, see :help lspconfig-index.',
- new_config.name
- ),
- vim.log.levels.ERROR
- )
- return
- end
- new_config.on_exit = M.add_hook_before(new_config.on_exit, function()
- for index, id in pairs(clients[root_dir]) do
- local exist = lsp.get_client_by_id(id)
- if exist.name == new_config.name then
- table.remove(clients[root_dir], index)
- end
- end
- end)
-
- -- Launch the server in the root directory used internally by lspconfig, if otherwise unset
- -- also check that the path exist
- if not new_config.cmd_cwd and uv.fs_realpath(root_dir) then
- new_config.cmd_cwd = root_dir
- end
-
- -- Sending rootDirectory and workspaceFolders as null is not explicitly
- -- codified in the spec. Certain servers crash if initialized with a NULL
- -- root directory.
- if single_file then
- new_config.root_dir = nil
- new_config.workspace_folders = nil
- end
- local client_id = lsp.start_client(new_config)
- if not client_id then
- return
- end
- attach_and_cache(root_dir, client_id)
- end
-
- local function attach_or_spawn(client)
- if check_in_workspace(client, root_dir) then
- return attach_and_cache(root_dir, client.id)
- end
-
- local supported = vim.tbl_get(client, 'server_capabilities', 'workspace', 'workspaceFolders', 'supported')
- if supported then
- return register_workspace_folders(client)
- end
- start_new_client()
- end
-
- local attach_after_client_initialized = function(client)
- local timer = vim.loop.new_timer()
- timer:start(
- 0,
- 10,
- vim.schedule_wrap(function()
- if client.initialized and client.server_capabilities and not timer:is_closing() then
- attach_or_spawn(client)
- timer:stop()
- timer:close()
- end
- end)
- )
- end
-
- local client = get_client_from_cache(new_config.name)
-
- if not client then
- return start_new_client()
- end
-
- if clients[root_dir] or single_file then
- lsp.buf_attach_client(bufnr, client.id)
- return
- end
-
- -- make sure neovim had exchanged capabilities from language server
- -- it's useful to check server support workspaceFolders or not
- if client.initialized and client.server_capabilities then
- attach_or_spawn(client)
- else
- attach_after_client_initialized(client)
- end
- end
-
- function manager.clients()
- local res = {}
- for _, client_ids in pairs(clients) do
- for _, id in ipairs(client_ids) do
- local client = lsp.get_client_by_id(id)
- if client then
- res[#res + 1] = client
- end
- end
- end
- return res
- end
-
- return manager
-end
-
function M.search_ancestors(startpath, func)
validate { func = { func, 'f' } }
if func(startpath) then
@@ -447,6 +264,7 @@ function M.root_pattern(...)
return M.search_ancestors(startpath, matcher)
end
end
+
function M.find_git_ancestor(startpath)
return M.search_ancestors(startpath, function(path)
-- Support git directories and git files (worktrees)
@@ -455,6 +273,7 @@ function M.find_git_ancestor(startpath)
end
end)
end
+
function M.find_mercurial_ancestor(startpath)
return M.search_ancestors(startpath, function(path)
-- Support Mercurial directories
@@ -463,6 +282,7 @@ function M.find_mercurial_ancestor(startpath)
end
end)
end
+
function M.find_node_modules_ancestor(startpath)
return M.search_ancestors(startpath, function(path)
if M.path.is_dir(M.path.join(path, 'node_modules')) then
@@ -470,6 +290,7 @@ function M.find_node_modules_ancestor(startpath)
end
end)
end
+
function M.find_package_json_ancestor(startpath)
return M.search_ancestors(startpath, function(path)
if M.path.is_file(M.path.join(path, 'package.json')) then
@@ -580,38 +401,4 @@ function M.strip_archive_subpath(path)
return path
end
-function M.async_run_command(cmd)
- local co = assert(coroutine.running())
-
- local stdout = {}
- local stderr = {}
- local jobid = vim.fn.jobstart(cmd, {
- on_stdout = function(_, data, _)
- data = table.concat(data, '\n')
- if #data > 0 then
- stdout[#stdout + 1] = data
- end
- end,
- on_stderr = function(_, data, _)
- stderr[#stderr + 1] = table.concat(data, '\n')
- end,
- on_exit = function()
- coroutine.resume(co)
- end,
- stdout_buffered = true,
- stderr_buffered = true,
- })
-
- if jobid <= 0 then
- vim.notify(('[lspconfig] cmd go failed:\n%s'):format(table.concat(stderr, '')), vim.log.levels.WARN)
- return
- end
-
- coroutine.yield()
- if next(stdout) == nil then
- return nil
- end
- return stdout and stdout or nil
-end
-
return M