Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial packspec support #910

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions lua/lazy/core/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ M.defaults = {
-- executed. In this case, a warning message will be shown.
warn_on_override = true,
},
packspec = {
enabled = true,
versions = true, -- Honor dependency versions in packspecs
path = vim.fn.stdpath("state") .. "/lazy/packspec.lua",
},
debug = false,
}

Expand Down Expand Up @@ -252,6 +257,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,
})

Expand Down
125 changes: 125 additions & 0 deletions lua/lazy/core/packspec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
local Config = require("lazy.core.config")
local Util = require("lazy.util")

---@class PackSpec
---@field dependencies? table<string, string>
---@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<string, LazyPkg>
M.packspecs = nil
---@type table<string, LazySpec>
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
27 changes: 26 additions & 1 deletion lua/lazy/core/plugin.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local Config = require("lazy.core.config")
local Util = require("lazy.core.util")
local Handler = require("lazy.core.handler")
local Packspec = require("lazy.core.packspec")

---@class LazyCorePlugin
local M = {}
Expand All @@ -13,6 +14,8 @@ M.loading = false
---@field notifs {msg:string, level:number, file?:string}[]
---@field importing? string
---@field optional? boolean
---@field packspecs table<string, boolean>
---@field names table<string,string>
local Spec = {}
M.Spec = Spec

Expand All @@ -24,7 +27,9 @@ function Spec.new(spec, opts)
self.disabled = {}
self.modules = {}
self.notifs = {}
self.packspecs = {}
self.optional = opts and opts.optional
self.names = {}
if spec then
self:parse(spec)
end
Expand All @@ -47,6 +52,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
Expand Down Expand Up @@ -83,7 +89,17 @@ function Spec:add(plugin, results, is_dep)
-- 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
Expand Down Expand Up @@ -124,6 +140,15 @@ function Spec:add(plugin, results, is_dep)
plugin.config = nil
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

plugin._ = {}
plugin._.dep = is_dep

Expand Down
3 changes: 3 additions & 0 deletions lua/lazy/core/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ function M.norm(path)
return path:sub(-1) == "/" and path:sub(1, -2) or path
end

---@generic R
---@param fn fun():R
---@param opts? string|{msg:string, on_error:fun(msg)}
---@return R
function M.try(fn, opts)
opts = type(opts) == "string" and { msg = opts } or opts or {}
local msg = opts.msg
Expand Down
2 changes: 2 additions & 0 deletions lua/lazy/manage/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -115,6 +116,7 @@ function M.update(opts)
}, opts):wait(function()
require("lazy.manage.lock").update()
require("lazy.help").update()
require("lazy.core.packspec").update()
end)
end
--
Expand Down
29 changes: 17 additions & 12 deletions lua/lazy/manage/reloader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,24 @@ function M.check(start)
end

if not (start or #changes == 0) then
vim.schedule(function()
if Config.options.change_detection.notify and not Config.headless() then
local lines = { "# Config Change Detected. Reloading...", "" }
for _, change in ipairs(changes) do
table.insert(lines, "- **" .. change.what .. "**: `" .. vim.fn.fnamemodify(change.file, ":p:~:.") .. "`")
end
Util.warn(lines)
end
Plugin.load()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
vim.api.nvim_exec_autocmds("User", { pattern = "LazyReload", modeline = false })
end)
M.reload(changes)
end
end

---@param {file:string, what:string}[]
function M.reload(changes)
vim.schedule(function()
if Config.options.change_detection.notify and not Config.headless() then
local lines = { "# Config Change Detected. Reloading...", "" }
for _, change in ipairs(changes) do
table.insert(lines, "- **" .. change.what .. "**: `" .. vim.fn.fnamemodify(change.file, ":p:~:.") .. "`")
end
Util.warn(lines)
end
Plugin.load()
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
vim.api.nvim_exec_autocmds("User", { pattern = "LazyReload", modeline = false })
end)
end

return M
68 changes: 22 additions & 46 deletions lua/lazy/manage/task/plugin.lua
Original file line number Diff line number Diff line change
@@ -1,69 +1,45 @@
local Util = require("lazy.util")
local Loader = require("lazy.core.loader")
local Config = require("lazy.core.config")

---@type table<string, LazyTaskDef>
local M = {}

---@param plugin LazyPlugin
local function get_build_file(plugin)
for _, path in ipairs({ "build.lua", "build/init.lua" }) do
path = plugin.dir .. "/" .. path
if Util.file_exists(path) then
return path
end
end
end

M.build = {
---@param opts? {force:boolean}
skip = function(plugin, opts)
if opts and opts.force then
return false
end
return not (plugin._.dirty and (plugin.build or get_build_file(plugin)))
return not (plugin._.dirty and plugin.build)
end,
run = function(self)
vim.cmd([[silent! runtime plugin/rplugin.vim]])

Loader.load(self.plugin, { task = "build" })

local builders = self.plugin.build

local build_file = get_build_file(self.plugin)
if build_file then
if builders then
if Config.options.build.warn_on_override then
Util.warn(
("Plugin **%s** provides its own build script, but you also defined a `build` command.\nThe `build.lua` file will not be used"):format(
self.plugin.name
)
)
end
local builders = type(self.plugin.build) == "table" and self.plugin.build or { self.plugin.build }
---@cast builders (string|fun(LazyPlugin))[]

for _, build in ipairs(builders) do
if type(build) == "function" then
build(self.plugin)
elseif type(build) ~= "string" then
error("invalid build type: " .. type(build))
elseif
vim.list_contains({ "vim", "lua" }, build:sub(-3)) and vim.loop.fs_stat(self.plugin.dir .. "/" .. build)
then
Loader.source(self.plugin.dir .. "/" .. build)
elseif build:sub(1, 1) == ":" then
local cmd = vim.api.nvim_parse_cmd(build:sub(2), {})
self.output = vim.api.nvim_cmd(cmd, { output = true })
else
builders = function()
Loader.source(build_file)
end
end
end
if builders then
builders = type(builders) == "table" and builders or { builders }
---@cast builders (string|fun(LazyPlugin))[]
for _, build in ipairs(builders) do
if type(build) == "string" and build:sub(1, 1) == ":" then
local cmd = vim.api.nvim_parse_cmd(build:sub(2), {})
self.output = vim.api.nvim_cmd(cmd, { output = true })
elseif type(build) == "function" then
build(self.plugin)
else
local shell = vim.env.SHELL or vim.o.shell
local shell_args = shell:find("cmd.exe", 1, true) and "/c" or "-c"
local shell = vim.env.SHELL or vim.o.shell
local shell_args = shell:find("cmd.exe", 1, true) and "/c" or "-c"

self:spawn(shell, {
args = { shell_args, build },
cwd = self.plugin.dir,
})
end
self:spawn(shell, {
args = { shell_args, build },
cwd = self.plugin.dir,
})
end
end
end,
Expand Down