diff options
Diffstat (limited to 'lua/mason-registry/init.lua')
| -rw-r--r-- | lua/mason-registry/init.lua | 272 |
1 files changed, 100 insertions, 172 deletions
diff --git a/lua/mason-registry/init.lua b/lua/mason-registry/init.lua index 746e487b..5e4d124f 100644 --- a/lua/mason-registry/init.lua +++ b/lua/mason-registry/init.lua @@ -1,230 +1,158 @@ local EventEmitter = require "mason-core.EventEmitter" local InstallLocation = require "mason-core.installer.InstallLocation" -local Optional = require "mason-core.optional" -local _ = require "mason-core.functional" -local fs = require "mason-core.fs" local log = require "mason-core.log" -local path = require "mason-core.path" -local sources = require "mason-registry.sources" +local uv = vim.loop +local LazySourceCollection = require "mason-registry.sources" ----@class RegistrySource ----@field id string ----@field get_package fun(self: RegistrySource, pkg_name: string): Package? ----@field get_all_package_names fun(self: RegistrySource): string[] ----@field get_all_package_specs fun(self: RegistrySource): PackageSpec[] | RegistryPackageSpec[] ----@field get_display_name fun(self: RegistrySource): string ----@field is_installed fun(self: RegistrySource): boolean ----@field get_installer fun(self: RegistrySource): Optional # Optional<async fun (): Result> +-- singleton +local Registry = EventEmitter:new() ----@class MasonRegistry : EventEmitter ----@diagnostic disable-next-line: assign-type-mismatch -local M = setmetatable({}, { __index = EventEmitter }) -EventEmitter.init(M) - ----@type fun(location: InstallLocation): table<string, true> -local scan_install_root - -do - ---@type table<string, true>? - local cached_dirs - - local get_directories = _.compose( - _.set_of, - _.filter_map(function(entry) - if entry.type == "directory" then - return Optional.of(entry.name) - else - return Optional.empty() - end - end) - ) - - ---@param location InstallLocation - ---@return table<string, true> - scan_install_root = function(location) - if cached_dirs then - return cached_dirs - end - log.trace "Scanning installation root dir" - local ok, entries = pcall(fs.sync.readdir, location:package()) - if not ok then - log.debug("Failed to scan installation root dir", entries) - -- presume installation root dir has not been created yet (i.e., no packages installed) - return {} - end - cached_dirs = get_directories(entries) - vim.schedule(function() - cached_dirs = nil - end) - log.trace("Resolved installation root dirs", cached_dirs) - return cached_dirs - end -end +Registry.sources = LazySourceCollection:new() +---@type table<string, string[]> +Registry.aliases = {} ----Checks whether the provided package name is installed. ----In many situations, this is a more efficient option than the Package:is_installed() method due to a smaller amount of ----modules required to load. ----@param package_name string -function M.is_installed(package_name) - return scan_install_root(InstallLocation.global())[package_name] == true +---@param pkg_name string +function Registry.is_installed(pkg_name) + local ok, stat = pcall(uv.fs_stat, InstallLocation.global():package(pkg_name), "r", 438) + return ok and stat.type == "directory" end ----Returns an instance of the Package class if the provided package name exists. This function errors if a package cannot be found. ----@param package_name string +---Returns an instance of the Package class if the provided package name exists. This function errors if a package +---cannot be found. +---@param pkg_name string ---@return Package -function M.get_package(package_name) - for source in sources.iter() do - local pkg = source:get_package(package_name) +function Registry.get_package(pkg_name) + for source in Registry.sources:iterate() do + local pkg = source:get_package(pkg_name) if pkg then return pkg end end - log.fmt_error("Cannot find package %q.", package_name) - error(("Cannot find package %q."):format(package_name)) + log.fmt_error("Cannot find package %q.", pkg_name) + error(("Cannot find package %q."):format(pkg_name)) end ----Returns true if the provided package_name can be found in the registry. ----@param package_name string ----@return boolean -function M.has_package(package_name) - local ok = pcall(M.get_package, package_name) +function Registry.has_package(pkg_name) + local ok = pcall(Registry.get_package, pkg_name) return ok end -local get_packages = _.map(M.get_package) - ----Returns all installed package names. This is a fast function that doesn't load any extra modules. ----@return string[] -function M.get_installed_package_names() - return _.keys(scan_install_root(InstallLocation.global())) +function Registry.get_installed_package_names() + local fs = require "mason-core.fs" + if not fs.sync.dir_exists(InstallLocation.global():package()) then + return {} + end + local entries = fs.sync.readdir(InstallLocation:global():package()) + local directories = {} + for _, entry in ipairs(entries) do + if entry.type == "directory" then + directories[#directories + 1] = entry.name + end + end + -- TODO: validate that entry is a mason package + return directories end ----Returns all installed package instances. This is a slower function that loads more modules. ----@return Package[] -function M.get_installed_packages() - return get_packages(M.get_installed_package_names()) +function Registry.get_installed_packages() + return vim.tbl_map(Registry.get_package, Registry.get_installed_package_names()) end ----Returns all package names. This is a fast function that doesn't load any extra modules. ----@return string[] -function M.get_all_package_names() +function Registry.get_all_package_names() local pkgs = {} - for source in sources.iter() do + for source in Registry.sources:iterate() do for _, name in ipairs(source:get_all_package_names()) do pkgs[name] = true end end - return _.keys(pkgs) + return vim.tbl_keys(pkgs) end ----Returns all package instances. This is a slower function that loads more modules. ----@return Package[] -function M.get_all_packages() - return get_packages(M.get_all_package_names()) +function Registry.get_all_packages() + return vim.tbl_map(Registry.get_package, Registry.get_all_package_names()) end ----@return RegistryPackageSpec[] -function M.get_all_package_specs() +function Registry.get_all_package_specs() local specs = {} - for source in sources.iter() do - vim.list_extend(specs, source:get_all_package_specs()) + for source in Registry.sources:iterate() do + for _, spec in ipairs(source:get_all_package_specs()) do + if not specs[spec.name] then + specs[spec.name] = spec + end + end end - return _.uniq_by(_.prop "name", specs) + return vim.tbl_values(specs) end -local STATE_FILE = path.concat { - vim.fn.stdpath((vim.fn.has "nvim-0.8.0" == 1) and "state" or "cache"), - "mason-registry-update", -} - ----@param time integer -local function get_store_age(time) - local checksum = sources.checksum() - if fs.sync.file_exists(STATE_FILE) then - local parse_state_file = - _.compose(_.evolve { timestamp = tonumber }, _.zip_table { "checksum", "timestamp" }, _.split "\n") - local state = parse_state_file(fs.sync.read_file(STATE_FILE)) - if checksum == state.checksum then - return math.abs(time - state.timestamp) +---Register aliases for the specified packages +---@param new_aliases table<string, string[]> +function Registry.register_package_aliases(new_aliases) + for pkg_name, pkg_aliases in pairs(new_aliases) do + Registry.aliases[pkg_name] = Registry.aliases[pkg_name] or {} + for _, alias in pairs(pkg_aliases) do + if alias ~= pkg_name then + table.insert(Registry.aliases[pkg_name], alias) + end end end - return time end ----@param time integer -local function update_store_timestamp(time) - local dir = vim.fn.fnamemodify(STATE_FILE, ":h") - if not fs.sync.dir_exists(dir) then - fs.sync.mkdirp(dir) - end - fs.sync.write_file(STATE_FILE, _.join("\n", { sources.checksum(), tostring(time) })) +---@param name string +function Registry.get_package_aliases(name) + return Registry.aliases[name] or {} end ---@param callback? fun(success: boolean, updated_registries: RegistrySource[]) -function M.update(callback) +function Registry.update(callback) + log.debug "Updating the registry." local a = require "mason-core.async" + local installer = require "mason-registry.installer" + local noop = function() end - return a.run(require("mason-registry.installer").run, function(success, result) - if not callback then - return + a.run(function() + if installer.channel then + log.trace "Registry update already in progress." + return installer.channel:receive():get_or_throw() + else + return installer + .install(Registry.sources) + :on_success(function(updated_registries) + Registry:emit("update", updated_registries) + end) + :get_or_throw() end - if not success then - return callback(false, result) - end - result - :on_success(function(value) - callback(true, value) - end) - :on_failure(function(err) - callback(false, err) - end) - end) + end, callback or noop) end local REGISTRY_STORE_TTL = 86400 -- 24 hrs ----@param cb? fun() -function M.refresh(cb) +---@param callback? fun(success: boolean, updated_registries: RegistrySource[]) +function Registry.refresh(callback) + log.debug "Refreshing the registry." local a = require "mason-core.async" + local installer = require "mason-registry.installer" - ---@async - local function refresh() - a.scheduler() - local is_outdated = get_store_age(os.time()) > REGISTRY_STORE_TTL - if is_outdated or not sources.is_installed() then - if a.wait(M.update) then - a.scheduler() - update_store_timestamp(os.time()) - end + local state = installer.get_registry_state() + local is_state_ok = state + and (os.time() - state.timestamp) <= REGISTRY_STORE_TTL + and state.checksum == Registry.sources:checksum() + + if is_state_ok and Registry.sources:is_all_installed() then + log.debug "Registry refresh not necessary." + if callback then + callback(true, {}) end + return end - if not cb then - a.run_blocking(refresh) + if not callback then + return a.run_blocking(function() + return a.wait(Registry.update) + end) else - a.run(refresh, cb) + a.run(function() + return a.wait(Registry.update) + end, callback) end end ----@type table<string, string[]> -local aliases = {} - ----Register aliases for the specified packages ----@param new_aliases table<string, string[]> -function M.register_package_aliases(new_aliases) - for pkg_name, pkg_aliases in pairs(new_aliases) do - aliases[pkg_name] = aliases[pkg_name] or {} - for _, alias in pairs(pkg_aliases) do - if alias ~= pkg_name then - table.insert(aliases[pkg_name], alias) - end - end - end -end - ----@param name string -function M.get_package_aliases(name) - return aliases[name] or {} -end - -return M +return Registry |
