aboutsummaryrefslogtreecommitdiffstats
path: root/lua/nvim-lsp-installer
diff options
context:
space:
mode:
authorWilliam Boman <william@redwill.se>2022-03-05 22:42:25 +0100
committerGitHub <noreply@github.com>2022-03-05 22:42:25 +0100
commitf9299bb59c9e42d59fc57ed034fb84bdd23bbd77 (patch)
tree0ac6e701d7fa206ce5e05b4daed2238ab1eaf6c0 /lua/nvim-lsp-installer
parentasync: raise errors instead of returning pcall-style (#521) (diff)
downloadmason-f9299bb59c9e42d59fc57ed034fb84bdd23bbd77.tar
mason-f9299bb59c9e42d59fc57ed034fb84bdd23bbd77.tar.gz
mason-f9299bb59c9e42d59fc57ed034fb84bdd23bbd77.tar.bz2
mason-f9299bb59c9e42d59fc57ed034fb84bdd23bbd77.tar.lz
mason-f9299bb59c9e42d59fc57ed034fb84bdd23bbd77.tar.xz
mason-f9299bb59c9e42d59fc57ed034fb84bdd23bbd77.tar.zst
mason-f9299bb59c9e42d59fc57ed034fb84bdd23bbd77.zip
feat(ui): display installed server version (#520)
Diffstat (limited to 'lua/nvim-lsp-installer')
-rw-r--r--lua/nvim-lsp-installer/core/async/spawn.lua36
-rw-r--r--lua/nvim-lsp-installer/core/result.lua49
-rw-r--r--lua/nvim-lsp-installer/jobs/outdated-servers/gem.lua14
-rw-r--r--lua/nvim-lsp-installer/jobs/version-check/init.lua131
-rw-r--r--lua/nvim-lsp-installer/process.lua2
-rw-r--r--lua/nvim-lsp-installer/ui/status-win/init.lua68
6 files changed, 277 insertions, 23 deletions
diff --git a/lua/nvim-lsp-installer/core/async/spawn.lua b/lua/nvim-lsp-installer/core/async/spawn.lua
new file mode 100644
index 00000000..e643f098
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/async/spawn.lua
@@ -0,0 +1,36 @@
+local a = require "nvim-lsp-installer.core.async"
+local process = require "nvim-lsp-installer.process"
+local platform = require "nvim-lsp-installer.platform"
+
+local async_spawn = a.promisify(process.spawn)
+
+local spawn = {
+ aliases = {
+ npm = platform.is_win and "npm.cmd" or "npm",
+ },
+}
+
+setmetatable(spawn, {
+ __index = function(self, k)
+ return function(args)
+ local stdio = process.in_memory_sink()
+ local cmd_args = {}
+ for i, arg in ipairs(args) do
+ cmd_args[i] = arg
+ end
+ ---@type JobSpawnOpts
+ local spawn_args = {
+ stdio_sink = stdio.sink,
+ cwd = args.cwd,
+ env = args.env,
+ args = cmd_args,
+ }
+ local cmd = self.aliases[k] or k
+ local _, exit_code = async_spawn(cmd, spawn_args)
+ assert(exit_code == 0, ("%q exited with an error code: %d."):format(cmd, exit_code))
+ return table.concat(stdio.buffers.stdout, ""), table.concat(stdio.buffers.stderr, "")
+ end
+ end,
+})
+
+return spawn
diff --git a/lua/nvim-lsp-installer/core/result.lua b/lua/nvim-lsp-installer/core/result.lua
new file mode 100644
index 00000000..33623c66
--- /dev/null
+++ b/lua/nvim-lsp-installer/core/result.lua
@@ -0,0 +1,49 @@
+---@class Failure
+---@field error any
+local Failure = {}
+Failure.__index = Failure
+
+function Failure.new(error)
+ return setmetatable({ error = error }, Failure)
+end
+
+---@class Result
+---@field value any
+local Result = {}
+Result.__index = Result
+
+function Result.new(value)
+ return setmetatable({
+ value = value,
+ }, Result)
+end
+
+function Result.success(value)
+ return Result.new(value)
+end
+
+function Result.failure(error)
+ return Result.new(Failure.new(error))
+end
+
+function Result:get_or_nil()
+ if self:is_success() then
+ return self.value
+ end
+end
+
+function Result:err_or_nil()
+ if self:is_failure() then
+ return self.value.error
+ end
+end
+
+function Result:is_failure()
+ return getmetatable(self.value) == Failure
+end
+
+function Result:is_success()
+ return getmetatable(self.value) ~= Failure
+end
+
+return Result
diff --git a/lua/nvim-lsp-installer/jobs/outdated-servers/gem.lua b/lua/nvim-lsp-installer/jobs/outdated-servers/gem.lua
index 909bf7c3..cf880fd0 100644
--- a/lua/nvim-lsp-installer/jobs/outdated-servers/gem.lua
+++ b/lua/nvim-lsp-installer/jobs/outdated-servers/gem.lua
@@ -27,6 +27,19 @@ local function parse_outdated_gem(outdated_gem)
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)
@@ -74,6 +87,7 @@ 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(...)
diff --git a/lua/nvim-lsp-installer/jobs/version-check/init.lua b/lua/nvim-lsp-installer/jobs/version-check/init.lua
new file mode 100644
index 00000000..11df3721
--- /dev/null
+++ b/lua/nvim-lsp-installer/jobs/version-check/init.lua
@@ -0,0 +1,131 @@
+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 spawn = require "nvim-lsp-installer.core.async.spawn"
+
+local M = {}
+
+local ServerVersion = {}
+ServerVersion.__index = ServerVersion
+
+---@param field_name string
+local function version_in_receipt(field_name)
+ ---@param receipt InstallReceipt
+ ---@return Result
+ return function(_, receipt)
+ return Result.success(receipt.primary_source[field_name])
+ end
+end
+
+---@param package string
+---@return string
+---@TODO DRY
+local function normalize_pip3_package(package)
+ -- https://stackoverflow.com/a/60307740
+ local s = package:gsub("%[.*%]", "")
+ return s
+end
+
+local function noop()
+ return Result.failure "Unable to detect version."
+end
+
+---@type Record<InstallReceiptSourceType, fun(server: Server, receipt: InstallReceipt): Result>
+local version_checker = {
+ ["npm"] = function(server, receipt)
+ local stdout = spawn.npm {
+ "ls",
+ "--json",
+ cwd = server.root_dir,
+ }
+ local npm_packages = vim.json.decode(stdout)
+ return Result.success(npm_packages.dependencies[receipt.primary_source.package].version)
+ end,
+ ["pip3"] = function(server, receipt)
+ local stdout = spawn.python3 {
+ "-m",
+ "pip",
+ "list",
+ "--format",
+ "json",
+ cwd = server.root_dir,
+ env = process.graft_env(pip3.env(server.root_dir)),
+ }
+ local pip_packages = vim.json.decode(stdout)
+ local normalized_pip_package = normalize_pip3_package(receipt.primary_source.package)
+ for _, pip_package in ipairs(pip_packages) do
+ if pip_package.name == normalized_pip_package then
+ return Result.success(pip_package.version)
+ end
+ end
+ return Result.failure "Failed to find pip package version."
+ end,
+ ["gem"] = function(server, receipt)
+ local stdout = spawn.gem {
+ "list",
+ cwd = server.root_dir,
+ env = process.graft_env(gem.env(server.root_dir)),
+ }
+ local gems = gem_check.parse_gem_list_output(stdout)
+ if gems[receipt.primary_source.package] then
+ return Result.success(gems[receipt.primary_source.package])
+ else
+ return Result.failure "Failed to find gem package version."
+ end
+ end,
+ ["cargo"] = function(server, receipt)
+ local stdout = spawn.cargo {
+ "install",
+ "--list",
+ "--root",
+ server.root_dir,
+ cwd = server.root_dir,
+ }
+ local crates = cargo_check.parse_installed_crates(stdout)
+ a.scheduler() -- needed because vim.fn.* call
+ local package = vim.fn.fnamemodify(receipt.primary_source.package, ":t")
+ if crates[package] then
+ return Result.success(crates[package])
+ else
+ return Result.failure "Failed to find cargo package version."
+ end
+ end,
+ ["git"] = function(server)
+ local stdout = spawn.git {
+ "rev-parse",
+ "--short",
+ "HEAD",
+ cwd = server.root_dir,
+ }
+ return Result.success(vim.trim(stdout))
+ 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.
+---@param server Server
+---@return Result
+function M.check_server_version(server)
+ local receipt = server:get_receipt()
+ if not receipt then
+ return Result.failure "Unable to retrieve installation receipt."
+ end
+ local version_check = version_checker[receipt.primary_source.type] or noop
+ local ok, result = pcall(version_check, server, receipt)
+ if ok then
+ return result
+ else
+ return Result.failure(result)
+ end
+end
+
+return M
diff --git a/lua/nvim-lsp-installer/process.lua b/lua/nvim-lsp-installer/process.lua
index 07d51c32..4c5bb11c 100644
--- a/lua/nvim-lsp-installer/process.lua
+++ b/lua/nvim-lsp-installer/process.lua
@@ -86,7 +86,7 @@ local function sanitize_env_list(env_list)
return sanitized_list
end
----@alias JobSpawnCallback fun(success: boolean)
+---@alias JobSpawnCallback fun(success: boolean, exit_code: integer)
---@class JobSpawnOpts
---@field env string[] @List of "key=value" string.
diff --git a/lua/nvim-lsp-installer/ui/status-win/init.lua b/lua/nvim-lsp-installer/ui/status-win/init.lua
index 3b0ec33a..beb788ed 100644
--- a/lua/nvim-lsp-installer/ui/status-win/init.lua
+++ b/lua/nvim-lsp-installer/ui/status-win/init.lua
@@ -1,3 +1,4 @@
+local a = require "nvim-lsp-installer.core.async"
local Ui = require "nvim-lsp-installer.ui"
local fs = require "nvim-lsp-installer.fs"
local log = require "nvim-lsp-installer.log"
@@ -6,7 +7,8 @@ local display = require "nvim-lsp-installer.ui.display"
local settings = require "nvim-lsp-installer.settings"
local lsp_servers = require "nvim-lsp-installer.servers"
local JobExecutionPool = require "nvim-lsp-installer.jobs.pool"
-local jobs = require "nvim-lsp-installer.jobs.outdated-servers"
+local outdated_servers = require "nvim-lsp-installer.jobs.outdated-servers"
+local version_check = require "nvim-lsp-installer.jobs.version-check"
local ServerHints = require "nvim-lsp-installer.ui.status-win.server_hints"
local ServerSettingsSchema = require "nvim-lsp-installer.ui.status-win.components.settings-schema"
@@ -173,15 +175,11 @@ end
---@return string
local function format_new_package_versions(outdated_packages)
local result = {}
+ if #outdated_packages == 1 then
+ return outdated_packages[1].latest_version
+ end
for _, outdated_package in ipairs(outdated_packages) do
- table.insert(
- result,
- ("%s@%s → %s"):format(
- outdated_package.name,
- outdated_package.current_version,
- outdated_package.latest_version
- )
- )
+ result[#result + 1] = ("%s@%s"):format(outdated_package.name, outdated_package.latest_version)
end
return table.concat(result, ", ")
end
@@ -207,10 +205,22 @@ local function ServerMetadata(server)
))
end),
Ui.Table(Data.list_not_nil(
+ Data.lazy(server.is_installed, function()
+ return {
+ { "version", "LspInstallerMuted" },
+ server.installed_version_err and {
+ "Unable to detect version.",
+ "LspInstallerMuted",
+ } or { server.installed_version or "Loading...", "" },
+ }
+ end),
Data.lazy(#server.metadata.outdated_packages > 0, function()
return {
- { "new version", "LspInstallerMuted" },
- { format_new_package_versions(server.metadata.outdated_packages), "LspInstallerGreen" },
+ { "latest version", "LspInstallerGreen" },
+ {
+ format_new_package_versions(server.metadata.outdated_packages),
+ "LspInstallerGreen",
+ },
}
end),
Data.lazy(server.metadata.install_timestamp_seconds, function()
@@ -219,10 +229,10 @@ local function ServerMetadata(server)
{ format_time(server.metadata.install_timestamp_seconds), "" },
}
end),
- {
+ Data.when(not server.is_installed, {
{ "filetypes", "LspInstallerMuted" },
{ server.metadata.filetypes, "" },
- },
+ }),
Data.when(server.is_installed, {
{ "path", "LspInstallerMuted" },
{ server.metadata.install_dir, "String" },
@@ -276,7 +286,10 @@ local function InstalledServers(servers, props)
{ " " .. server.name .. " ", "" },
{ server.hints, "Comment" },
Data.when(server.deprecated, { " deprecated", "LspInstallerOrange" }),
- Data.when(#server.metadata.outdated_packages > 0, { " new version available", "Comment" })
+ Data.when(
+ #server.metadata.outdated_packages > 0 and not is_expanded,
+ { " new version available", "LspInstallerGreen" }
+ )
),
},
Ui.Keybind(settings.current.ui.keymaps.toggle_server_expand, "EXPAND_SERVER", { server.name }),
@@ -513,6 +526,8 @@ local function create_initial_server_state(server)
hints = tostring(ServerHints.new(server)),
expanded_schema_properties = {},
has_expanded_schema = false,
+ installed_version = nil, -- lazy
+ installed_version_err = nil, -- lazy
---@type table
schema = nil, -- lazy
metadata = {
@@ -606,9 +621,8 @@ local function init(all_servers)
---@type fun(): StatusWinState
local get_state = get_state_generic
- -- TODO: memoize or throttle.. or cache. Do something. Also, as opposed to what the naming currently suggests, this
- -- is not really doing anything async stuff, but will very likely do so in the future :tm:.
- local async_populate_server_metadata = vim.schedule_wrap(function(server_name)
+ local async_populate_server_metadata = a.scope(function(server_name)
+ a.scheduler()
local ok, server = lsp_servers.get_server(server_name)
if not ok then
return log.warn("Unable to get server when populating metadata.", server_name)
@@ -618,7 +632,16 @@ local function init(all_servers)
if fstat_ok then
state.servers[server.name].metadata.install_timestamp_seconds = fstat.mtime.sec
end
- state.servers[server_name].schema = server:get_settings_schema()
+ state.servers[server.name].schema = server:get_settings_schema()
+ end)
+ local version = version_check.check_server_version(server)
+ mutate_state(function(state)
+ if version:is_success() then
+ state.servers[server.name].installed_version = version:get_or_nil()
+ state.servers[server.name].installed_version_err = nil
+ else
+ state.servers[server.name].installed_version_err = true
+ end
end)
end)
@@ -671,12 +694,13 @@ local function init(all_servers)
state.servers[server.name].installer.is_running = false
state.servers[server.name].installer.has_run = true
if not state.expanded_server then
+ -- Only automatically expand the server upon installation if none is already expanded, for UX reasons
expand_server(server.name)
+ elseif state.expanded_server == server.name then
+ -- Refresh server metadata
+ async_populate_server_metadata(server.name)
end
end)
- if not get_state().expanded_server then
- expand_server(server.name)
- end
on_complete()
end)
end
@@ -828,7 +852,7 @@ local function init(all_servers)
state.server_version_check_completed_percentage = 0
end)
end
- jobs.identify_outdated_servers(servers, function(check_result, progress)
+ outdated_servers.identify_outdated_servers(servers, function(check_result, progress)
mutate_state(function(state)
local completed_percentage = progress.completed / progress.total
state.server_version_check_completed_percentage = math.floor(completed_percentage * 100)