diff options
| author | William Boman <william@redwill.se> | 2022-03-05 22:42:25 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-03-05 22:42:25 +0100 |
| commit | f9299bb59c9e42d59fc57ed034fb84bdd23bbd77 (patch) | |
| tree | 0ac6e701d7fa206ce5e05b4daed2238ab1eaf6c0 | |
| parent | async: raise errors instead of returning pcall-style (#521) (diff) | |
| download | mason-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)
| -rw-r--r-- | lua/nvim-lsp-installer/core/async/spawn.lua | 36 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/core/result.lua | 49 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/jobs/outdated-servers/gem.lua | 14 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/jobs/version-check/init.lua | 131 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/process.lua | 2 | ||||
| -rw-r--r-- | lua/nvim-lsp-installer/ui/status-win/init.lua | 68 | ||||
| -rw-r--r-- | tests/jobs/outdated-servers/gem_spec.lua | 17 | ||||
| -rw-r--r-- | tests/server_spec.lua | 5 |
8 files changed, 294 insertions, 28 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) diff --git a/tests/jobs/outdated-servers/gem_spec.lua b/tests/jobs/outdated-servers/gem_spec.lua index 6367d7be..eb2ce086 100644 --- a/tests/jobs/outdated-servers/gem_spec.lua +++ b/tests/jobs/outdated-servers/gem_spec.lua @@ -25,4 +25,21 @@ describe("gem outdated package checker", function() assert.is_nil(gem_check.parse_outdated_gem "a whole bunch of gibberish!") assert.is_nil(gem_check.parse_outdated_gem "") end) + + it("should parse gem list output", function() + assert.equals( + vim.inspect { + ["solargraph"] = "0.44.3", + ["unicode-display_width"] = "2.1.0", + }, + vim.inspect(gem_check.parse_gem_list_output [[ + +*** LOCAL GEMS *** + +nokogiri (1.13.3 arm64-darwin) +solargraph (0.44.3) +unicode-display_width (2.1.0) +]]) + ) + end) end) diff --git a/tests/server_spec.lua b/tests/server_spec.lua index 5204fb69..744b4cf8 100644 --- a/tests/server_spec.lua +++ b/tests/server_spec.lua @@ -1,12 +1,7 @@ local spy = require "luassert.spy" -local match = require "luassert.match" local lsp_installer = require "nvim-lsp-installer" local server = require "nvim-lsp-installer.server" local a = require "nvim-lsp-installer.core.async" -local std = require "nvim-lsp-installer.installers.std" -local fs = require "nvim-lsp-installer.fs" -local path = require "nvim-lsp-installer.path" -local settings = require "nvim-lsp-installer.settings" describe("server", function() it( |
