Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions lua/zpack/import.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,31 @@ local get_source_url = function(spec)
return src
end

---Check if a table has any non-integer keys
---@param tbl table
---@return boolean
local has_non_integer_keys = function(tbl)
for k in pairs(tbl) do
if type(k) ~= "number" then
return true
end
end
return false
end

---Check if value is a single spec (not a list of specs)
---A single spec has a source identifier ([1]=string, src, dir, url) and may have
---spec fields (opts, config, etc). A list of specs has tables or strings as elements.
---@param value zpack.Spec|zpack.Spec[]
---@return boolean
local is_single_spec = function(value)
return type(value[1]) == "string"
or value.src ~= nil
or value.dir ~= nil
or value.url ~= nil
or value.import ~= nil
if value.src ~= nil or value.dir ~= nil or value.url ~= nil or value.import ~= nil then
return true
end
if type(value[1]) == "string" then
return value[2] == nil or has_non_integer_keys(value)
end
return false
end

---Check if spec is an import spec
Expand All @@ -77,7 +93,10 @@ local normalize_dependencies = function(deps, parent_src)
)
return {}
end
if type(deps[1]) == "string" and deps.src == nil and deps.dir == nil and deps.url == nil then
if is_single_spec(deps) then
return { deps }
end
if type(deps[1]) == "string" then
local result = {}
for i, dep in ipairs(deps) do
if type(dep) == "string" then
Expand All @@ -93,7 +112,7 @@ local normalize_dependencies = function(deps, parent_src)
end
return result
end
return is_single_spec(deps) and { deps } or deps
return deps
end

---Load a spec module and import its specs
Expand Down
3 changes: 2 additions & 1 deletion lua/zpack/lazy_trigger/event.lua
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ M.setup = function(pack_spec, spec, event)
end

