aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-registry/init.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/mason-registry/init.lua')
-rw-r--r--lua/mason-registry/init.lua272
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