aboutsummaryrefslogtreecommitdiffstats
path: root/lua
diff options
context:
space:
mode:
authorWilliam Boman <william@redwill.se>2022-03-26 13:41:50 +0100
committerGitHub <noreply@github.com>2022-03-26 13:41:50 +0100
commit212d17a039da449043b67529c29851db37acc236 (patch)
tree38411b14487895cef0d7648e198b79fd28793fe6 /lua
parentrun autogen_metadata.lua (diff)
downloadmason-212d17a039da449043b67529c29851db37acc236.tar
mason-212d17a039da449043b67529c29851db37acc236.tar.gz
mason-212d17a039da449043b67529c29851db37acc236.tar.bz2
mason-212d17a039da449043b67529c29851db37acc236.tar.lz
mason-212d17a039da449043b67529c29851db37acc236.tar.xz
mason-212d17a039da449043b67529c29851db37acc236.tar.zst
mason-212d17a039da449043b67529c29851db37acc236.zip
add async managers (#536)
Diffstat (limited to 'lua')
-rw-r--r--lua/nvim-lsp-installer/core/async/init.lua4
-rw-r--r--lua/nvim-lsp-installer/core/async/uv.lua55
-rw-r--r--lua/nvim-lsp-installer/core/context.lua167
-rw-r--r--lua/nvim-lsp-installer/core/fs.lua68
-rw-r--r--lua/nvim-lsp-installer/core/managers/cargo/init.lua100
-rw-r--r--lua/nvim-lsp-installer/core/managers/composer/init.lua106
-rw-r--r--lua/nvim-lsp-installer/core/managers/dotnet/init.lua31
-rw-r--r--lua/nvim-lsp-installer/core/managers/gem/init.lua132
-rw-r--r--lua/nvim-lsp-installer/core/managers/git/init.lua54
-rw-r--r--lua/nvim-lsp-installer/core/managers/go/init.lua109
-rw-r--r--lua/nvim-lsp-installer/core/managers/npm/init.lua129
-rw-r--r--lua/nvim-lsp-installer/core/managers/opam/init.lua41
-rw-r--r--lua/nvim-lsp-installer/core/managers/pip3/init.lua144
-rw-r--r--lua/nvim-lsp-installer/core/optional.lua90
-rw-r--r--lua/nvim-lsp-installer/core/result.lua51
-rw-r--r--lua/nvim-lsp-installer/core/spawn.lua (renamed from lua/nvim-lsp-installer/core/async/spawn.lua)26
-rw-r--r--lua/nvim-lsp-installer/installers/npm.lua1
-rw-r--r--lua/nvim-lsp-installer/installers/pip3.lua1
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/cargo.lua59
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/composer.lua42
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/gem.lua95
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/git.lua42
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/github_release_file.lua41
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/github_tag.lua34
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/init.lua51
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/jdtls.lua35
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/npm.lua45
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/pip3.lua72
-rw-r--r--lua/nvim-lsp-installer/jobs/version-check/init.lua95
-rw-r--r--lua/nvim-lsp-installer/path.lua2
30 files changed, 1396 insertions, 526 deletions
diff --git a/lua/nvim-lsp-installer/core/async/init.lua b/lua/nvim-lsp-installer/core/async/init.lua
index 6ecb5a61..0c278373 100644
--- a/lua/nvim-lsp-installer/core/async/init.lua
+++ b/lua/nvim-lsp-installer/core/async/init.lua
@@ -91,8 +91,8 @@ local function new_execution_context(suspend_fn, callback, ...)
end
end
-exports.run = function(suspend_fn, callback)
- return new_execution_context(suspend_fn, callback)
+exports.run = function(suspend_fn, callback, ...)
+ return new_execution_context(suspend_fn, callback, ...)
end
exports.scope = function(suspend_fn)
diff --git a/lua/nvim-lsp-installer/core/async/uv.lua b/lua/nvim-lsp-installer/core/async/uv.lua
new file mode 100644
index 00000000..a2249be9
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/async/uv.lua
@@ -0,0 +1,55 @@
+local a = require "nvim-lsp-installer.core.async"
+
+---@type Record<UvMethod, async fun(...)>
+local M = setmetatable({}, {
+ __index = function(_, method)
+ ---@async
+ return function(...)
+ local err, result = a.promisify(vim.loop[method])(...)
+ if err then
+ error(err, 2)
+ end
+ return result
+ end
+ end,
+})
+
+return M
+
+---@alias UvMethod
+---| '"fs_close"'
+---| '"fs_open"'
+---| '"fs_read"'
+---| '"fs_unlink"'
+---| '"fs_write"'
+---| '"fs_mkdir"'
+---| '"fs_mkdtemp"'
+---| '"fs_mkstemp"'
+---| '"fs_rmdir"'
+---| '"fs_scandir"'
+---| '"fs_stat"'
+---| '"fs_fstat"'
+---| '"fs_lstat"'
+---| '"fs_rename"'
+---| '"fs_fsync"'
+---| '"fs_fdatasync"'
+---| '"fs_ftruncate"'
+---| '"fs_sendfile"'
+---| '"fs_access"'
+---| '"fs_chmod"'
+---| '"fs_fchmod"'
+---| '"fs_utime"'
+---| '"fs_futime"'
+---| '"fs_lutime"'
+---| '"fs_link"'
+---| '"fs_symlink"'
+---| '"fs_readlink"'
+---| '"fs_realpath"'
+---| '"fs_chown"'
+---| '"fs_fchown"'
+---| '"fs_lchown"'
+---| '"fs_copyfile"'
+---| '"fs_opendir"'
+---| '"fs_readdir"'
+---| '"fs_closedir"'
+---| '"fs_statfs"'
diff --git a/lua/nvim-lsp-installer/core/context.lua b/lua/nvim-lsp-installer/core/context.lua
new file mode 100644
index 00000000..128ad417
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/context.lua
@@ -0,0 +1,167 @@
+local spawn = require "nvim-lsp-installer.core.spawn"
+local log = require "nvim-lsp-installer.log"
+local Optional = require "nvim-lsp-installer.core.optional"
+local fs = require "nvim-lsp-installer.core.fs"
+local settings = require "nvim-lsp-installer.settings"
+local Result = require "nvim-lsp-installer.core.result"
+local path = require "nvim-lsp-installer.path"
+local platform = require "nvim-lsp-installer.platform"
+
+---@class ContextualSpawn
+---@field cwd CwdManager
+---@field stdio_sink StdioSink
+local ContextualSpawn = {}
+
+---@param cwd CwdManager
+---@param stdio_sink StdioSink
+function ContextualSpawn.new(cwd, stdio_sink)
+ return setmetatable({ cwd = cwd, stdio_sink = stdio_sink }, ContextualSpawn)
+end
+function ContextualSpawn.__index(self, cmd)
+ return function(args)
+ args.cwd = args.cwd or self.cwd:get()
+ args.stdio_sink = args.stdio_sink or self.stdio_sink
+ -- We get_or_throw() here for convenience reasons.
+ -- Almost every time spawn is called via context we want the command to succeed.
+ return spawn[cmd](args):get_or_throw()
+ end
+end
+
+---@class ContextualFs
+---@field private cwd CwdManager
+local ContextualFs = {}
+ContextualFs.__index = ContextualFs
+
+---@param cwd CwdManager
+function ContextualFs.new(cwd)
+ return setmetatable({ cwd = cwd }, ContextualFs)
+end
+
+---@async
+---@param rel_path string @The relative path from the current working directory to the file to append.
+---@param contents string
+function ContextualFs:append_file(rel_path, contents)
+ return fs.append_file(path.concat { self.cwd:get(), rel_path }, contents)
+end
+
+---@async
+---@param rel_path string @The relative path from the current working directory.
+function ContextualFs:file_exists(rel_path)
+ return fs.file_exists(path.concat { self.cwd:get(), rel_path })
+end
+
+---@async
+---@param rel_path string @The relative path from the current working directory.
+function ContextualFs:dir_exists(rel_path)
+ return fs.dir_exists(path.concat { self.cwd:get(), rel_path })
+end
+
+---@class CwdManager
+local CwdManager = {}
+CwdManager.__index = CwdManager
+function CwdManager.new(cwd)
+ return setmetatable({ cwd = cwd }, CwdManager)
+end
+function CwdManager:get()
+ return self.cwd
+end
+function CwdManager:set(new_cwd)
+ self.cwd = new_cwd
+end
+
+---@class InstallContext
+---@field public receipt InstallReceiptBuilder
+---@field public requested_version Optional
+---@field public fs ContextualFs
+---@field public spawn JobSpawn
+---@field private cwd_manager CwdManager
+---@field private destination_dir string
+local InstallContext = {}
+InstallContext.__index = InstallContext
+
+function InstallContext.new(opts)
+ local cwd_manager = CwdManager.new(opts.cwd)
+ return setmetatable({
+ cwd_manager = cwd_manager,
+ spawn = ContextualSpawn.new(cwd_manager, opts.stdio_sink),
+ fs = ContextualFs.new(cwd_manager),
+ receipt = opts.receipt,
+ requested_version = opts.requested_version,
+ destination_dir = opts.destination_dir,
+ }, InstallContext)
+end
+
+---@deprecated
+---@param ctx ServerInstallContext
+---@param destination_dir string
+function InstallContext.from_server_context(ctx, destination_dir)
+ return InstallContext.new {
+ cwd = ctx.install_dir,
+ receipt = ctx.receipt,
+ stdio_sink = ctx.stdio_sink,
+ requested_version = Optional.of_nilable(ctx.requested_server_version),
+ destination_dir = destination_dir,
+ }
+end
+
+function InstallContext:cwd()
+ return self.cwd_manager:get()
+end
+
+---@param new_cwd string @The new cwd (absolute path).
+function InstallContext:set_cwd(new_cwd)
+ self
+ :ensure_path_ownership(new_cwd)
+ :map(function(p)
+ self.cwd_manager:set(p)
+ return p
+ end)
+ :get_or_throw()
+end
+
+---@param abs_path string
+function InstallContext:ensure_path_ownership(abs_path)
+ if path.is_subdirectory(self:cwd_manager(), abs_path) or self.destination_dir == abs_path then
+ return Result.success(abs_path)
+ else
+ return Result.failure(
+ ("Path %q is outside of current path ownership (%q)."):format(abs_path, settings.current.install_root_dir)
+ )
+ end
+end
+
+---@async
+function InstallContext:promote_cwd()
+ local cwd = self:cwd()
+ if self.destination_dir == cwd then
+ log.fmt_debug("cwd %s is already promoted (at %s)", cwd, self.destination_dir)
+ return Result.success "Current working dir is already in destination."
+ end
+ log.fmt_debug("Promoting cwd %s to %s", cwd, self.destination_dir)
+ return Result.run_catching(function()
+ -- 1. Remove destination dir, if it exists
+ if fs.dir_exists(self.destination_dir) then
+ fs.rmrf(self.destination_dir)
+ end
+ return self.destination_dir
+ end)
+ :map_catching(function(destination_dir)
+ -- 2. Prepare for renaming cwd to destination
+ if platform.is_unix then
+ -- Some Unix systems will raise an error when renaming a directory to a destination that does not already exist.
+ fs.mkdir(destination_dir)
+ end
+ return destination_dir
+ end)
+ :map_catching(function(destination_dir)
+ -- 3. Move the cwd to the final installation directory
+ fs.rename(cwd, destination_dir)
+ return destination_dir
+ end)
+ :map_catching(function(destination_dir)
+ -- 4. Update cwd
+ self:set_cwd(destination_dir)
+ end)
+end
+
+return InstallContext
diff --git a/lua/nvim-lsp-installer/core/fs.lua b/lua/nvim-lsp-installer/core/fs.lua
new file mode 100644
index 00000000..f3db813a
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/fs.lua
@@ -0,0 +1,68 @@
+local uv = require "nvim-lsp-installer.core.async.uv"
+local log = require "nvim-lsp-installer.log"
+local a = require "nvim-lsp-installer.core.async"
+
+local M = {}
+
+---@async
+---@param path string
+---@param contents string
+function M.append_file(path, contents)
+ local fd = uv.fs_open(path, "a", 438)
+ uv.fs_write(fd, contents, -1)
+ uv.fs_close(fd)
+end
+
+---@async
+---@param path string
+function M.file_exists(path)
+ local ok, fd = pcall(uv.fs_open, path, "r", 438)
+ if not ok then
+ return false
+ end
+ local fstat = uv.fs_fstat(fd)
+ uv.fs_close(fd)
+ return fstat.type == "file"
+end
+
+---@async
+---@param path string
+function M.dir_exists(path)
+ local ok, fd = pcall(uv.fs_open, path, "r", 438)
+ if not ok then
+ return false
+ end
+ local fstat = uv.fs_fstat(fd)
+ uv.fs_close(fd)
+ return fstat.type == "directory"
+end
+
+---@async
+---@param path string
+function M.rmrf(path)
+ log.debug("fs: rmrf", path)
+ if vim.in_fast_event() then
+ a.scheduler()
+ end
+ if vim.fn.delete(path, "rf") ~= 0 then
+ log.debug "fs: rmrf failed"
+ error(("rmrf: Could not remove directory %q."):format(path))
+ end
+end
+
+---@async
+---@param path string
+function M.mkdir(path)
+ log.debug("fs: mkdir", path)
+ uv.fs_mkdir(path, 493) -- 493(10) == 755(8)
+end
+
+---@async
+---@param path string
+---@param new_path string
+function M.rename(path, new_path)
+ log.debug("fs: rename", path, new_path)
+ uv.fs_rename(path, new_path)
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/core/managers/cargo/init.lua b/lua/nvim-lsp-installer/core/managers/cargo/init.lua
new file mode 100644
index 00000000..29c07868
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/managers/cargo/init.lua
@@ -0,0 +1,100 @@
+local process = require "nvim-lsp-installer.process"
+local path = require "nvim-lsp-installer.path"
+local spawn = require "nvim-lsp-installer.core.spawn"
+local a = require "nvim-lsp-installer.core.async"
+local Optional = require "nvim-lsp-installer.core.optional"
+local crates = require "nvim-lsp-installer.core.clients.crates"
+local Result = require "nvim-lsp-installer.core.result"
+
+local fetch_crate = a.promisify(crates.fetch_crate, true)
+
+local M = {}
+
+---@param crate string The crate to install.
+---@param opts {git:boolean, features:string|nil}
+function M.crate(crate, opts)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ opts = opts or {}
+ ctx.requested_version:if_present(function()
+ assert(not opts.git, "Providing a version when installing a git crate is not allowed.")
+ end)
+
+ ctx.receipt:with_primary_source(ctx.receipt.cargo(crate))
+
+ ctx.spawn.cargo {
+ "install",
+ "--root",
+ ".",
+ "--locked",
+ ctx.requested_version
+ :map(function(version)
+ return { "--version", version }
+ end)
+ :or_else(vim.NIL),
+ opts.features and { "--features", opts.features } or vim.NIL,
+ opts.git and { "--git", crate } or crate,
+ }
+ end
+end
+
+---@param output string @The `cargo install --list` output.
+---@return Record<string, string> @Key is the crate name, value is its version.
+function M.parse_installed_crates(output)
+ local installed_crates = {}
+ for _, line in ipairs(vim.split(output, "\n")) do
+ local name, version = line:match "^(.+)%s+v([.%S]+)[%s:]"
+ if name and version then
+ installed_crates[name] = version
+ end
+ end
+ return installed_crates
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.check_outdated_primary_package(receipt, install_dir)
+ local installed_version = M.get_installed_primary_package_version(receipt, install_dir):get_or_throw()
+
+ local response = fetch_crate(receipt.primary_source.package)
+ if installed_version ~= response.crate.max_stable_version then
+ return Result.success {
+ name = receipt.primary_source.package,
+ current_version = installed_version,
+ latest_version = response.crate.max_stable_version,
+ }
+ else
+ return Result.failure "Primary package is not outdated."
+ end
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.get_installed_primary_package_version(receipt, install_dir)
+ return spawn.cargo({
+ "install",
+ "--list",
+ "--root",
+ ".",
+ cwd = install_dir,
+ }):map_catching(function(result)
+ local installed_crates = M.parse_installed_crates(result.stdout)
+ if vim.in_fast_event() then
+ a.scheduler() -- needed because vim.fn.* call
+ end
+ local package = vim.fn.fnamemodify(receipt.primary_source.package, ":t")
+ return Optional.of_nilable(installed_crates[package]):or_else_throw "Failed to find cargo package version."
+ end)
+end
+
+---@param install_dir string
+function M.env(install_dir)
+ return {
+ PATH = process.extend_path { path.concat { install_dir, "bin" } },
+ }
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/core/managers/composer/init.lua b/lua/nvim-lsp-installer/core/managers/composer/init.lua
new file mode 100644
index 00000000..9a1e2dd4
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/managers/composer/init.lua
@@ -0,0 +1,106 @@
+local Data = require "nvim-lsp-installer.data"
+local process = require "nvim-lsp-installer.process"
+local path = require "nvim-lsp-installer.path"
+local Result = require "nvim-lsp-installer.core.result"
+local spawn = require "nvim-lsp-installer.core.spawn"
+local Optional = require "nvim-lsp-installer.core.optional"
+
+local list_copy, list_find_first = Data.list_copy, Data.list_find_first
+
+local M = {}
+
+---@param packages string[] The composer packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one.
+function M.require(packages)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ local pkgs = list_copy(packages)
+
+ ctx.receipt:with_primary_source(ctx.receipt.composer(pkgs[1]))
+ for i = 2, #pkgs do
+ ctx.receipt:with_secondary_source(ctx.receipt.composer(pkgs[i]))
+ end
+
+ if not ctx.fs:file_exists "composer.json" then
+ ctx.spawn.composer { "init", "--no-interaction", "--stability=stable" }
+ end
+
+ ctx.requested_version:if_present(function(version)
+ pkgs[1] = ("%s:%s"):format(pkgs[1], version)
+ end)
+
+ ctx.spawn.composer { "require", pkgs }
+ end
+end
+
+function M.install()
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ ctx.spawn.composer {
+ "install",
+ "--no-interaction",
+ "--no-dev",
+ "--optimize-autoloader",
+ "--classmap-authoritative",
+ }
+ end
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.check_outdated_primary_package(receipt, install_dir)
+ if receipt.primary_source.type ~= "composer" then
+ return Result.failure "Receipt does not have a primary source of type composer"
+ end
+ return spawn.composer({
+ "outdated",
+ "--no-interaction",
+ "--format=json",
+ cwd = install_dir,
+ }):map_catching(function(result)
+ local outdated_packages = vim.json.decode(result.stdout)
+ local outdated_package = list_find_first(outdated_packages.installed, function(package)
+ return package.name == receipt.primary_source.package
+ end)
+ return Optional.of_nilable(outdated_package)
+ :map(function(package)
+ if package.version ~= package.latest then
+ return {
+ name = package.name,
+ current_version = package.version,
+ latest_version = package.latest,
+ }
+ end
+ end)
+ :or_else_throw "Primary package is not outdated."
+ end)
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.get_installed_primary_package_version(receipt, install_dir)
+ if receipt.primary_source.type ~= "composer" then
+ return Result.failure "Receipt does not have a primary source of type composer"
+ end
+ return spawn.composer({
+ "info",
+ "--format=json",
+ receipt.primary_source.package,
+ cwd = install_dir,
+ }):map_catching(function(result)
+ local info = vim.json.decode(result.stdout)
+ return info.versions[1]
+ end)
+end
+
+---@param install_dir string
+function M.env(install_dir)
+ return {
+ PATH = process.extend_path { path.concat { install_dir, "vendor", "bin" } },
+ }
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/core/managers/dotnet/init.lua b/lua/nvim-lsp-installer/core/managers/dotnet/init.lua
new file mode 100644
index 00000000..c862146f
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/managers/dotnet/init.lua
@@ -0,0 +1,31 @@
+local process = require "nvim-lsp-installer.process"
+local M = {}
+
+---@param package string
+function M.package(package)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ ctx.receipt:with_primary_source(ctx.receipt.dotnet(package))
+ ctx.spawn.dotnet {
+ "tool",
+ "update",
+ "--tool-path",
+ ".",
+ ctx.requested_version
+ :map(function(version)
+ return { "--version", version }
+ end)
+ :or_else(vim.NIL),
+ package,
+ }
+ end
+end
+
+function M.env(root_dir)
+ return {
+ PATH = process.extend_path { root_dir },
+ }
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/core/managers/gem/init.lua b/lua/nvim-lsp-installer/core/managers/gem/init.lua
new file mode 100644
index 00000000..7b030797
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/managers/gem/init.lua
@@ -0,0 +1,132 @@
+local Data = require "nvim-lsp-installer.data"
+local process = require "nvim-lsp-installer.process"
+local path = require "nvim-lsp-installer.path"
+local Result = require "nvim-lsp-installer.core.result"
+local spawn = require "nvim-lsp-installer.core.spawn"
+local Optional = require "nvim-lsp-installer.core.optional"
+
+local list_copy, list_find_first = Data.list_copy, Data.list_find_first
+
+local M = {}
+
+---@param packages string[] @The Gem packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one.
+function M.packages(packages)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ local pkgs = list_copy(packages or {})
+
+ ctx.receipt:with_primary_source(ctx.receipt.gem(pkgs[1]))
+ for i = 2, #pkgs do
+ ctx.receipt:with_secondary_source(ctx.receipt.gem(pkgs[i]))
+ end
+
+ ctx.requested_version:if_present(function(version)
+ pkgs[1] = ("%s:%s"):format(pkgs[1], version)
+ end)
+
+ ctx.spawn.gem {
+ "install",
+ "--no-user-install",
+ "--install-dir=.",
+ "--bindir=bin",
+ "--no-document",
+ pkgs,
+ }
+ end
+end
+
+---@alias GemOutdatedPackage {name:string, current_version: string, latest_version: string}
+
+---Parses a string input like "package (0.1.0 < 0.2.0)" into its components
+---@param outdated_gem string
+---@return GemOutdatedPackage
+function M.parse_outdated_gem(outdated_gem)
+ local package_name, version_expression = outdated_gem:match "^(.+) %((.+)%)"
+ if not package_name or not version_expression then
+ -- unparseable
+ return nil
+ end
+ local current_version, latest_version = unpack(vim.split(version_expression, "<"))
+
+ ---@type GemOutdatedPackage
+ local outdated_package = {
+ name = vim.trim(package_name),
+ current_version = vim.trim(current_version),
+ latest_version = vim.trim(latest_version),
+ }
+ return outdated_package
+end
+
+---Parses the stdout of the `gem list` command into a Record<package_name, version>
+---@param output string
+function M.parse_gem_list_output(output)
+ ---@type Record<string, string>
+ local gem_versions = {}
+ for _, line in ipairs(vim.split(output, "\n")) do
+ local gem_package, version = line:match "^(%S+) %((%S+)%)$"
+ if gem_package and version then
+ gem_versions[gem_package] = version
+ end
+ end
+ return gem_versions
+end
+
+local function not_empty(s)
+ return s ~= nil and s ~= ""
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.check_outdated_primary_package(receipt, install_dir)
+ if receipt.primary_source.type ~= "gem" then
+ return Result.failure "Receipt does not have a primary source of type gem"
+ end
+ return spawn.gem({ "outdated", cwd = install_dir, env = process.graft_env(M.env(install_dir)) }):map_catching(
+ function(result)
+ ---@type string[]
+ local lines = vim.split(result.stdout, "\n")
+ local outdated_gems = vim.tbl_map(M.parse_outdated_gem, vim.tbl_filter(not_empty, lines))
+
+ local outdated_gem = list_find_first(outdated_gems, function(gem)
+ return gem.name == receipt.primary_source.package and gem.current_version ~= gem.latest_version
+ end)
+
+ return Optional.of_nilable(outdated_gem)
+ :map(function(gem)
+ return {
+ name = receipt.primary_source.package,
+ current_version = assert(gem.current_version),
+ latest_version = assert(gem.latest_version),
+ }
+ end)
+ :or_else_throw "Primary package is not outdated."
+ end
+ )
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.get_installed_primary_package_version(receipt, install_dir)
+ return spawn.gem({
+ "list",
+ cwd = install_dir,
+ env = process.graft_env(M.env(install_dir)),
+ }):map_catching(function(result)
+ local gems = M.parse_gem_list_output(result.stdout)
+ return Optional.of_nilable(gems[receipt.primary_source.package]):or_else_throw "Failed to find gem package version."
+ end)
+end
+
+---@param install_dir string
+function M.env(install_dir)
+ return {
+ GEM_HOME = install_dir,
+ GEM_PATH = install_dir,
+ PATH = process.extend_path { path.concat { install_dir, "bin" } },
+ }
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/core/managers/git/init.lua b/lua/nvim-lsp-installer/core/managers/git/init.lua
new file mode 100644
index 00000000..bdd79917
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/managers/git/init.lua
@@ -0,0 +1,54 @@
+local spawn = require "nvim-lsp-installer.core.spawn"
+local Result = require "nvim-lsp-installer.core.result"
+local M = {}
+
+---@param opts {[1]:string} @The first item in the table is the repository to clone.
+function M.clone(opts)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ local repo = assert(opts[1], "No git URL provided.")
+ ctx.spawn.git { "clone", "--depth", "1", repo, "." }
+ ctx.requested_version:if_present(function(version)
+ ctx.spawn.git { "fetch", "--depth", "1", "origin", version }
+ ctx.spawn.git { "checkout", "FETCH_HEAD" }
+ end)
+ ctx.receipt:with_primary_source(ctx.receipt.git_remote(repo))
+ end
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.check_outdated_git_clone(receipt, install_dir)
+ if receipt.primary_source.type ~= "git" then
+ return Result.failure "Receipt does not have a primary source of type git"
+ end
+ return spawn.git({ "fetch", "origin", "HEAD", cwd = install_dir }):map_catching(function()
+ local result = spawn.git({ "rev-parse", "FETCH_HEAD", "HEAD", cwd = install_dir }):get_or_throw()
+ local remote_head, local_head = unpack(vim.split(result.stdout, "\n"))
+ if remote_head == local_head then
+ error("Git clone is up to date.", 2)
+ end
+ return {
+ name = receipt.primary_source.remote,
+ current_version = assert(local_head),
+ latest_version = assert(remote_head),
+ }
+ end)
+end
+
+---@async
+---@param install_dir string
+function M.get_installed_revision(install_dir)
+ return spawn.git({
+ "rev-parse",
+ "--short",
+ "HEAD",
+ cwd = install_dir,
+ }):map_catching(function(result)
+ return assert(vim.trim(result.stdout))
+ end)
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/core/managers/go/init.lua b/lua/nvim-lsp-installer/core/managers/go/init.lua
new file mode 100644
index 00000000..167cfff4
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/managers/go/init.lua
@@ -0,0 +1,109 @@
+local process = require "nvim-lsp-installer.process"
+local spawn = require "nvim-lsp-installer.core.spawn"
+local a = require "nvim-lsp-installer.core.async"
+local Optional = require "nvim-lsp-installer.core.optional"
+
+local M = {}
+
+---@param packages string[] The Go packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one.
+function M.packages(packages)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ local env = process.graft_env {
+ GOBIN = ctx.cwd(),
+ }
+ -- Install the head package
+ do
+ local head_package = packages[1]
+ ctx.receipt:with_primary_source(ctx.receipt.go(head_package))
+ local version = ctx.requested_version:or_else "latest"
+ ctx.spawn.go {
+ "install",
+ "-v",
+ ("%s@%s"):format(head_package, version),
+ env = env,
+ }
+ end
+
+ -- Install secondary packages
+ for i = 2, #packages do
+ local package = packages[i]
+ ctx.receipt:with_secondary_source(ctx.receipt.go(package))
+ ctx.spawn.go { "install", "-v", ("%s@latest"):format(package), env = env }
+ end
+ end
+end
+
+---@param output string @The output from `go version -m` command.
+function M.parse_mod_version_output(output)
+ ---@type {path: string[], mod: string[], dep: string[], build: string[]}
+ local result = {}
+ local lines = vim.split(output, "\n")
+ for _, line in ipairs { unpack(lines, 2) } do
+ local type, id, value = unpack(vim.split(line, "%s+", { trimempty = true }))
+ if type and id then
+ result[type] = result[type] or {}
+ result[type][id] = value or ""
+ end
+ end
+ return result
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.get_installed_primary_package_version(receipt, install_dir)
+ if vim.in_fast_event() then
+ a.scheduler()
+ end
+ -- trims e.g. golang.org/x/tools/gopls to gopls
+ local executable = vim.fn.fnamemodify(receipt.primary_source.package, ":t")
+ return spawn.go({
+ "version",
+ "-m",
+ executable,
+ cwd = install_dir,
+ }):map_catching(function(result)
+ local parsed_output = M.parse_mod_version_output(result.stdout)
+ return Optional.of_nilable(parsed_output.mod[receipt.primary_source.package]):or_else_throw "Failed to parse mod version"
+ end)
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.check_outdated_primary_package(receipt, install_dir)
+ return spawn.go({
+ "list",
+ "-json",
+ "-m",
+ "-versions",
+ receipt.primary_source.package,
+ cwd = install_dir,
+ }):map_catching(function(result)
+ ---@type {Path: string, Versions: string[]}
+ local output = vim.json.decode(result.stdout)
+ return Optional.of_nilable(output.Versions[#output.Versions])
+ :map(function(latest_version)
+ local installed_version = M.get_installed_primary_package_version(receipt, install_dir):get_or_throw()
+ if installed_version ~= latest_version then
+ return {
+ name = receipt.primary_source.package,
+ current_version = assert(installed_version),
+ latest_version = assert(latest_version),
+ }
+ end
+ end)
+ :or_else_throw "Primary package is not outdated."
+ end)
+end
+
+---@param install_dir string
+function M.env(install_dir)
+ return {
+ PATH = process.extend_path { install_dir },
+ }
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/core/managers/npm/init.lua b/lua/nvim-lsp-installer/core/managers/npm/init.lua
new file mode 100644
index 00000000..c3be01da
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/managers/npm/init.lua
@@ -0,0 +1,129 @@
+local Data = require "nvim-lsp-installer.data"
+local spawn = require "nvim-lsp-installer.core.spawn"
+local Optional = require "nvim-lsp-installer.core.optional"
+local Result = require "nvim-lsp-installer.core.result"
+local process = require "nvim-lsp-installer.process"
+local path = require "nvim-lsp-installer.path"
+
+local list_copy = Data.list_copy
+
+local M = {}
+
+---@async
+---@param ctx InstallContext
+local function ensure_npm_root(ctx)
+ if not (ctx.fs:dir_exists "node_modules" or ctx.fs:file_exists "package.json") then
+ -- Create a package.json to set a boundary for where npm installs packages.
+ ctx.spawn.npm { "init", "--yes", "--scope=lsp-installer" }
+ end
+end
+
+---@param packages string[] @The npm packages to install. The first item in this list will be the recipient of the requested version, if set.
+function M.packages(packages)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ local pkgs = list_copy(packages)
+
+ -- Use global-style. The reasons for this are:
+ -- a) To avoid polluting the executables (aka bin-links) that npm creates.
+ -- b) The installation is, after all, more similar to a "global" installation. We don't really gain
+ -- any of the benefits of not using global style (e.g., deduping the dependency tree).
+ --
+ -- We write to .npmrc manually instead of going through npm because managing a local .npmrc file
+ -- is a bit unreliable across npm versions (especially <7), so we take extra measures to avoid
+ -- inadvertently polluting global npm config.
+ ctx.fs:append_file(".npmrc", "global-style=true")
+
+ ctx.receipt:with_primary_source(ctx.receipt.npm(pkgs[1]))
+ for i = 2, #pkgs do
+ ctx.receipt:with_secondary_source(ctx.receipt.npm(pkgs[i]))
+ end
+
+ ctx.requested_version:if_present(function(version)
+ pkgs[1] = ("%s@%s"):format(pkgs[1], version)
+ end)
+
+ M.install(pkgs)(ctx)
+ end
+end
+
+---@param packages string[] @The npm packages to install.
+function M.install(packages)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ ensure_npm_root(ctx)
+ ctx.spawn.npm { "install", packages }
+ end
+end
+
+---@param exec_args string[] @The arguments to pass to npm exec.
+function M.exec(exec_args)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ ctx.spawn.npm { "exec", "--yes", exec_args }
+ end
+end
+
+---@param script string @The npm script to run.
+function M.run(script)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ ctx.spawn.npm { "run", script }
+ end
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.get_installed_primary_package_version(receipt, install_dir)
+ if receipt.primary_source.type ~= "npm" then
+ return Result.failure "Receipt does not have a primary source of type npm"
+ end
+ return spawn.npm({ "ls", "--json", cwd = install_dir }):map_catching(function(result)
+ local npm_packages = vim.json.decode(result.stdout)
+ return npm_packages.dependencies[receipt.primary_source.package].version
+ end)
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.check_outdated_primary_package(receipt, install_dir)
+ if receipt.primary_source.type ~= "npm" then
+ return Result.failure "Receipt does not have a primary source of type npm"
+ end
+ local primary_package = receipt.primary_source.package
+ local npm_outdated = spawn.npm { "outdated", "--json", primary_package, cwd = install_dir }
+ if npm_outdated:is_success() then
+ return Result.failure "Primary package is not outdated."
+ end
+ return npm_outdated:recover_catching(function(result)
+ assert(result.exit_code == 1, "Expected npm outdated to return exit code 1.")
+ local data = vim.json.decode(result.stdout)
+
+ return Optional.of_nilable(data[primary_package])
+ :map(function(outdated_package)
+ if outdated_package.current ~= outdated_package.latest then
+ return {
+ name = primary_package,
+ current_version = assert(outdated_package.current),
+ latest_version = assert(outdated_package.latest),
+ }
+ end
+ end)
+ :or_else_throw()
+ end)
+end
+
+---@param install_dir string
+function M.env(install_dir)
+ return {
+ PATH = process.extend_path { path.concat { install_dir, "node_modules", ".bin" } },
+ }
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/core/managers/opam/init.lua b/lua/nvim-lsp-installer/core/managers/opam/init.lua
new file mode 100644
index 00000000..8fd60de7
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/managers/opam/init.lua
@@ -0,0 +1,41 @@
+local Data = require "nvim-lsp-installer.data"
+local path = require "nvim-lsp-installer.path"
+local process = require "nvim-lsp-installer.process"
+
+local M = {}
+
+local list_copy = Data.list_copy
+
+---@param packages string[] @The opam packages to install. The first item in this list will be the recipient of the requested version, if set.
+function M.packages(packages)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ local pkgs = list_copy(packages)
+
+ ctx.receipt:with_primary_source(ctx.receipt.opam(pkgs[1]))
+ for i = 2, #pkgs do
+ ctx.receipt:with_secondary_source(ctx.receipt.opam(pkgs[i]))
+ end
+
+ ctx.requested_version:if_present(function(version)
+ pkgs[1] = ("%s.%s"):format(pkgs[1], version)
+ end)
+
+ ctx.spawn.opam {
+ "install",
+ "--destdir=.",
+ "--yes",
+ "--verbose",
+ pkgs,
+ }
+ end
+end
+
+function M.env(root_dir)
+ return {
+ PATH = process.extend_path { path.concat { root_dir, "bin" } },
+ }
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/core/managers/pip3/init.lua b/lua/nvim-lsp-installer/core/managers/pip3/init.lua
new file mode 100644
index 00000000..61130a96
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/managers/pip3/init.lua
@@ -0,0 +1,144 @@
+local Data = require "nvim-lsp-installer.data"
+local settings = require "nvim-lsp-installer.settings"
+local process = require "nvim-lsp-installer.process"
+local path = require "nvim-lsp-installer.path"
+local platform = require "nvim-lsp-installer.platform"
+local Optional = require "nvim-lsp-installer.core.optional"
+local Result = require "nvim-lsp-installer.core.result"
+local spawn = require "nvim-lsp-installer.core.spawn"
+
+local list_find_first, list_copy, list_not_nil = Data.list_find_first, Data.list_copy, Data.list_not_nil
+local VENV_DIR = "venv"
+
+local M = {}
+
+---@param packages string[] @The pip packages to install. The first item in this list will be the recipient of the requested version, if set.
+function M.packages(packages)
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ local pkgs = list_copy(packages)
+
+ ctx.receipt:with_primary_source(ctx.receipt.pip3(pkgs[1]))
+ for i = 2, #pkgs do
+ ctx.receipt:with_secondary_source(ctx.receipt.pip3(pkgs[i]))
+ end
+
+ ctx.requested_version:if_present(function(version)
+ pkgs[1] = ("%s==%s"):format(pkgs[1], version)
+ end)
+
+ local executables = platform.is_win and list_not_nil(vim.g.python3_host_prog, "python", "python3")
+ or list_not_nil(vim.g.python3_host_prog, "python3", "python")
+
+ -- pip3 will hardcode the full path to venv executables, so we need to promote cwd to make sure pip uses the final destination path.
+ ctx:promote_cwd():get_or_throw()
+
+ -- Find first executable that manages to create venv
+ local executable = list_find_first(executables, function(executable)
+ return pcall(ctx.spawn[executable], { "-m", "venv", VENV_DIR })
+ end)
+
+ Optional.of_nilable(executable)
+ :if_present(function(python3)
+ ctx.spawn[python3] {
+ env = process.graft_env(M.env(ctx:cwd())), -- use venv env
+ "-m",
+ "pip",
+ "install",
+ "-U",
+ settings.current.pip.install_args,
+ pkgs,
+ }
+ end)
+ :or_else_throw "Unable to create python3 venv environment."
+ end
+end
+
+---@param package string
+---@return string
+function M.normalize_package(package)
+ -- https://stackoverflow.com/a/60307740
+ local s = package:gsub("%[.*%]", "")
+ return s
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.check_outdated_primary_package(receipt, install_dir)
+ if receipt.primary_source.type ~= "pip3" then
+ return Result.failure "Receipt does not have a primary source of type pip3"
+ end
+ local normalized_package = M.normalize_package(receipt.primary_source.package)
+ return spawn.python({
+ "-m",
+ "pip",
+ "list",
+ "--outdated",
+ "--format=json",
+ cwd = install_dir,
+ env = process.graft_env(M.env(install_dir)), -- use venv
+ }):map_catching(function(result)
+ ---@alias PipOutdatedPackage {name: string, version: string, latest_version: string}
+ ---@type PipOutdatedPackage[]
+ local packages = vim.json.decode(result.stdout)
+
+ local outdated_primary_package = list_find_first(packages, function(outdated_package)
+ return outdated_package.name == normalized_package
+ and outdated_package.version ~= outdated_package.latest_version
+ end)
+
+ return Optional.of_nilable(outdated_primary_package)
+ :map(function(package)
+ return {
+ name = normalized_package,
+ current_version = assert(package.version),
+ latest_version = assert(package.latest_version),
+ }
+ end)
+ :or_else_throw "Primary package is not outdated."
+ end)
+end
+
+---@async
+---@param receipt InstallReceipt
+---@param install_dir string
+function M.get_installed_primary_package_version(receipt, install_dir)
+ if receipt.primary_source.type ~= "pip3" then
+ return Result.failure "Receipt does not have a primary source of type pip3"
+ end
+ return spawn.python({
+ "-m",
+ "pip",
+ "list",
+ "--format=json",
+ cwd = install_dir,
+ env = process.graft_env(M.env(install_dir)), -- use venv env
+ }):map_catching(function(result)
+ local pip_packages = vim.json.decode(result.stdout)
+ local normalized_pip_package = M.normalize_package(receipt.primary_source.package)
+ local pip_package = list_find_first(pip_packages, function(package)
+ return package.name == normalized_pip_package
+ end)
+ return Optional.of_nilable(pip_package)
+ :map(function(package)
+ return package.version
+ end)
+ :or_else_throw "Unable to find pip package."
+ end)
+end
+
+---@param install_dir string
+function M.env(install_dir)
+ return {
+ PATH = process.extend_path { M.venv_path(install_dir) },
+ }
+end
+
+---@param install_dir string
+function M.venv_path(install_dir)
+ return path.concat { install_dir, VENV_DIR, platform.is_win and "Scripts" or "bin" }
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/core/optional.lua b/lua/nvim-lsp-installer/core/optional.lua
new file mode 100644
index 00000000..d37a7e69
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/optional.lua
@@ -0,0 +1,90 @@
+---@class Optional
+---@field private _value unknown
+local Optional = {}
+Optional.__index = Optional
+
+---@param value any
+function Optional.new(value)
+ return setmetatable({ _value = value }, Optional)
+end
+
+local EMPTY = Optional.new(nil)
+
+---@param value any
+function Optional.of_nilable(value)
+ if value == nil then
+ return EMPTY
+ else
+ return Optional.new(value)
+ end
+end
+
+function Optional.empty()
+ return EMPTY
+end
+
+---@param value any
+function Optional.of(value)
+ return Optional.new(value)
+end
+
+---@param mapper_fn fun(value: any): any
+function Optional:map(mapper_fn)
+ if self:is_present() then
+ return Optional.of_nilable(mapper_fn(self._value))
+ else
+ return EMPTY
+ end
+end
+
+function Optional:get()
+ if not self:is_present() then
+ error("No value present.", 2)
+ end
+ return self._value
+end
+
+---@param value any
+function Optional:or_else(value)
+ if self:is_present() then
+ return self._value
+ else
+ return value
+ end
+end
+
+---@param supplier fun(): Optional
+function Optional:when_empty(supplier)
+ if self:is_present() then
+ return self._value
+ else
+ return supplier()
+ end
+end
+
+---@param exception any @(optional) The exception to throw if the result is a failure.
+function Optional:or_else_throw(exception)
+ if self:is_present() then
+ return self._value
+ else
+ if exception then
+ error(exception, 2)
+ else
+ error("No value present.", 2)
+ end
+ end
+end
+
+---@param fn fun(value: any)
+function Optional:if_present(fn)
+ if self:is_present() then
+ fn(self._value)
+ end
+ return self
+end
+
+function Optional:is_present()
+ return self._value ~= nil
+end
+
+return Optional
diff --git a/lua/nvim-lsp-installer/core/result.lua b/lua/nvim-lsp-installer/core/result.lua
index a94ffe52..f5c2d747 100644
--- a/lua/nvim-lsp-installer/core/result.lua
+++ b/lua/nvim-lsp-installer/core/result.lua
@@ -32,11 +32,24 @@ function Result:get_or_nil()
end
end
-function Result:get_or_throw()
+function Result:get_or_else(value)
if self:is_success() then
return self.value
else
- error(self.value.error, 2)
+ return value
+ end
+end
+
+---@param exception any @(optional) The exception to throw if the result is a failure.
+function Result:get_or_throw(exception)
+ if self:is_success() then
+ return self.value
+ else
+ if exception ~= nil then
+ error(exception, 2)
+ else
+ error(self.value.error, 2)
+ end
end
end
@@ -77,4 +90,38 @@ function Result:map_catching(mapper_fn)
end
end
+---@param recover_fn fun(value: any): any
+function Result:recover(recover_fn)
+ if self:is_failure() then
+ return Result.success(recover_fn(self:err_or_nil()))
+ else
+ return self
+ end
+end
+
+---@param recover_fn fun(value: any): any
+function Result:recover_catching(recover_fn)
+ if self:is_failure() then
+ local ok, value = pcall(recover_fn, self:err_or_nil())
+ if ok then
+ return Result.success(value)
+ else
+ return Result.failure(value)
+ end
+ else
+ return self
+ end
+end
+
+---@param fn fun(): any
+---@return Result
+function Result.run_catching(fn)
+ local ok, result = pcall(fn)
+ if ok then
+ return Result.success(result)
+ else
+ return Result.failure(result)
+ end
+end
+
return Result
diff --git a/lua/nvim-lsp-installer/core/async/spawn.lua b/lua/nvim-lsp-installer/core/spawn.lua
index 5fc7eee7..992b1557 100644
--- a/lua/nvim-lsp-installer/core/async/spawn.lua
+++ b/lua/nvim-lsp-installer/core/spawn.lua
@@ -5,11 +5,22 @@ local platform = require "nvim-lsp-installer.platform"
local async_spawn = a.promisify(process.spawn)
----@type Record<string, fun(opts: JobSpawnOpts): Result>
+---@alias JobSpawn Record<string, async fun(opts: JobSpawnOpts): Result>
+---@type JobSpawn
local spawn = {
- aliases = {
+ _aliases = {
npm = platform.is_win and "npm.cmd" or "npm",
+ gem = platform.is_win and "gem.cmd" or "gem",
+ composer = platform.is_win and "composer.bat" or "composer",
},
+ -- Utility function for optionally including arguments.
+ ---@generic T
+ ---@param condition boolean
+ ---@param value T
+ ---@return T
+ _when = function(condition, value)
+ return condition and value or vim.NIL
+ end,
}
local function Failure(err, cmd)
@@ -22,10 +33,15 @@ end
setmetatable(spawn, {
__index = function(self, k)
+ ---@param args string|nil|string[][]
return function(args)
local cmd_args = {}
- for i, arg in ipairs(args) do
- cmd_args[i] = arg
+ for _, arg in ipairs(args) do
+ if type(arg) == "table" then
+ vim.list_extend(cmd_args, arg)
+ elseif arg ~= vim.NIL then
+ cmd_args[#cmd_args + 1] = arg
+ end
end
---@type JobSpawnOpts
local spawn_args = {
@@ -41,7 +57,7 @@ setmetatable(spawn, {
spawn_args.stdio_sink = stdio.sink
end
- local cmd = self.aliases[k] or k
+ local cmd = self._aliases[k] or k
local _, exit_code = async_spawn(cmd, spawn_args)
if exit_code == 0 then
diff --git a/lua/nvim-lsp-installer/installers/npm.lua b/lua/nvim-lsp-installer/installers/npm.lua
index f891589e..12ca407f 100644
--- a/lua/nvim-lsp-installer/installers/npm.lua
+++ b/lua/nvim-lsp-installer/installers/npm.lua
@@ -1,3 +1,4 @@
+---@deprecated Will be replaced by core.managers.npm
local path = require "nvim-lsp-installer.path"
local fs = require "nvim-lsp-installer.fs"
local Data = require "nvim-lsp-installer.data"
diff --git a/lua/nvim-lsp-installer/installers/pip3.lua b/lua/nvim-lsp-installer/installers/pip3.lua
index 9476318d..67032a40 100644
--- a/lua/nvim-lsp-installer/installers/pip3.lua
+++ b/lua/nvim-lsp-installer/installers/pip3.lua
@@ -1,3 +1,4 @@
+---@deprecated Will be replaced by core.managers.pip3
local path = require "nvim-lsp-installer.path"
local Data = require "nvim-lsp-installer.data"
local installers = require "nvim-lsp-installer.installers"
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/cargo.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/cargo.lua
deleted file mode 100644
index 19fe7448..00000000
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/cargo.lua
+++ /dev/null
@@ -1,59 +0,0 @@
-local process = require "nvim-lsp-installer.process"
-local VersionCheckResult = require "nvim-lsp-installer.jobs.outdated-servers.version-check-result"
-local crates = require "nvim-lsp-installer.core.clients.crates"
-
----@param output string The `cargo install --list` output.
-local function parse_installed_crates(output)
- local installed_crates = {}
- for _, line in ipairs(vim.split(output, "\n")) do
- local name, version = line:match "^(.+)%s+v([.%S]+)[%s:]"
- if name and version then
- installed_crates[name] = version
- end
- end
- return installed_crates
-end
-
----@param server Server
----@param source InstallReceiptSource
----@param on_result fun(result: VersionCheckResult)
-local function cargo_check(server, source, on_result)
- local stdio = process.in_memory_sink()
- process.spawn("cargo", {
- args = { "install", "--list", "--root", "." },
- cwd = server.root_dir,
- stdio_sink = stdio.sink,
- }, function(success)
- if not success then
- return on_result(VersionCheckResult.fail(server))
- end
- local installed_crates = parse_installed_crates(table.concat(stdio.buffers.stdout, ""))
- if not installed_crates[source.package] then
- return on_result(VersionCheckResult.fail(server))
- end
- crates.fetch_crate(source.package, function(err, response)
- if err then
- return on_result(VersionCheckResult.fail(server))
- end
- if response.crate.max_stable_version ~= installed_crates[source.package] then
- return on_result(VersionCheckResult.success(server, {
- {
- name = source.package,
- current_version = installed_crates[source.package],
- latest_version = response.crate.max_stable_version,
- },
- }))
- else
- return on_result(VersionCheckResult.empty(server))
- end
- end)
- end)
-end
-
-return setmetatable({
- parse_installed_crates = parse_installed_crates,
-}, {
- __call = function(_, ...)
- return cargo_check(...)
- end,
-})
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/composer.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/composer.lua
deleted file mode 100644
index b8e75424..00000000
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/composer.lua
+++ /dev/null
@@ -1,42 +0,0 @@
-local process = require "nvim-lsp-installer.process"
-local composer = require "nvim-lsp-installer.installers.composer"
-local VersionCheckResult = require "nvim-lsp-installer.jobs.outdated-servers.version-check-result"
-
----@param server Server
----@param source InstallReceiptSource
----@param on_check_complete fun(result: VersionCheckResult)
-local function composer_checker(server, source, on_check_complete)
- local stdio = process.in_memory_sink()
- process.spawn(composer.composer_cmd, {
- args = { "outdated", "--no-interaction", "--format=json" },
- cwd = server.root_dir,
- stdio_sink = stdio.sink,
- }, function(success)
- if not success then
- return on_check_complete(VersionCheckResult.fail(server))
- end
- ---@type {installed: {name: string, version: string, latest: string}[]}
- local decode_ok, outdated_json = pcall(vim.json.decode, table.concat(stdio.buffers.stdout, ""))
-
- if not decode_ok then
- return on_check_complete(VersionCheckResult.fail(server))
- end
-
- ---@type OutdatedPackage[]
- local outdated_packages = {}
-
- for _, outdated_package in ipairs(outdated_json.installed) do
- if outdated_package.name == source.package and outdated_package.version ~= outdated_package.latest then
- table.insert(outdated_packages, {
- name = outdated_package.name,
- current_version = outdated_package.version,
- latest_version = outdated_package.latest,
- })
- end
- end
-
- on_check_complete(VersionCheckResult.success(server, outdated_packages))
- end)
-end
-
-return composer_checker
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/gem.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/gem.lua
deleted file mode 100644
index cf880fd0..00000000
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/gem.lua
+++ /dev/null
@@ -1,95 +0,0 @@
-local process = require "nvim-lsp-installer.process"
-local gem = require "nvim-lsp-installer.installers.gem"
-local log = require "nvim-lsp-installer.log"
-local VersionCheckResult = require "nvim-lsp-installer.jobs.outdated-servers.version-check-result"
-
-local function not_empty(s)
- return s ~= nil and s ~= ""
-end
-
----Parses a string input like "package (0.1.0 < 0.2.0)" into its components
----@param outdated_gem string
----@return GemOutdatedPackage
-local function parse_outdated_gem(outdated_gem)
- local package_name, version_expression = outdated_gem:match "^(.+) %((.+)%)"
- if not package_name or not version_expression then
- -- unparseable
- return nil
- end
- local current_version, latest_version = unpack(vim.split(version_expression, "<"))
-
- ---@alias GemOutdatedPackage {name:string, current_version: string, latest_version: string}
- local outdated_package = {
- name = vim.trim(package_name),
- current_version = vim.trim(current_version),
- latest_version = vim.trim(latest_version),
- }
- return outdated_package
-end
-
----@param output string
-local function parse_gem_list_output(output)
- ---@type Record<string, string>
- local gem_versions = {}
- for _, line in ipairs(vim.split(output, "\n")) do
- local gem_package, version = line:match "^(%S+) %((%S+)%)$"
- if gem_package and version then
- gem_versions[gem_package] = version
- end
- end
- return gem_versions
-end
-
----@param server Server
----@param source InstallReceiptSource
----@param on_check_complete fun(result: VersionCheckResult)
-local function gem_checker(server, source, on_check_complete)
- local stdio = process.in_memory_sink()
- process.spawn(
- "gem",
- {
- args = { "outdated" },
- cwd = server.root_dir,
- stdio_sink = stdio.sink,
- env = process.graft_env(gem.env(server.root_dir)),
- },
- vim.schedule_wrap(function(success)
- if not success then
- return on_check_complete(VersionCheckResult.fail(server))
- end
- ---@type string[]
- local lines = vim.split(table.concat(stdio.buffers.stdout, ""), "\n")
- log.trace("Gem outdated lines output", lines)
- local outdated_gems = vim.tbl_map(parse_outdated_gem, vim.tbl_filter(not_empty, lines))
- log.trace("Gem outdated packages", outdated_gems)
-
- ---@type OutdatedPackage[]
- local outdated_packages = {}
-
- for _, outdated_gem in ipairs(outdated_gems) do
- if
- outdated_gem.name == source.package
- and outdated_gem.current_version ~= outdated_gem.latest_version
- then
- table.insert(outdated_packages, {
- name = outdated_gem.name,
- current_version = outdated_gem.current_version,
- latest_version = outdated_gem.latest_version,
- })
- end
- end
-
- on_check_complete(VersionCheckResult.success(server, outdated_packages))
- end)
- )
-end
-
--- to allow tests to access internals
-return setmetatable({
- parse_outdated_gem = parse_outdated_gem,
- parse_gem_list_output = parse_gem_list_output,
-}, {
- __call = function(_, ...)
- return gem_checker(...)
- end,
-})
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/git.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/git.lua
deleted file mode 100644
index 74007abc..00000000
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/git.lua
+++ /dev/null
@@ -1,42 +0,0 @@
-local process = require "nvim-lsp-installer.process"
-local VersionCheckResult = require "nvim-lsp-installer.jobs.outdated-servers.version-check-result"
-
----@param server Server
----@param source InstallReceiptSource
----@param on_check_complete fun(result: VersionCheckResult)
-return function(server, source, on_check_complete)
- process.spawn("git", {
- -- We assume git installation track the remote HEAD branch
- args = { "fetch", "origin", "HEAD" },
- cwd = server.root_dir,
- stdio_sink = process.empty_sink(),
- }, function(fetch_success)
- local stdio = process.in_memory_sink()
- if not fetch_success then
- return on_check_complete(VersionCheckResult.fail(server))
- end
- process.spawn("git", {
- args = { "rev-parse", "FETCH_HEAD", "HEAD" },
- cwd = server.root_dir,
- stdio_sink = stdio.sink,
- }, function(success)
- if success then
- local stdout = table.concat(stdio.buffers.stdout, "")
- local remote_head, local_head = unpack(vim.split(stdout, "\n"))
- if remote_head ~= local_head then
- on_check_complete(VersionCheckResult.success(server, {
- {
- name = source.remote,
- latest_version = remote_head,
- current_version = local_head,
- },
- }))
- else
- on_check_complete(VersionCheckResult.empty(server))
- end
- else
- on_check_complete(VersionCheckResult.fail(server))
- end
- end)
- end)
-end
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/github_release_file.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/github_release_file.lua
index 37e30798..527a37ff 100644
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/github_release_file.lua
+++ b/lua/nvim-lsp-installer/jobs/outdated-servers/github_release_file.lua
@@ -1,29 +1,22 @@
+local a = require "nvim-lsp-installer.core.async"
+local Result = require "nvim-lsp-installer.core.result"
local github = require "nvim-lsp-installer.core.clients.github"
-local VersionCheckResult = require "nvim-lsp-installer.jobs.outdated-servers.version-check-result"
----@param server Server
----@param source InstallReceiptSource
----@param on_result fun(result: VersionCheckResult)
-return function(server, source, on_result)
- github.fetch_latest_release(
- source.repo,
- { tag_name_pattern = source.tag_name_pattern },
- function(err, latest_release)
- if err then
- return on_result(VersionCheckResult.fail(server))
- end
+local fetch_latest_release = a.promisify(github.fetch_latest_release, true)
- if source.release ~= latest_release.tag_name then
- return on_result(VersionCheckResult.success(server, {
- {
- name = source.repo,
- current_version = source.release,
- latest_version = latest_release.tag_name,
- },
- }))
- else
- return on_result(VersionCheckResult.empty(server))
- end
+---@async
+---@param receipt InstallReceipt
+return function(receipt)
+ local source = receipt.primary_source
+ return Result.run_catching(function()
+ local latest_release = fetch_latest_release(source.repo, { tag_name_pattern = source.tag_name_pattern })
+ if source.release ~= latest_release.tag_name then
+ return {
+ name = source.repo,
+ current_version = source.release,
+ latest_version = latest_release.tag_name,
+ }
end
- )
+ error "Primary package is not outdated."
+ end)
end
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/github_tag.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/github_tag.lua
index 6f45b4ba..f9df2438 100644
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/github_tag.lua
+++ b/lua/nvim-lsp-installer/jobs/outdated-servers/github_tag.lua
@@ -1,25 +1,23 @@
+local a = require "nvim-lsp-installer.core.async"
+local Result = require "nvim-lsp-installer.core.result"
local github = require "nvim-lsp-installer.core.clients.github"
-local VersionCheckResult = require "nvim-lsp-installer.jobs.outdated-servers.version-check-result"
----@param server Server
----@param source InstallReceiptSource
----@param on_result fun(result: VersionCheckResult)
-return function(server, source, on_result)
- github.fetch_latest_tag(source.repo, function(err, latest_tag)
- if err then
- return on_result(VersionCheckResult.fail(server))
- end
+local fetch_latest_tag = a.promisify(github.fetch_latest_tag, true)
+
+---@async
+---@param receipt InstallReceipt
+return function(receipt)
+ local source = receipt.primary_source
+ return Result.run_catching(function()
+ local latest_tag = fetch_latest_tag(source.repo)
if source.tag ~= latest_tag.name then
- return on_result(VersionCheckResult.success(server, {
- {
- name = source.repo,
- current_version = source.tag,
- latest_version = latest_tag.name,
- },
- }))
- else
- return on_result(VersionCheckResult.empty(server))
+ return {
+ name = source.repo,
+ current_version = source.tag,
+ latest_version = latest_tag.name,
+ }
end
+ error "Primary package is not outdated."
end)
end
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/init.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/init.lua
index b892e847..e50ca9fd 100644
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/init.lua
+++ b/lua/nvim-lsp-installer/jobs/outdated-servers/init.lua
@@ -1,16 +1,18 @@
+local a = require "nvim-lsp-installer.core.async"
local JobExecutionPool = require "nvim-lsp-installer.jobs.pool"
local VersionCheckResult = require "nvim-lsp-installer.jobs.outdated-servers.version-check-result"
local log = require "nvim-lsp-installer.log"
-local npm_check = require "nvim-lsp-installer.jobs.outdated-servers.npm"
-local cargo_check = require "nvim-lsp-installer.jobs.outdated-servers.cargo"
-local pip3_check = require "nvim-lsp-installer.jobs.outdated-servers.pip3"
-local gem_check = require "nvim-lsp-installer.jobs.outdated-servers.gem"
-local git_check = require "nvim-lsp-installer.jobs.outdated-servers.git"
+local npm = require "nvim-lsp-installer.core.managers.npm"
+local pip3 = require "nvim-lsp-installer.core.managers.pip3"
+local git = require "nvim-lsp-installer.core.managers.git"
+local gem = require "nvim-lsp-installer.core.managers.gem"
+local go = require "nvim-lsp-installer.core.managers.go"
+local cargo = require "nvim-lsp-installer.core.managers.cargo"
+local composer = require "nvim-lsp-installer.core.managers.composer"
local github_release_file_check = require "nvim-lsp-installer.jobs.outdated-servers.github_release_file"
local github_tag_check = require "nvim-lsp-installer.jobs.outdated-servers.github_tag"
-local jdtls = require "nvim-lsp-installer.jobs.outdated-servers.jdtls"
-local composer_check = require "nvim-lsp-installer.jobs.outdated-servers.composer"
+local jdtls_check = require "nvim-lsp-installer.jobs.outdated-servers.jdtls"
local M = {}
@@ -18,27 +20,18 @@ local jobpool = JobExecutionPool:new {
size = 4,
}
-local function noop(server, _, on_result)
- on_result(VersionCheckResult.empty(server))
-end
-
----@type Record<InstallReceiptSourceType, function>
+---@type Record<InstallReceiptSourceType, async fun(receipt: InstallReceipt, install_dir: string): Result>
local checkers = {
- ["npm"] = npm_check,
- ["pip3"] = pip3_check,
- ["cargo"] = cargo_check,
- ["gem"] = gem_check,
- ["composer"] = composer_check,
- ["go"] = noop, -- TODO
- ["dotnet"] = noop, -- TODO
- ["r_package"] = noop, -- TODO
- ["unmanaged"] = noop,
- ["system"] = noop,
- ["jdtls"] = jdtls,
- ["git"] = git_check,
+ ["npm"] = npm.check_outdated_primary_package,
+ ["pip3"] = pip3.check_outdated_primary_package,
+ ["git"] = git.check_outdated_git_clone,
+ ["cargo"] = cargo.check_outdated_primary_package,
+ ["composer"] = composer.check_outdated_primary_package,
+ ["gem"] = gem.check_outdated_primary_package,
+ ["go"] = go.check_outdated_primary_package,
+ ["jdtls"] = jdtls_check,
["github_release_file"] = github_release_file_check,
["github_tag"] = github_tag_check,
- ["opam"] = noop,
}
local pending_servers = {}
@@ -73,7 +66,13 @@ function M.identify_outdated_servers(servers, on_result)
local checker = checkers[receipt.primary_source.type]
if checker then
- checker(server, receipt.primary_source, complete)
+ a.run(checker, function(success, result)
+ if success and result:is_success() then
+ complete(VersionCheckResult.success(server, { result:get_or_nil() }))
+ else
+ complete(VersionCheckResult.fail(server))
+ end
+ end, receipt, server.root_dir)
else
complete(VersionCheckResult.empty(server))
log.fmt_error("Unable to find checker for source=%s", receipt.primary_source.type)
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/jdtls.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/jdtls.lua
index bb40714a..3b10f42a 100644
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/jdtls.lua
+++ b/lua/nvim-lsp-installer/jobs/outdated-servers/jdtls.lua
@@ -1,24 +1,21 @@
+local a = require "nvim-lsp-installer.core.async"
+local Result = require "nvim-lsp-installer.core.result"
local eclipse = require "nvim-lsp-installer.core.clients.eclipse"
-local VersionCheckResult = require "nvim-lsp-installer.jobs.outdated-servers.version-check-result"
----@param server Server
----@param source InstallReceiptSource
----@param on_check_result fun(result: VersionCheckResult)
-return function(server, source, on_check_result)
- eclipse.fetch_latest_jdtls_version(function(err, latest_version)
- if err then
- return on_check_result(VersionCheckResult.fail(server))
- end
- if source.version ~= latest_version then
- return on_check_result(VersionCheckResult.success(server, {
- {
- name = "jdtls",
- current_version = source.version,
- latest_version = latest_version,
- },
- }))
- else
- return on_check_result(VersionCheckResult.empty(server))
+local fetch_latest_jdtls_version = a.promisify(eclipse.fetch_latest_jdtls_version, true)
+
+---@async
+---@param receipt InstallReceipt
+return function(receipt)
+ return Result.run_catching(function()
+ local latest_version = fetch_latest_jdtls_version()
+ if receipt.primary_source.version ~= latest_version then
+ return {
+ name = "jdtls",
+ current_version = receipt.primary_source.version,
+ latest_version = latest_version,
+ }
end
+ error "Primary package is not outdated."
end)
end
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/npm.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/npm.lua
deleted file mode 100644
index bad76e81..00000000
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/npm.lua
+++ /dev/null
@@ -1,45 +0,0 @@
-local process = require "nvim-lsp-installer.process"
-local npm = require "nvim-lsp-installer.installers.npm"
-local log = require "nvim-lsp-installer.log"
-local VersionCheckResult = require "nvim-lsp-installer.jobs.outdated-servers.version-check-result"
-
----@param server Server
----@param source InstallReceiptSource
----@param on_check_complete fun(result: VersionCheckResult)
-return function(server, source, on_check_complete)
- local stdio = process.in_memory_sink()
- process.spawn(
- npm.npm_command,
- {
- args = vim.list_extend({ "outdated", "--json" }, { source.package }),
- cwd = server.root_dir,
- stdio_sink = stdio.sink,
- },
- -- Note that `npm outdated` exits with code 1 if it finds outdated packages
- vim.schedule_wrap(function()
- ---@alias NpmOutdatedPackage {current: string, wanted: string, latest: string, dependent: string, location: string}
- ---@type table<string, NpmOutdatedPackage>
- local ok, data = pcall(vim.json.decode, table.concat(stdio.buffers.stdout, ""))
-
- if not ok then
- log.fmt_error("Failed to parse npm outdated --json output. %s", data)
- return on_check_complete(VersionCheckResult.fail(server))
- end
-
- ---@type OutdatedPackage[]
- local outdated_packages = {}
-
- for package, outdated_package in pairs(data) do
- if outdated_package.current ~= outdated_package.latest then
- table.insert(outdated_packages, {
- name = package,
- current_version = outdated_package.current,
- latest_version = outdated_package.latest,
- })
- end
- end
-
- on_check_complete(VersionCheckResult.success(server, outdated_packages))
- end)
- )
-end
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/pip3.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/pip3.lua
deleted file mode 100644
index 9534617a..00000000
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/pip3.lua
+++ /dev/null
@@ -1,72 +0,0 @@
-local process = require "nvim-lsp-installer.process"
-local pip3 = require "nvim-lsp-installer.installers.pip3"
-local VersionCheckResult = require "nvim-lsp-installer.jobs.outdated-servers.version-check-result"
-local log = require "nvim-lsp-installer.log"
-
----@param package string
----@return string
-local function normalize_package(package)
- -- https://stackoverflow.com/a/60307740
- local s = package:gsub("%[.*%]", "")
- return s
-end
-
----@param server Server
----@param source InstallReceiptSource
----@param on_check_complete fun(result: VersionCheckResult)
-local function pip3_check(server, source, on_check_complete)
- local normalized_package = normalize_package(source.package)
- log.fmt_trace("Normalized package from %s to %s.", source.package, normalized_package)
- local stdio = process.in_memory_sink()
- process.spawn(
- "python",
- {
- args = { "-m", "pip", "list", "--outdated", "--format=json" },
- cwd = server.root_dir,
- stdio_sink = stdio.sink,
- env = process.graft_env(pip3.env(server.root_dir)),
- },
- vim.schedule_wrap(function(success)
- if not success then
- return on_check_complete(VersionCheckResult.fail(server))
- end
- ---@alias PipOutdatedPackage {name: string, version: string, latest_version: string}
- ---@type PipOutdatedPackage[]
- local ok, packages = pcall(vim.json.decode, table.concat(stdio.buffers.stdout, ""))
-
- if not ok then
- log.fmt_error("Failed to parse pip3 output. %s", packages)
- return on_check_complete(VersionCheckResult.fail(server))
- end
-
- log.trace("Outdated packages", packages)
-
- ---@type OutdatedPackage[]
- local outdated_packages = {}
-
- for _, outdated_package in ipairs(packages) do
- if
- outdated_package.name == normalized_package
- and outdated_package.version ~= outdated_package.latest_version
- then
- table.insert(outdated_packages, {
- name = outdated_package.name,
- current_version = outdated_package.version,
- latest_version = outdated_package.latest_version,
- })
- end
- end
-
- on_check_complete(VersionCheckResult.success(server, outdated_packages))
- end)
- )
-end
-
--- to allow tests to access internals
-return setmetatable({
- normalize_package = normalize_package,
-}, {
- __call = function(_, ...)
- return pip3_check(...)
- end,
-})
diff --git a/lua/nvim-lsp-installer/jobs/version-check/init.lua b/lua/nvim-lsp-installer/jobs/version-check/init.lua
index da33ea9d..ef36fefa 100644
--- a/lua/nvim-lsp-installer/jobs/version-check/init.lua
+++ b/lua/nvim-lsp-installer/jobs/version-check/init.lua
@@ -1,12 +1,11 @@
-local a = require "nvim-lsp-installer.core.async"
local Result = require "nvim-lsp-installer.core.result"
-local process = require "nvim-lsp-installer.process"
-local pip3 = require "nvim-lsp-installer.installers.pip3"
-local gem = require "nvim-lsp-installer.installers.gem"
-local cargo_check = require "nvim-lsp-installer.jobs.outdated-servers.cargo"
-local gem_check = require "nvim-lsp-installer.jobs.outdated-servers.gem"
-local pip3_check = require "nvim-lsp-installer.jobs.outdated-servers.pip3"
-local spawn = require "nvim-lsp-installer.core.async.spawn"
+local npm = require "nvim-lsp-installer.core.managers.npm"
+local cargo = require "nvim-lsp-installer.core.managers.cargo"
+local pip3 = require "nvim-lsp-installer.core.managers.pip3"
+local gem = require "nvim-lsp-installer.core.managers.gem"
+local go = require "nvim-lsp-installer.core.managers.go"
+local git = require "nvim-lsp-installer.core.managers.git"
+local composer = require "nvim-lsp-installer.core.managers.composer"
local M = {}
@@ -26,89 +25,35 @@ local function noop()
return Result.failure "Unable to detect version."
end
----@type Record<InstallReceiptSourceType, fun(server: Server, receipt: InstallReceipt): Result>
+---@type Record<InstallReceiptSourceType, async fun(server: Server, receipt: InstallReceipt): Result>
local version_checker = {
["npm"] = function(server, receipt)
- return spawn.npm({
- "ls",
- "--json",
- cwd = server.root_dir,
- }):map_catching(function(result)
- local npm_packages = vim.json.decode(result.stdout)
- return npm_packages.dependencies[receipt.primary_source.package].version
- end)
+ return npm.get_installed_primary_package_version(receipt, server.root_dir)
end,
["pip3"] = function(server, receipt)
- return spawn.python3({
- "-m",
- "pip",
- "list",
- "--format",
- "json",
- cwd = server.root_dir,
- env = process.graft_env(pip3.env(server.root_dir)),
- }):map_catching(function(result)
- local pip_packages = vim.json.decode(result.stdout)
- local normalized_pip_package = pip3_check.normalize_package(receipt.primary_source.package)
- for _, pip_package in ipairs(pip_packages) do
- if pip_package.name == normalized_pip_package then
- return pip_package.version
- end
- end
- error "Unable to find pip package."
- end)
+ return pip3.get_installed_primary_package_version(receipt, server.root_dir)
end,
["gem"] = function(server, receipt)
- return spawn.gem({
- "list",
- cwd = server.root_dir,
- env = process.graft_env(gem.env(server.root_dir)),
- }):map_catching(function(result)
- local gems = gem_check.parse_gem_list_output(result.stdout)
- if gems[receipt.primary_source.package] then
- return gems[receipt.primary_source.package]
- else
- error "Failed to find gem package version."
- end
- end)
+ return gem.get_installed_primary_package_version(receipt, server.root_dir)
end,
["cargo"] = function(server, receipt)
- return spawn.cargo({
- "install",
- "--list",
- "--root",
- server.root_dir,
- cwd = server.root_dir,
- }):map_catching(function(result)
- local crates = cargo_check.parse_installed_crates(result.stdout)
- a.scheduler() -- needed because vim.fn.* call
- local package = vim.fn.fnamemodify(receipt.primary_source.package, ":t")
- if crates[package] then
- return crates[package]
- else
- error "Failed to find cargo package version."
- end
- end)
+ return cargo.get_installed_primary_package_version(receipt, server.root_dir)
+ end,
+ ["composer"] = function(server, receipt)
+ return composer.get_installed_primary_package_version(receipt, server.root_dir)
end,
["git"] = function(server)
- return spawn.git({
- "rev-parse",
- "--short",
- "HEAD",
- cwd = server.root_dir,
- }):map_catching(function(result)
- return vim.trim(result.stdout)
- end)
+ return git.get_installed_revision(server.root_dir)
+ end,
+ ["go"] = function(server, receipt)
+ return go.get_installed_primary_package_version(receipt, server.root_dir)
end,
- ["opam"] = noop,
- ["dotnet"] = noop,
- ["r_package"] = noop,
["github_release_file"] = version_in_receipt "release",
["github_tag"] = version_in_receipt "tag",
["jdtls"] = version_in_receipt "version",
}
---- Async function.
+---@async
---@param server Server
---@return Result
function M.check_server_version(server)
diff --git a/lua/nvim-lsp-installer/path.lua b/lua/nvim-lsp-installer/path.lua
index 5d3fcee4..2f0fd84b 100644
--- a/lua/nvim-lsp-installer/path.lua
+++ b/lua/nvim-lsp-installer/path.lua
@@ -27,6 +27,8 @@ function M.concat(path_components)
return table.concat(path_components, sep)
end
+---@path root_path string
+---@path path string
function M.is_subdirectory(root_path, path)
return root_path == path or path:sub(1, #root_path + 1) == root_path .. sep
end