aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md6
-rw-r--r--doc/nvim-lsp-installer.txt23
-rw-r--r--lua/nvim-lsp-installer.lua140
-rw-r--r--lua/nvim-lsp-installer/log.lua3
-rw-r--r--lua/nvim-lsp-installer/platform.lua2
-rw-r--r--lua/nvim-lsp-installer/process.lua4
-rw-r--r--lua/nvim-lsp-installer/server.lua6
-rw-r--r--lua/nvim-lsp-installer/servers/init.lua8
-rw-r--r--plugin/nvim-lsp-installer.vim49
9 files changed, 185 insertions, 56 deletions
diff --git a/README.md b/README.md
index 70a2ac81..d2df1dc8 100644
--- a/README.md
+++ b/README.md
@@ -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()