diff --git a/lua/lazy/core/config.lua b/lua/lazy/core/config.lua index 8b42617..50b54b1 100644 --- a/lua/lazy/core/config.lua +++ b/lua/lazy/core/config.lua @@ -179,6 +179,11 @@ M.defaults = { -- Track each new require in the Lazy profiling tab require = false, }, + packspec = { + enabled = true, + versions = true, -- Honor dependency versions in packspecs + path = vim.fn.stdpath("state") .. "/lazy/packspec.lua", + }, debug = false, } @@ -281,6 +286,14 @@ function M.setup(opts) require("lazy.manage.checker").start() end, 10) end + + -- useful for plugin developers when making changes to a packspec file + vim.api.nvim_create_autocmd("BufWritePost", { + pattern = "package.lua", + callback = function() + require("lazy.view.commands").cmd("packspec") + end, + }) end, }) diff --git a/lua/lazy/core/packspec.lua b/lua/lazy/core/packspec.lua new file mode 100644 index 0000000..75217d1 --- /dev/null +++ b/lua/lazy/core/packspec.lua @@ -0,0 +1,125 @@ +local Config = require("lazy.core.config") +local Util = require("lazy.util") + +---@class PackSpec +---@field dependencies? table +---@field lazy? LazyPluginSpec +local M = {} + +M.lazy_file = "lazy.lua" +M.pkg_file = "pkg.json" +M.enable_lazy_file = false + +---@alias LazyPkg {lazy?:(fun():LazySpec), pkg?:PackSpec} + +---@type table +M.packspecs = nil +---@type table +M.specs = {} + +---@param spec LazyPkg +---@param plugin LazyPlugin +---@return LazySpec? +local function convert(plugin, spec) + ---@type LazySpec + local ret = {} + + local pkg = spec.pkg + if pkg then + if pkg.dependencies then + for url, version in pairs(pkg.dependencies) do + if (not Config.options.packspec.versions) or version == "*" or version == "" then + version = nil + end + -- HACK: Add `.git` to github urls + if url:find("github") and not url:match("%.git$") then + url = url .. ".git" + end + ret[#ret + 1] = { url = url, version = version } + end + end + local p = pkg.lazy + if p then + p.url = p.url or plugin.url + p.dir = p.dir or plugin.dir + ret[#ret + 1] = p + end + end + + if spec.lazy then + ret[#ret + 1] = spec.lazy() + end + + return ret +end + +local function load() + Util.track("packspec") + M.packspecs = {} + if vim.loop.fs_stat(Config.options.packspec.path) then + Util.try(function() + M.packspecs = loadfile(Config.options.packspec.path)() + end, "Error loading packspecs:") + end + Util.track() +end + +---@param plugin LazyPlugin +---@return LazySpec? +function M.get(plugin) + if not M.packspecs then + load() + end + + if not M.packspecs[plugin.dir] then + return + end + M.specs[plugin.dir] = M.specs[plugin.dir] or convert(plugin, M.packspecs[plugin.dir]) + return vim.deepcopy(M.specs[plugin.dir]) +end + +function M.update() + local ret = {} + for _, plugin in pairs(Config.plugins) do + local spec = { + pkg = M.pkg(plugin), + lazy = M.enable_lazy_file and M.lazy_pkg(plugin) or nil, + } + if not vim.tbl_isempty(spec) then + ret[plugin.dir] = spec + end + end + local code = "return " .. Util.dump(ret) + Util.write_file(Config.options.packspec.path, code) + M.packspecs = nil + M.specs = {} +end + +---@param plugin LazyPlugin +function M.lazy_pkg(plugin) + local file = Util.norm(plugin.dir .. "/" .. M.lazy_file) + if Util.file_exists(file) then + ---@type LazySpec + local chunk = Util.try(function() + return loadfile(file) + end, "`" .. M.lazy_file .. "` for **" .. plugin.name .. "** has errors:") + if chunk then + return { _raw = ([[function() %s end]]):format(Util.read_file(file)) } + else + Util.error("Invalid `package.lua` for **" .. plugin.name .. "**") + end + end +end + +---@param plugin LazyPlugin +function M.pkg(plugin) + local file = Util.norm(plugin.dir .. "/" .. M.pkg_file) + if Util.file_exists(file) then + ---@type PackSpec + return Util.try(function() + return vim.json.decode(Util.read_file(file)) + end, "`" .. M.pkg_file .. "` for **" .. plugin.name .. "** has errors:") + end +end + +return M diff --git a/lua/lazy/core/plugin.lua b/lua/lazy/core/plugin.lua index 8d18e33..3de712f 100644 --- a/lua/lazy/core/plugin.lua +++ b/lua/lazy/core/plugin.lua @@ -1,4 +1,5 @@ local Config = require("lazy.core.config") +local Packspec = require("lazy.core.packspec") local Util = require("lazy.core.util") ---@class LazyCorePlugin @@ -15,6 +16,8 @@ M.loading = false ---@field notifs {msg:string, level:number, file?:string}[] ---@field importing? string ---@field optional? boolean +---@field packspecs table +---@field names table local Spec = {} M.Spec = Spec M.last_fid = 0 @@ -32,7 +35,9 @@ function Spec.new(spec, opts) self.dirty = {} self.notifs = {} self.ignore_installed = {} + self.packspecs = {} self.optional = opts and opts.optional + self.names = {} if spec then self:parse(spec) end @@ -45,6 +50,7 @@ function Spec:parse(spec) end -- PERF: optimized code to get package name without using lua patterns +---@return string function Spec.get_name(pkg) local name = pkg:sub(-4) == ".git" and pkg:sub(1, -5) or pkg name = name:sub(-1) == "/" and name:sub(1, -2) or name @@ -83,7 +89,17 @@ function Spec:add(plugin, results) -- local plugin plugin.name = plugin.name or Spec.get_name(plugin.dir) elseif plugin.url then - plugin.name = plugin.name or Spec.get_name(plugin.url) + if plugin.name then + self.names[plugin.url] = plugin.name + local name = Spec.get_name(plugin.url) + if name and self.plugins[name] then + self.plugins[name].name = plugin.name + self.plugins[plugin.name] = self.plugins[name] + self.plugins[name] = nil + end + else + plugin.name = self.names[plugin.url] or Spec.get_name(plugin.url) + end -- check for dev plugins if plugin.dev == nil then for _, pattern in ipairs(Config.options.dev.patterns) do @@ -156,6 +172,15 @@ function Spec:add(plugin, results) table.remove(M.fid_stack) end + -- import the plugin's spec + if Config.options.packspec.enabled and plugin.dir and not self.packspecs[plugin.dir] then + self.packspecs[plugin.dir] = true + local packspec = Packspec.get(plugin) + if packspec then + self:normalize(packspec, nil, true) + end + end + if self.plugins[plugin.name] then plugin = self:merge(self.plugins[plugin.name], plugin) end diff --git a/lua/lazy/manage/init.lua b/lua/lazy/manage/init.lua index 897bdbf..f5a96a9 100644 --- a/lua/lazy/manage/init.lua +++ b/lua/lazy/manage/init.lua @@ -92,6 +92,7 @@ function M.install(opts) }, opts):wait(function() require("lazy.manage.lock").update() require("lazy.help").update() + require("lazy.core.packspec").update() end) end @@ -116,6 +117,7 @@ function M.update(opts) }, opts):wait(function() require("lazy.manage.lock").update() require("lazy.help").update() + require("lazy.core.packspec").update() end) end -- diff --git a/lua/lazy/view/commands.lua b/lua/lazy/view/commands.lua index 1210917..13c5070 100644 --- a/lua/lazy/view/commands.lua +++ b/lua/lazy/view/commands.lua @@ -34,6 +34,20 @@ M.commands = { health = function() vim.cmd.checkhealth("lazy") end, + pkg = function(opts) + local Pkg = require("lazy.core.packspec") + Pkg.update() + require("lazy.manage.reloader").reload({ + { + file = "packspec", --Config.options.packspec.path, + what = "changed", + }, + }) + for _, plugin in pairs(opts and opts.plugins or {}) do + local spec = Pkg.get(plugin) + Util.info(vim.inspect(spec), { lang = "lua", title = plugin.name }) + end + end, home = function() View.show("home") end, @@ -74,7 +88,7 @@ M.commands = { } function M.complete(cmd, prefix) - if not (ViewConfig.commands[cmd] or {}).plugins then + if not (ViewConfig.commands[cmd] or {}).plugins and cmd ~= "pkg" then return end ---@type string[] diff --git a/tests/core/plugin_spec.lua b/tests/core/plugin_spec.lua index 51f6e03..c15505f 100644 --- a/tests/core/plugin_spec.lua +++ b/tests/core/plugin_spec.lua @@ -30,6 +30,14 @@ describe("plugin spec url/name", function() { { "foo/bar", name = "foobar" }, { [1] = "foo/bar", name = "foobar", url = "https://github.com/foo/bar.git" } }, { { "foo/bar", url = "123" }, { [1] = "foo/bar", name = "123", url = "123" } }, { { url = "https://foobar" }, { name = "foobar", url = "https://foobar" } }, + { + { { url = "https://foo", name = "foobar" }, { url = "https://foo" } }, + { name = "foobar", url = "https://foo" }, + }, + { + { { url = "https://foo" }, { url = "https://foo", name = "foobar" } }, + { name = "foobar", url = "https://foo" }, + }, { { url = "ssh://foobar" }, { name = "foobar", url = "ssh://foobar" } }, { "foo/bar", { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } }, { { { { "foo/bar" } } }, { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } }, @@ -46,6 +54,7 @@ describe("plugin spec url/name", function() plugins[1]._ = {} assert(#spec.notifs == 0) assert.equal(1, #plugins) + plugins[1]._.super = nil assert.same(test[2], plugins[1]) end) end