8000 fix(merge): drop a removed plugin's specs before deriving what stays by zuqini · Pull Request #49 · zuqini/zpack.nvim · GitHub
[go: up one dir, main page]

Skip to content
31 changes: 23 additions & 8 deletions doc/zpack.txt
Original file line number Diff line number Diff line change
Expand Up @@ -707,10 +707,23 @@ dependencies (string|string[]|zpack.Spec|zpack.Spec[], optional)
Can be a string (a `[1]` value, see |zpack-Spec|), array
of strings, inline spec, or array of specs. Dependency
specs can have their own configuration (opts, config, etc.)
and nested dependencies. When the parent is disabled or
skipped as `optional`, its dependency specs are dropped with
it (lazy.nvim parity), including their configuration of a
plugin that other specs keep enabled.
and nested dependencies. A plugin whose dependency is
disabled is disabled too, with a warning (lazy.nvim keeps
it). When the parent is disabled or skipped as `optional`,
its dependency specs (and its `specs`) are dropped with it,
as in lazy.nvim: they no longer configure or add
dependencies to a plugin that other specs keep, and those
under a disabled parent no longer disable it. Such a plugin
keeps its name even if a dropped spec set `name`, and keeps
its source while a spec giving it remains (otherwise its
remaining specs pick one). As in
lazy.nvim, `enabled = false` under an `optional` parent
still applies, since optional plugins are skipped only once
nothing else is disabled. A spec table listed under several
plugins stays while any of them does, counts once, and is a
dependency only if every remaining listing is one (lazy.nvim
records only its first listing). A plugin skipped by `cond`
keeps its dependency specs.

*zpack-Spec.enabled*
enabled (boolean|function, optional)
Expand Down Expand Up @@ -785,9 +798,9 @@ opts_extend (string|string[], optional)
opts, e.g. `function(_, o) o.ensure_installed = { 'x' } end`.
Lists are appended in merge order: specs pulled in as
`dependencies` first, then top-level specs in import order.
Specs listed under the `dependencies` of a plugin that is
disabled or skipped as `optional` are left out, as in
lazy.nvim. lazy.nvim differs:
Specs listed under the `dependencies` or `specs` of a
plugin that is disabled or skipped as `optional` are left
out, as in lazy.nvim. lazy.nvim differs:
- Each spec uses the nearest `opts_extend` at or before it.
A spec with none in effect replaces the lists before it,
so of the lists set before the first `opts_extend`, only
Expand Down Expand Up @@ -979,7 +992,9 @@ specs (zpack.Spec|zpack.Spec[], optional)
lazy.nvim parity. Companion plugin specs grouped with this
one. Unlike `dependencies`, these are peers (NOT loaded
before this plugin) — they walk through the normal import
path and become first-class registry entries.
path and become first-class registry entries. They are
< F438 /td> dropped with this plugin when it is disabled or skipped as
`optional` (see |zpack-Spec.dependencies|).

