diff options
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | doc/nvim-lsp-installer.txt | 23 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer.lua | 140 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/log.lua | 3 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/platform.lua | 2 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/process.lua | 4 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/server.lua | 6 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/servers/init.lua | 8 | ||||
| -rw-r--r-- | plugin/nvim-lsp-installer.vim | 49 |
9 files changed, 185 insertions, 56 deletions
@@ -59,9 +59,9 @@ Plug 'williamboman/nvim-lsp-installer' ### Commands - `:LspInstallInfo` - opens a graphical overview of your language servers -- `:LspInstall <server> ...` - installs/reinstalls language servers -- `:LspUninstall <server> ...` - uninstalls language servers -- `:LspUninstallAll` - uninstalls all language servers +- `:LspInstall [--sync] <server> ...` - installs/reinstalls language servers. Runs in a blocking fashion if the `--sync` argument is passed (only recommended for scripting purposes). +- `:LspUninstall [--sync] <server> ...` - uninstalls language servers. Runs in a blocking fashion if the `--sync` argument is passed (only recommended for scripting purposes). +- `:LspUninstallAll [--no-confirm]` - uninstalls all language servers - `:LspInstallLog` - opens the log file in a new tab window - `:LspPrintInstalled` - prints all installed language servers diff --git a/doc/nvim-lsp-installer.txt b/doc/nvim-lsp-installer.txt index f333e456..11771981 100644 --- a/doc/nvim-lsp-installer.txt +++ b/doc/nvim-lsp-installer.txt @@ -98,19 +98,30 @@ COMMANDS *nvim-lsp-installer-commands* Opens the UI for nvim-lsp-installer. *:LspInstall* -:LspInstall {server_name} ... +:LspInstall [--sync] {server_name} ... -Installs language servers. +Installs language servers. If the `--sync` argument is passed, the command will +be blocking until all installations complete. This is useful for headless +installations, for example: > + $ nvim --headless -c "LspInstall --sync rust_analyzer clangd clojure_lsp" -c q + +< *:LspUninstall* -:LspUninstall {server_name} ... +:LspUninstall [--sync] {server_name} ... -Uninstalls language servers. +Uninstalls language servers. If the `--sync` argument is passed, the command will +be blocking until all installations complete. This is useful for headless +installations, for example: > + + $ nvim --headless -c "LspUninstall --sync rust_analyzer clangd clojure_lsp" -c q +< *:LspUninstallAll* -:LspUninstallAll +:LspUninstallAll [--no-confirm] -Uninstalls all installed language servers. +Uninstalls all installed language servers. If the --no-confirm argument is +passed, there will be no confirmation prompt before uninstalling all servers. *:LspInstallLog* :LspInstallLog diff --git a/lua/nvim-lsp-installer.lua b/lua/nvim-lsp-installer.lua index 6b2f4a06..5253e654 100644 --- a/lua/nvim-lsp-installer.lua +++ b/lua/nvim-lsp-installer.lua @@ -6,6 +6,7 @@ local status_win = require "nvim-lsp-installer.ui.status-win" local servers = require "nvim-lsp-installer.servers" local settings = require "nvim-lsp-installer.settings" local log = require "nvim-lsp-installer.log" +local platform = require "nvim-lsp-installer.platform" local M = {} @@ -16,11 +17,93 @@ function M.display() status_win().open() end +---Raises an error with the provided message. If in a headless environment, +---will also schedule an immediate shutdown with the provided exit code. +---@param msg string +---@param code number @The exit code to use when in headless mode. +local function raise_error(msg, code) + if platform.is_headless then + vim.schedule(function() + -- We schedule the exit to make sure the call stack is exhausted + os.exit(code or 1) + end) + end + error(msg) +end + +---Installs the provided servers synchronously (blocking call). It's recommended to only use this in headless environments. +---@param server_identifiers string[] @A list of server identifiers (for example {"rust_analyzer@nightly", "tsserver"}). +function M.install_sync(server_identifiers) + local completed_servers = {} + local failed_servers = {} + local server_tuples = {} + + -- Collect all servers and exit early if unable to find one. + for _, server_identifier in pairs(server_identifiers) do + local server_name, version = servers.parse_server_identifier(server_identifier) + local ok, server = servers.get_server(server_name) + if not ok then + raise_error(("Could not find server %q."):format(server_name)) + end + table.insert(server_tuples, { server, version }) + end + + -- Start all installations. + for _, server_tuple in ipairs(server_tuples) do + local server, version = unpack(server_tuple) + + server:install_attached({ + stdio_sink = process.simple_sink(), + requested_server_version = version, + }, function(success) + table.insert(completed_servers, server) + if not success then + table.insert(failed_servers, server) + end + end) + end + + -- Poll for completion. + if vim.wait(60000 * 15, function() + return #completed_servers >= #server_identifiers + end, 100) then + if #failed_servers > 0 then + for _, server in pairs(failed_servers) do + log.fmt_error("Server %s failed to install.", server.name) + end + raise_error(("%d/%d servers failed to install."):format(#failed_servers, #completed_servers)) + end + + for _, server in pairs(completed_servers) do + log.fmt_info("Server %s was successfully installed.", server.name) + end + end +end + +---Unnstalls the provided servers synchronously (blocking call). It's recommended to only use this in headless environments. +---@param server_identifiers string[] @A list of server identifiers (for example {"rust_analyzer@nightly", "tsserver"}). +function M.uninstall_sync(server_identifiers) + for _, server_identifier in pairs(server_identifiers) do + local server_name = servers.parse_server_identifier(server_identifier) + local ok, server = servers.get_server(server_name) + if not ok then + log.error(server) + raise_error(("Could not find server %q."):format(server_name)) + end + local uninstall_ok, uninstall_error = pcall(server.uninstall, server) + if not uninstall_ok then + log.error(tostring(uninstall_error)) + raise_error(("Failed to uninstall server %q."):format(server.name)) + end + log.fmt_info("Successfully uninstalled server %s.", server.name) + end +end + --- Queues a server to be installed. Will also open the status window. --- Use the .on_server_ready(cb) function to register a handler to be executed when a server is ready to be set up. ---@param server_identifier string @The server to install. This can also include a requested version, for example "rust_analyzer@nightly". function M.install(server_identifier) - local server_name, version = unpack(servers.parse_server_identifier(server_identifier)) + local server_name, version = servers.parse_server_identifier(server_identifier) local ok, server = servers.get_server(server_name) if not ok then return notify(("Unable to find LSP server %s.\n\n%s"):format(server_name, server), vim.log.levels.ERROR) @@ -41,35 +124,42 @@ function M.uninstall(server_name) end --- Queues all servers to be uninstalled. Will also open the status window. -function M.uninstall_all() - local choice = vim.fn.confirm( - ("This will uninstall all servers currently installed at %q. Continue?"):format( - vim.fn.fnamemodify(settings.current.install_root_dir, ":~") - ), - "&Yes\n&No", - 2 - ) - if settings.current.install_root_dir ~= settings._DEFAULT_SETTINGS.install_root_dir then - choice = vim.fn.confirm( - ( - "WARNING: You are using a non-default install_root_dir (%q). This command will delete the entire directory. Continue?" - ):format(vim.fn.fnamemodify(settings.current.install_root_dir, ":~")), +function M.uninstall_all(no_confirm) + if not no_confirm then + local choice = vim.fn.confirm( + ("This will uninstall all servers currently installed at %q. Continue?"):format( + vim.fn.fnamemodify(settings.current.install_root_dir, ":~") + ), "&Yes\n&No", 2 ) + if settings.current.install_root_dir ~= settings._DEFAULT_SETTINGS.install_root_dir then + choice = vim.fn.confirm( + ( + "WARNING: You are using a non-default install_root_dir (%q). This command will delete the entire directory. Continue?" + ):format(vim.fn.fnamemodify(settings.current.install_root_dir, ":~")), + "&Yes\n&No", + 2 + ) + end + + if choice ~= 1 then + print "Uninstalling all servers was aborted." + return + end end - if choice == 1 then - log.info "Uninstalling all servers." - status_win().open() - vim.schedule(function() - if fs.dir_exists(settings.current.install_root_dir) then - fs.rmrf(settings.current.install_root_dir) - status_win().mark_all_servers_uninstalled() - end - end) - else - print "Uninstalling all servers was aborted." + + log.info "Uninstalling all servers." + if fs.dir_exists(settings.current.install_root_dir) then + local ok, err = pcall(fs.rmrf, settings.current.install_root_dir) + if not ok then + log.error(err) + raise_error "Failed to uninstall all servers." + end end + log.info "Successfully uninstalled all servers." + status_win().mark_all_servers_uninstalled() + status_win().open() end ---@param cb fun(server: Server) @Callback to be executed whenever a server is ready to be set up. diff --git a/lua/nvim-lsp-installer/log.lua b/lua/nvim-lsp-installer/log.lua index d93fa444..a395a049 100644 --- a/lua/nvim-lsp-installer/log.lua +++ b/lua/nvim-lsp-installer/log.lua @@ -1,6 +1,7 @@ local Data = require "nvim-lsp-installer.data" local path = require "nvim-lsp-installer.path" local settings = require "nvim-lsp-installer.settings" +local platform = require "nvim-lsp-installer.platform" local tbl_pack = Data.tbl_pack @@ -10,7 +11,7 @@ local config = { -- Should print the output to neovim while running -- values: 'sync','async',false - use_console = false, + use_console = platform.is_headless, -- Should highlighting be used in console (using echohl) highlights = true, diff --git a/lua/nvim-lsp-installer/platform.lua b/lua/nvim-lsp-installer/platform.lua index 44b99199..9ab54c6a 100644 --- a/lua/nvim-lsp-installer/platform.lua +++ b/lua/nvim-lsp-installer/platform.lua @@ -28,4 +28,6 @@ M.is_linux = not M.is_mac and M.is_unix -- PATH separator M.path_sep = M.is_win and ";" or ":" +M.is_headless = #vim.api.nvim_list_uis() == 0 + return M diff --git a/lua/nvim-lsp-installer/process.lua b/lua/nvim-lsp-installer/process.lua index ad17465c..7fe3625c 100644 --- a/lua/nvim-lsp-installer/process.lua +++ b/lua/nvim-lsp-installer/process.lua @@ -214,8 +214,8 @@ end function M.simple_sink() return { - stdout = vim.schedule_wrap(print), - stderr = vim.schedule_wrap(vim.api.nvim_err_writeln), + stdout = vim.schedule_wrap(vim.api.nvim_out_write), + stderr = vim.schedule_wrap(vim.api.nvim_err_write), } end diff --git a/lua/nvim-lsp-installer/server.lua b/lua/nvim-lsp-installer/server.lua index f1e0fdef..b1081d7a 100644 --- a/lua/nvim-lsp-installer/server.lua +++ b/lua/nvim-lsp-installer/server.lua @@ -65,10 +65,12 @@ function M.Server:setup(opts) end end +---@return table @A deep copy of this server's default options. Note that these default options are nvim-lsp-installer specific, and does not include any default options provided by lspconfig. function M.Server:get_default_options() return vim.deepcopy(self._default_options) end +---@return string[] @The list of supported filetypes. function M.Server:get_supported_filetypes() local metadata = require "nvim-lsp-installer._generated.metadata" @@ -79,6 +81,7 @@ function M.Server:get_supported_filetypes() return {} end +---@return boolean function M.Server:is_installed() return servers.is_server_installed(self.name) end @@ -87,10 +90,13 @@ function M.Server:create_root_dir() fs.mkdirp(self.root_dir) end +---Queues the server to be asynchronously installed. Also opens the UI window. function M.Server:install() status_win().install_server(self) end +---@param context ServerInstallContext +---@param callback ServerInstallCallback function M.Server:install_attached(context, callback) local uninstall_ok, uninstall_err = pcall(self.uninstall, self) if not uninstall_ok then diff --git a/lua/nvim-lsp-installer/servers/init.lua b/lua/nvim-lsp-installer/servers/init.lua index d98bd224..3f252b83 100644 --- a/lua/nvim-lsp-installer/servers/init.lua +++ b/lua/nvim-lsp-installer/servers/init.lua @@ -154,14 +154,10 @@ function M.is_server_installed(server_name) return scanned_server_dirs[dirname] or false end ----@class ServerTuple ----@field public [1] string The server name. ----@field public [2] string|nil The requested server version. - ---@param server_identifier string @The server identifier to parse. ----@return ServerTuple +---@return string, string|nil @Returns a (server_name, requested_version) tuple, where requested_version may be nil. function M.parse_server_identifier(server_identifier) - return vim.split(server_identifier, "@") + return unpack(vim.split(server_identifier, "@")) end ---@param server_name string diff --git a/plugin/nvim-lsp-installer.vim b/plugin/nvim-lsp-installer.vim index 6ee00af8..fc815e72 100644 --- a/plugin/nvim-lsp-installer.vim +++ b/plugin/nvim-lsp-installer.vim @@ -4,6 +4,8 @@ let g:loaded_nvim_lsp_installer = v:true let s:save_cpo = &cpo set cpo&vim +let s:no_confirm_flag = "--no-confirm" + function! s:LspInstallCompletion(...) abort return join(sort(luaeval("require'nvim-lsp-installer.servers'.get_available_server_names()")), "\n") endfunction @@ -12,20 +14,41 @@ function! s:LspUninstallCompletion(...) abort return join(sort(luaeval("require'nvim-lsp-installer.servers'.get_installed_server_names()")), "\n") endfunction -function! s:LspInstall(server_names) abort - for server_name in split(a:server_names, " ") - call luaeval("require'nvim-lsp-installer'.install(_A)", server_name) - endfor +function! s:LspUninstallAllCompletion(...) abort + return s:no_confirm_flag +endfunction + +function! s:ParseArgs(args) + let sync = a:args[0] == "--sync" + let servers = sync ? a:args[1:] : a:args + return { 'sync': sync, 'servers': servers } +endfunction + +function! s:LspInstall(args) abort + let parsed_args = s:ParseArgs(a:args) + if parsed_args.sync + call luaeval("require'nvim-lsp-installer'.install_sync(_A)", parsed_args.servers) + else + for server_name in l:parsed_args.servers + call luaeval("require'nvim-lsp-installer'.install(_A)", server_name) + endfor + endif endfunction -function! s:LspUninstall(server_names) abort - for server_name in split(a:server_names, " ") - call luaeval("require'nvim-lsp-installer'.uninstall(_A)", server_name) - endfor +function! s:LspUninstall(args) abort + let parsed_args = s:ParseArgs(a:args) + if parsed_args.sync + call luaeval("require'nvim-lsp-installer'.uninstall_sync(_A)", parsed_args.servers) + else + for server_name in l:parsed_args.servers + call luaeval("require'nvim-lsp-installer'.uninstall(_A)", server_name) + endfor + endif endfunction -function! s:LspUninstallAll() abort - lua require'nvim-lsp-installer'.uninstall_all() +function! s:LspUninstallAll(args) abort + let no_confirm = get(a:args, 0, "") == s:no_confirm_flag + call luaeval("require'nvim-lsp-installer'.uninstall_all(_A)", no_confirm ? v:true : v:false) endfunction function! s:LspPrintInstalled() abort @@ -40,10 +63,10 @@ function! s:LspInstallLog() abort exe 'tabnew ' .. luaeval("require'nvim-lsp-installer.log'.outfile") endfunction -command! -nargs=+ -complete=custom,s:LspInstallCompletion LspInstall exe s:LspInstall("<args>") -command! -nargs=+ -complete=custom,s:LspUninstallCompletion LspUninstall exe s:LspUninstall("<args>") +command! -bar -nargs=+ -complete=custom,s:LspInstallCompletion LspInstall call s:LspInstall([<f-args>]) +command! -bar -nargs=+ -complete=custom,s:LspUninstallCompletion LspUninstall call s:LspUninstall([<f-args>]) +command! -bar -nargs=? -complete=custom,s:LspUninstallAllCompletion LspUninstallAll call s:LspUninstallAll([<f-args>]) -command! LspUninstallAll call s:LspUninstallAll() command! LspPrintInstalled call s:LspPrintInstalled() command! LspInstallInfo call s:LspInstallInfo() command! LspInstallLog call s:LspInstallLog() |
