diff options
| author | Raphael <glepnir@neovim.pro> | 2023-05-06 19:35:25 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-06 19:35:25 +0800 |
| commit | f01429048950195bcab6c430b430fc44b2d9e05a (patch) | |
| tree | 61465e4615f38d1c5798060f513271478924db15 /lua | |
| parent | fix(remarkls): make root_dir more restrictive (#2405) (diff) | |
| download | nvim-lspconfig-f01429048950195bcab6c430b430fc44b2d9e05a.tar nvim-lspconfig-f01429048950195bcab6c430b430fc44b2d9e05a.tar.gz nvim-lspconfig-f01429048950195bcab6c430b430fc44b2d9e05a.tar.bz2 nvim-lspconfig-f01429048950195bcab6c430b430fc44b2d9e05a.tar.lz nvim-lspconfig-f01429048950195bcab6c430b430fc44b2d9e05a.tar.xz nvim-lspconfig-f01429048950195bcab6c430b430fc44b2d9e05a.tar.zst nvim-lspconfig-f01429048950195bcab6c430b430fc44b2d9e05a.zip | |
feat: wrap get root dir in coroutine . (#2565)
Co-authored-by: ii14 <59243201+ii14@users.noreply.github.com>
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/lspconfig/configs.lua | 176 | ||||
| -rw-r--r-- | lua/lspconfig/server_configurations/rust_analyzer.lua | 68 | ||||
| -rw-r--r-- | lua/lspconfig/ui/lspinfo.lua | 59 |
3 files changed, 174 insertions, 129 deletions
diff --git a/lua/lspconfig/configs.lua b/lua/lspconfig/configs.lua index 209a6157..b10bd75d 100644 --- a/lua/lspconfig/configs.lua +++ b/lua/lspconfig/configs.lua @@ -4,6 +4,25 @@ 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 + +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) + end + end)) +end + function configs.__newindex(t, config_name, config_def) validate { name = { config_name, 's' }, @@ -72,17 +91,10 @@ function configs.__newindex(t, config_name, config_def) end if config.autostart == true then - local event - local pattern - if config.filetypes then - event = 'FileType' - pattern = table.concat(config.filetypes, ',') - else - event = 'BufReadPost' - pattern = '*' - end - api.nvim_create_autocmd(event, { - pattern = pattern, + local event_conf = config.filetypes and { event = 'FileType', pattern = config.filetypes } + or { event = 'BufReadPost' } + api.nvim_create_autocmd(event_conf.event, { + pattern = event_conf.pattern or '*', callback = function(opt) M.manager.try_add(opt.buf) end, @@ -97,54 +109,59 @@ function configs.__newindex(t, config_name, config_def) local get_root_dir = config.root_dir function M.launch() - local root_dir - if get_root_dir then - local bufnr = api.nvim_get_current_buf() - 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 + local bufnr = api.nvim_get_current_buf() + local bufname = api.nvim_buf_get_name(bufnr) + if (#bufname == 0 and not config.single_file_support) or (#bufname ~= 0 and not util.bufname_valid(bufname)) then + return + end + + local pwd = uv.cwd() + + async_run(function() + local root_dir + if get_root_dir then + root_dir = get_root_dir(util.path.sanitize(bufname), bufnr) + reenter() + if not api.nvim_buf_is_valid(bufnr) then return end - root_dir = get_root_dir(util.path.sanitize(bufname), bufnr) end - end - if root_dir then - api.nvim_create_autocmd('BufReadPost', { - pattern = fn.fnameescape(root_dir) .. '/*', - callback = function() - M.manager.try_add_wrapper() - end, - group = lsp_group, - desc = string.format( - 'Checks whether server %s should attach to a newly opened buffer inside workspace %q.', - config.name, - root_dir - ), - }) - for _, bufnr in ipairs(api.nvim_list_bufs()) do - local bufname = api.nvim_buf_get_name(bufnr) - if util.bufname_valid(bufname) then - local buf_dir = util.path.sanitize(bufname) - if buf_dir:sub(1, root_dir:len()) == root_dir then - M.manager.try_add_wrapper(bufnr) + if root_dir then + api.nvim_create_autocmd('BufReadPost', { + pattern = fn.fnameescape(root_dir) .. '/*', + callback = function(arg) + M.manager.try_add_wrapper(arg.buf, root_dir) + end, + group = lsp_group, + desc = string.format( + 'Checks whether server %s should attach to a newly opened buffer inside workspace %q.', + config.name, + root_dir + ), + }) + + for _, buf in ipairs(api.nvim_list_bufs()) do + local buf_name = api.nvim_buf_get_name(buf) + 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) + end end end + elseif config.single_file_support then + -- This allows on_new_config to use the parent directory of the file + -- Effectively this is the root from lspconfig's perspective, as we use + -- this to attach additional files in the same parent folder to the same server. + -- We just no longer send rootDirectory or workspaceFolders during initialization. + if not api.nvim_buf_is_valid(bufnr) or (#bufname ~= 0 and not util.bufname_valid(bufname)) then + return + end + local pseudo_root = #bufname == 0 and pwd or util.path.dirname(util.path.sanitize(bufname)) + M.manager.add(pseudo_root, true, bufnr) end - elseif config.single_file_support then - -- This allows on_new_config to use the parent directory of the file - -- Effectively this is the root from lspconfig's perspective, as we use - -- this to attach additional files in the same parent folder to the same server. - -- We just no longer send rootDirectory or workspaceFolders during initialization. - local bufname = api.nvim_buf_get_name(0) - if #bufname ~= 0 and not util.bufname_valid(bufname) then - return - end - local pseudo_root = #bufname == 0 and uv.cwd() or util.path.dirname(util.path.sanitize(bufname)) - M.manager.add(pseudo_root, true, api.nvim_get_current_buf()) - end + end) end -- Used by :LspInfo @@ -238,14 +255,13 @@ function configs.__newindex(t, config_name, config_def) -- 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) + 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 root_dir + local pwd = uv.cwd() local bufname = api.nvim_buf_get_name(bufnr) if #bufname == 0 and not config.single_file_support then @@ -255,35 +271,39 @@ function configs.__newindex(t, config_name, config_def) return end end - local buf_path = util.path.sanitize(bufname) - if get_root_dir then - root_dir = get_root_dir(buf_path, bufnr) + if project_root then + manager.add(project_root, false, bufnr) + return end - if root_dir then - manager.add(root_dir, false, bufnr) - elseif config.single_file_support then - local pseudo_root = #bufname == 0 and uv.cwd() or util.path.dirname(buf_path) - manager.add(pseudo_root, true, bufnr) - end - end + local buf_path = util.path.sanitize(bufname) - -- 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) - bufnr = bufnr or api.nvim_get_current_buf() - local buf_filetype = api.nvim_buf_get_option(bufnr, 'filetype') - if config.filetypes then - for _, filetype in ipairs(config.filetypes) do - if buf_filetype == filetype then - manager.try_add(bufnr) + 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 - -- `config.filetypes = nil` means all filetypes are valid. - else - manager.try_add(bufnr) + + 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 @@ -317,8 +337,6 @@ function configs.__newindex(t, config_name, config_def) M.document_config = config_def rawset(t, config_name, M) - - return M end return setmetatable({}, configs) diff --git a/lua/lspconfig/server_configurations/rust_analyzer.lua b/lua/lspconfig/server_configurations/rust_analyzer.lua index d5c0d940..38039b91 100644 --- a/lua/lspconfig/server_configurations/rust_analyzer.lua +++ b/lua/lspconfig/server_configurations/rust_analyzer.lua @@ -10,6 +10,38 @@ local function reload_workspace(bufnr) end) end +local function get_workspace_dir(cmd) + local co = assert(coroutine.running()) + + local stdout = {} + local stderr = {} + local jobid = vim.fn.jobstart(cmd, { + on_stdout = function(_, data, _) + stdout[#stdout + 1] = table.concat(data, '\n') + 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 (%q) failed:\n%s'):format(table.concat(cmd, ' '), table.concat(stderr, '')), + vim.log.levels.WARN + ) + return + end + + coroutine.yield() + stdout = vim.json.decode(table.concat(stdout, '')) + return stdout and stdout['workspace_root'] or nil +end + return { default_config = { cmd = { 'rust-analyzer' }, @@ -21,36 +53,14 @@ return { cmd[#cmd + 1] = '--manifest-path' cmd[#cmd + 1] = util.path.join(cargo_crate_dir, 'Cargo.toml') end - local cargo_metadata = '' - local cargo_metadata_err = '' - local cm = vim.fn.jobstart(cmd, { - on_stdout = function(_, d, _) - cargo_metadata = table.concat(d, '\n') - end, - on_stderr = function(_, d, _) - cargo_metadata_err = table.concat(d, '\n') - end, - stdout_buffered = true, - stderr_buffered = true, - }) - if cm > 0 then - cm = vim.fn.jobwait({ cm })[1] - else - cm = -1 - end - local cargo_workspace_dir = nil - if cm == 0 then - cargo_workspace_dir = vim.json.decode(cargo_metadata)['workspace_root'] - if cargo_workspace_dir ~= nil then - cargo_workspace_dir = util.path.sanitize(cargo_workspace_dir) - end - else - vim.notify( - string.format('[lspconfig] cmd (%q) failed:\n%s', table.concat(cmd, ' '), cargo_metadata_err), - vim.log.levels.WARN - ) + + local cargo_workspace_root = get_workspace_dir(cmd) + + if cargo_workspace_root then + cargo_workspace_root = util.path.sanitize(cargo_workspace_root) end - return cargo_workspace_dir + + return cargo_workspace_root or cargo_crate_dir or util.root_pattern 'rust-project.json'(fname) or util.find_git_ancestor(fname) diff --git a/lua/lspconfig/ui/lspinfo.lua b/lua/lspconfig/ui/lspinfo.lua index 7bc71d9d..2008efd0 100644 --- a/lua/lspconfig/ui/lspinfo.lua +++ b/lua/lspconfig/ui/lspinfo.lua @@ -6,6 +6,7 @@ local error_messages = { cmd_not_found = 'Unable to find executable. Please check your path and ensure the server is installed', no_filetype_defined = 'No filetypes defined, Please define filetypes in setup()', root_dir_not_found = 'Not found.', + async_root_dir_function = 'Asynchronous root_dir functions are not supported in :LspInfo', } local helptags = { @@ -15,8 +16,8 @@ local helptags = { local function trim_blankspace(cmd) local trimmed_cmd = {} - for _, str in pairs(cmd) do - table.insert(trimmed_cmd, str:match '^%s*(.*)') + for _, str in ipairs(cmd) do + trimmed_cmd[#trimmed_cmd + 1] = str:match '^%s*(.*)' end return trimmed_cmd end @@ -64,9 +65,25 @@ local function make_config_info(config, bufnr) local buffer_dir = api.nvim_buf_call(bufnr, function() return vim.fn.expand '%:p:h' end) - local root_dir = config.get_root_dir and config.get_root_dir(buffer_dir) - if root_dir then - config_info.root_dir = root_dir + + if config.get_root_dir then + local root_dir + local co = coroutine.create(function() + local status, err = pcall(function() + root_dir = config.get_root_dir(buffer_dir) + end) + if not status then + vim.notify(('[lspconfig] unhandled error: %s'):format(tostring(err), vim.log.levels.WARN)) + end + end) + coroutine.resume(co) + if root_dir then + config_info.root_dir = root_dir + elseif coroutine.status(co) == 'suspended' then + config_info.root_dir = error_messages.async_root_dir_function + else + config_info.root_dir = error_messages.root_dir_not_found + end else config_info.root_dir = error_messages.root_dir_not_found vim.list_extend(config_info.helptags, helptags[error_messages.root_dir_not_found]) @@ -118,7 +135,7 @@ local function make_client_info(client, fname) end if workspace_folders then - for _, schema in pairs(workspace_folders) do + for _, schema in ipairs(workspace_folders) do local matched = true local root_dir = uv.fs_realpath(schema.name) if fname:sub(1, root_dir:len()) ~= root_dir then @@ -188,19 +205,19 @@ return function() local buf_lines = {} local buf_client_ids = {} - for _, client in pairs(buf_clients) do - table.insert(buf_client_ids, client.id) + for _, client in ipairs(buf_clients) do + buf_client_ids[#buf_client_ids + 1] = client.id end local other_active_clients = {} - for _, client in pairs(clients) do + for _, client in ipairs(clients) do if not vim.tbl_contains(buf_client_ids, client.id) then - table.insert(other_active_clients, client) + other_active_clients[#other_active_clients + 1] = client end end -- insert the tips at the top of window - table.insert(buf_lines, 'Press q or <Esc> to close this window. Press <Tab> to view server doc.') + buf_lines[#buf_lines + 1] = 'Press q or <Esc> to close this window. Press <Tab> to view server doc.' local header = { '', @@ -215,7 +232,7 @@ return function() } vim.list_extend(buf_lines, buffer_clients_header) - for _, client in pairs(buf_clients) do + for _, client in ipairs(buf_clients) do local client_info = make_client_info(client, fname) vim.list_extend(buf_lines, client_info) end @@ -227,7 +244,7 @@ return function() if not vim.tbl_isempty(other_active_clients) then vim.list_extend(buf_lines, other_active_section_header) end - for _, client in pairs(other_active_clients) do + for _, client in ipairs(other_active_clients) do local client_info = make_client_info(client, fname) vim.list_extend(buf_lines, client_info) end @@ -242,7 +259,7 @@ return function() if not vim.tbl_isempty(other_matching_configs) then vim.list_extend(buf_lines, other_matching_configs_header) - for _, config in pairs(other_matching_configs) do + for _, config in ipairs(other_matching_configs) do vim.list_extend(buf_lines, make_config_info(config, original_bufnr)) end end @@ -310,22 +327,22 @@ return function() end local desc = vim.tbl_get(config, 'document_config', 'docs', 'description') if desc then - table.insert(lines, string.format('# %s', config.name)) - table.insert(lines, '') + lines[#lines + 1] = string.format('# %s', config.name) + lines[#lines + 1] = '' vim.list_extend(lines, vim.split(desc, '\n')) - table.insert(lines, '') + lines[#lines + 1] = '' end end - table.insert(lines, 'Press <Tab> to close server info.') - table.insert(lines, '') + lines[#lines + 1] = 'Press <Tab> to close server info.' + lines[#lines + 1] = '' - for _, client in pairs(buf_clients) do + for _, client in ipairs(buf_clients) do local config = require('lspconfig.configs')[client.name] append_lines(config) end - for _, config in pairs(other_matching_configs) do + for _, config in ipairs(other_matching_configs) do append_lines(config) end |