if #other_events > 0 then
util.autocmd(other_events, function()
util.autocmd(other_events, function(ev)
loader.process_spec(pack_spec)
vim.api.nvim_exec_autocmds(ev.event, { buffer = ev.buf, modeline = false })
end, { group = state.lazy_group, once = true, pattern = normalized_event.pattern })
end
end
Expand Down
28 changes: 17 additions & 11 deletions lua/zpack/plugin_loader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ M.process_spec = function(pack_spec, opts)

registry_entry.load_status = "loading"

local spec = registry_entry.merged_spec
local plugin = registry_entry.plugin

if not plugin then
utils.schedule_notify(("Cannot load %s: plugin not registered"):format(pack_spec.src), vim.log.levels.ERROR)
return
end

local name = plugin.spec.name
vim.cmd.packadd({ name, bang = opts.bang })

-- packadd may skip sourcing plugin files when vim.pack.add() already
-- added the plugin to the rtp. Source them explicitly.
if not opts.bang and plugin.path then
utils.source_plugin_files(plugin.path)
end

local deps = state.dependency_graph[pack_spec.src]
if deps then
for dep_src in pairs(deps) do
Expand All @@ -113,17 +130,6 @@ M.process_spec = function(pack_spec, opts)
end
end

local spec = registry_entry.merged_spec
local plugin = registry_entry.plugin

if not plugin then
utils.schedule_notify(("Cannot load %s: plugin not registered"):format(pack_spec.src), vim.log.levels.ERROR)
return
end

local name = plugin.spec.name
vim.cmd.packadd({ name, bang = opts.bang })

if spec.config or spec.opts ~= nil then
M.run_config(pack_spec.src, plugin, spec)
end
Expand Down
6 changes: 6 additions & 0 deletions lua/zpack/startup.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ M.process_all = function(ctx)

for _, pack_spec in ipairs(sorted_packs) do
vim.cmd.packadd({ pack_spec.name, bang = not ctx.load })
if ctx.load then
local entry = state.spec_registry[pack_spec.src]
if entry and entry.plugin and entry.plugin.path then
util.source_plugin_files(entry.plugin.path)
end
end
end

for _, pack_spec in ipairs(sorted_packs) do
Expand Down
40 changes: 40 additions & 0 deletions lua/zpack/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,44 @@ M.resolve_main = function(plugin, spec)
return nil
end

---Track which plugin paths have had their files sourced
---@type { [string]: true }
local sourced_plugin_paths = {}

---Source plugin/ and after/plugin/ files for a plugin.
---packadd may skip sourcing when vim.pack.add() already added the rtp entry.
---@param plugin_path string
M.source_plugin_files = function(plugin_path)
if sourced_plugin_paths[plugin_path] then
return
end
sourced_plugin_paths[plugin_path] = true

local dirs = {
plugin_path .. "/plugin",
plugin_path .. "/after/plugin",
}
for _, dir in ipairs(dirs) do
local handle = vim.uv.fs_scandir(dir)
if handle then
while true do
local fname = vim.uv.fs_scandir_next(handle)
if not fname then break end
if fname:sub(-4) == ".lua" or fname:sub(-4) == ".vim" then
local already_sourced = false
for _, s in ipairs(vim.fn.getscriptinfo()) do
if s.name == dir .. "/" .. fname then
already_sourced = true
break
end
end
if not already_sourced then
vim.cmd.source(dir .. "/" .. fname)
end
end
end
end
end
end

return M
149 changes: 149 additions & 0 deletions tests/is_single_spec_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
local helpers = require('helpers')

return function()
helpers.describe("is_single_spec heuristic", function()
helpers.test("single spec with string source and opts is detected", function()
helpers.setup_test_env()

require('zpack').setup({
spec = {
{ 'test/plugin', opts = { foo = true } },
},
defaults = { confirm = false },
})

helpers.flush_pending()
local state = require('zpack.state')
local entry = state.spec_registry['https://github.com/test/plugin']

helpers.assert_not_nil(entry, "plugin should be registered")
helpers.assert_equal(entry.merged_spec.opts.foo, true, "opts should be preserved")

helpers.cleanup_test_env()
end)

helpers.test("list of bare string specs is treated as list", function()
helpers.setup_test_env()

require('zpack').setup({
spec = {
{ 'test/plugin-a' },
{ 'test/plugin-b' },
},
defaults = { confirm = false },
})

helpers.flush_pending()
local state = require('zpack.state')

helpers.assert_not_nil(state.spec_registry['https://github.com/test/plugin-a'])
helpers.assert_not_nil(state.spec_registry['https://github.com/test/plugin-b'])

helpers.cleanup_test_env()
end)

helpers.test("single dependency spec with opts preserves all fields", function()
helpers.setup_test_env()

require('zpack').setup({
spec = {
{
'test/parent',
dependencies = {
'test/dep',
init = function() _G._test_dep_init_called = true end,
opts = { select = { lookahead = true } },
},
},
},
defaults = { confirm = false },
})

helpers.flush_pending()
local state = require('zpack.state')
local dep_entry = state.spec_registry['https://github.com/test/dep']

helpers.assert_not_nil(dep_entry, "dependency should be registered")
helpers.assert_not_nil(dep_entry.merged_spec.opts, "opts should be preserved on dependency")
helpers.assert_equal(dep_entry.merged_spec.opts.select.lookahead, true,
"opts fields should be preserved")
helpers.assert_not_nil(dep_entry.merged_spec.init, "init should be preserved on dependency")

_G._test_dep_init_called = nil
helpers.cleanup_test_env()
end)

helpers.test("list of string dependencies are all registered", function()
helpers.setup_test_env()

require('zpack').setup({
spec = {
{
'test/parent',
dependencies = { 'test/dep-a', 'test/dep-b' },
},
},
defaults = { confirm = false },
})

helpers.flush_pending()
local state = require('zpack.state')

helpers.assert_not_nil(state.spec_registry['https://github.com/test/dep-a'],
"first string dep should be registered")
helpers.assert_not_nil(state.spec_registry['https://github.com/test/dep-b'],
"second string dep should be registered")

helpers.cleanup_test_env()
end)

helpers.test("list of table specs is treated as list", function()
helpers.setup_test_env()

require('zpack').setup({
spec = {
{ 'test/plugin-a', opts = {} },
{ 'test/plugin-b', opts = { bar = true } },
},
defaults = { confirm = false },
})

helpers.flush_pending()
local state = require('zpack.state')

helpers.assert_not_nil(state.spec_registry['https://github.com/test/plugin-a'],
"first spec should be registered")
helpers.assert_not_nil(state.spec_registry['https://github.com/test/plugin-b'],
"second spec should be registered")

helpers.cleanup_test_env()
end)

helpers.test("single dependency with config preserves config function", function()
helpers.setup_test_env()
local config_called = false

require('zpack').setup({
spec = {
{
'test/parent',
dependencies = {
'test/dep-with-config',
config = function() config_called = true end,
},
},
},
defaults = { confirm = false },
})

helpers.flush_pending()
local state = require('zpack.state')
local dep_entry = state.spec_registry['https://github.com/test/dep-with-config']

helpers.assert_not_nil(dep_entry, "dependency should be registered")
helpers.assert_not_nil(dep_entry.merged_spec.config, "config should be preserved on dependency")

helpers.cleanup_test_env()
end)
end)
end
2 changes: 2 additions & 0 deletions tests/run_all.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ local test_modules = {
'zclean_test',
'zupdate_test',
'zrestore_test',
'is_single_spec_test',
'source_plugin_files_test',
}

print("\n" .. string.rep("=", 60))
Expand Down
Loading
Loading