*zpack-Spec.deactivate*
deactivate (function(plugin), optional)
Expand Down
2 changes: 2 additions & 0 deletions docs/tips.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Most of your lazy.nvim plugin specs will work as-is with zpack. However, zpack f
- **profiling**: Use `nvim --startuptime startuptime.log`. Also refer to example [Neovim Profiler script](https://gist.github.com/zuqini/35993710f81983fbfa6baca67bdb32ed)
- **default lazy plugins**: lazy.nvim's community specs silently default top-level specs for utility libraries like `plenary.nvim` to `lazy = true`, even without lazy triggers or a lazy parent. zpack respects your specs as-written, so set `lazy = true` explicitly on such specs if you want the same default — or set `defaults.lazy = true` in [`setup()`](../README.md#configurations) to apply it to every spec
- **`enabled` vs `cond`**: lazy.nvim collapses both to a single "disabled" state. zpack splits them: `enabled = false` skips install entirely (the plugin is never cloned), while `cond = false` installs the plugin but skips its load (so it still shows up under `vim.pack`'s data dir). Use `enabled` when you want lazy.nvim's "don't install at all" behavior
- **disabled dependencies**: a plugin whose dependency has `enabled = false` is disabled too, with a warning, while lazy.nvim keeps it
- **a spec table listed in several places**: zpack keeps it while any plugin listing it stays, and treats it as a dependency only if every remaining listing is under `dependencies`; lazy.nvim records only its first listing
- **`opts_extend`**: in lazy.nvim each spec uses the nearest `opts_extend` at or before it, and a spec with none in effect replaces the lists before it, so of the lists set before the first `opts_extend` only the last is kept, and a later `opts_extend` that leaves a key out stops that key from appending. lazy.nvim also keeps only the list entries of a table at an `opts_extend` key, dropping its named keys. zpack combines `opts_extend` from all specs of a plugin, so it can be set on any of them, and appends a table with named keys (such as which-key's `{ mode = 'v', ... }`) as one entry. zpack appends `dependencies` specs' lists before top-level ones, while lazy.nvim appends in import order, so with plain lists the two match when only the plugin's first spec sets `opts_extend` (LazyVim does this) and no `dependencies` entry adds to the list

## Gotchas
Expand Down
34 changes: 22 additions & 12 deletions lua/zpack/import.lua
Original file line number Diff line number Diff line change
Expand Up @@ -219,28 +219,20 @@ local import_from_module = function(module_path, ctx)
end
end

---Register a spec's dependencies into the dependency graph and import them.
---Import a spec's dependencies as its children.
---@param spec zpack.Spec
---@param src string Normalized source of the parent spec
---@param ctx zpack.ProcessContext
local register_dependencies = function(spec, src, ctx)
local dep_specs = normalize_dependencies(spec.dependencies, src)
state.dependency_graph[src] = state.dependency_graph[src] or {}
local dep_ctx = vim.tbl_extend('force', ctx, { is_dependency = true })
local dep_ctx = vim.tbl_extend('force', ctx, { is_dependency = true, parent = spec })
for _, dep_spec in ipairs(dep_specs) do
local dep_src, err = normalize_source(dep_spec)
if not dep_src then
if err then
utils.schedule_notify(("Invalid dependency for %s: %s"):format(src, err), vim.log.levels.WARN)
end
else
if not state.dependency_graph[src][dep_src] then
state.dependency_graph[src][dep_src] = true
10BC8 state.reverse_dependency_graph[dep_src] = state.reverse_dependency_graph[dep_src] or {}
state.reverse_dependency_graph[dep_src][src] = true
end
dep_spec._parent_specs = dep_spec._parent_specs or {}
dep_spec._parent_specs[spec] = true
M.import_specs(dep_spec, dep_ctx)
end
end
Expand Down Expand Up @@ -308,6 +300,24 @@ local import_one_spec = function(spec, ctx)
end
local is_dep = ctx.is_dependency or false

local seen = state.top_specs[spec] or state.spec_parents[spec] ~= nil
if ctx.parent then
state.spec_parents[spec] = state.spec_parents[spec] or {}
state.spec_parents[spec][ctx.parent] = true
state.spec_children[ctx.parent] = state.spec_children[ctx.parent] or {}
state.spec_children[ctx.parent][spec] = state.spec_children[ctx.parent][spec] or is_dep
else
state.top_specs[spec] = true
end
-- lazy.nvim parity: a table listed in several places is one fragment, so
-- its opts and lists count once. It is a dependency only if every
-- listing is one.
if seen then
spec._is_dependency = spec._is_dependency and is_dep
return
end

state.spec_src[spec] = src
spec._import_order = state.import_order
state.import_order = state.import_order + 1
spec._is_dependency = is_dep
Expand All @@ -321,15 +331,15 @@ local import_one_spec = function(spec, ctx)
-- Walk dependencies unconditionally. `enabled` is evaluated post-merge
-- in resolve_all; gating the dep walk on the raw pre-merge spec would
-- call function-form `enabled` eagerly at import time and diverge from
-- the merged truth. prune_disabled_subtrees handles cleanup afterward.
-- the merged truth. prune_disabled handles cleanup afterward.
if spec.dependencies then
register_dependencies(spec, src, ctx)
end

-- Nested `specs` are peer plugins; reset is_dependency so a parent
-- reached via a `dependencies` chain doesn't propagate dep-status down.
if spec.specs then
local peer_ctx = vim.tbl_extend('force', ctx, { is_dependency = false })
local peer_ctx = vim.tbl_extend('force', ctx, { is_dependency = false, parent = spec })
M.import_specs(spec.specs, peer_ctx)
end
end
Expand Down
1 change: 1 addition & 0 deletions lua/zpack/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ local M = {}
---@field confirm boolean?
---@field defaults zpack.Config.Defaults
---@field is_dependency? boolean Internal: Whether currently importing as dependency
---@field parent? zpack.Spec Internal: The spec whose `dependencies` or `specs` is being imported
---@field _imported_functions? table<function, true> Internal: dedup set for function-form `import`

---@return zpack.ProcessContext
Expand Down
Loading
Loading
0