- Migrating from lazy.nvim
- Gotchas
- Compatibility Notes
- Lockfile location
- Native vim.pack commands
- Upgrading to 3.0
- Upgrading from 1.x to 2.0
Most of your lazy.nvim plugin specs will work as-is with zpack. However, zpack follows vim.pack conventions over lazy.nvim conventions, and is missing a few advanced features:
- version pinning: lazy.nvim's
versionfield maps to zpack'ssem_version.versionalso reads a string that can't be a git ref (*,1.*,^1.0) as a range, but any other string, such as1.x, stays a ref. As in lazy.nvim, a range written as a string follows the default branch if no tag fits. A plugin's version is fixed for the session, so if upstream later adds one,:ZPack updatestill moves the plugin to the branch's tip and says so; a restart and another update move it to the tag. Avim.version.range()object is an error fromvim.packif no tag fits. See Spec Reference and version pinning examples - dev mode: Set
dev = trueon a spec and configuresetup({ dev = { path = '~/projects' } })— the source is rewritten to<path>/<plugin-name>. Live file-watch / auto-reload is out of scope; use:ZPack reload {plugin}to manually re-source. See Spec Reference fordev/deactivate - profiling: Use
nvim --startuptime startuptime.log. Also refer to example Neovim Profiler script - default lazy plugins: lazy.nvim's community specs silently default top-level specs for utility libraries like
plenary.nvimtolazy = true, even without lazy triggers or a lazy parent. zpack respects your specs as-written, so setlazy = trueexplicitly on such specs if you want the same default — or setdefaults.lazy = trueinsetup()to apply it to every spec enabledvscond: lazy.nvim collapses both to a single "disabled" state. zpack splits them:enabled = falseskips install entirely (the plugin is never cloned), whilecond = falseinstalls the plugin but skips its load (so it still shows up undervim.pack's data dir). Useenabledwhen you want lazy.nvim's "don't install at all" behavior. On a spec with animport, a falsecondworks likeenabledfor what the import declares: seecondon animportacts likeenabled- empty triggers: a trigger that resolves to nothing usable (
event = {},cmd = { 123 }, or a function that adds nothing) leaves the plugin eager, so it can't go unloaded with no way to load it; lazy.nvim makes it lazy. Aneventwith no real event in it is eager too, with a warning. A function that throws counts as set, so a plugin stays lazy when its function requires the plugin's own module, which then loads it, and otherwise shows the error - a key that can't load the plugin: a plugin whose only keys are
{ lhs, false }or{ lhs, '<Nop>' }stays lazy, as in lazy.nvim, and loads when something requires it. Setlazy = falseif nothing does LazyFile: this is a LazyVim event, not a lazy.nvim one, and zpack doesn't define it. Useevent = { 'BufReadPost', 'BufNewFile', 'BufWritePre' }- a
nilin a list: zpack walks past it, so{ { 'a/b' }, vim.g.flag and { 'c/d' } or nil, { 'e/f' } }keepse/fwithout a warning, as LazyVim's AI extras expect; lazy.nvim'sipairsstops at the firstniland silently drops the rest - a list with fields:
{ 'a/b', 'c/d', opts = {} }is one spec fora/band warns about the rest ({ nil, 'a/b', opts = {} }is one spec with no source, and warns the same way), and:ZPack cleanlists an installedc/das unused until it's split; lazy.nvim reads it as a list and ignoresopts keysgiven as{ lhs, rhs, desc = ... }: zpack also takes one KeySpec without the extra nesting when it has anything besides strings; lazy.nvim always reads the outer table as a list. So does{ lhs, rhs }when the rhs can only be one (it is empty or<Nop>, starts with<cmd>,<Plug>or:, or has text before a final<CR>). Any other two strings are two keys in both, so{ '<leader>d', 'dd' }needs a field or nesting to be one key- shadowed runtime files: when
setup()runs frominit.luaand two eager plugins ship the same runtime file, such ascolors/x.lua, the higherpriorityplugin wins, being first on 'runtimepath'; in lazy.nvim the lowerpriorityone wins, since it loads later and goes ahead of the other - when
plugin/runs: whensetup()runs frominit.lua, theplugin/scripts of eager plugins, and of any plugin aninitorinit.lualoads, run where native startup sources them, afterinit.lua. So a command they define isn't there yet in aconfigor aninit; lazy.nvim sources a plugin'splugin/as it loads it, before itsconfig - 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 - an
importwith no plugin of its own:{ { 'u/dist' }, { import = 'dist.one' } }looks for the module in your listed plugins named after its top level, by name or repository, when your config, 'runtimepath' and the plugins otherimportspecs add don't have it, installing what's missing, on the first start and every later one. lazy.nvim tries those first and then every other plugin; zpack doesn't try the rest and warns, so for a module named otherwise put theimporton its plugin's spec. zpack also finds a plugin listed after theimport, where lazy.nvim reports no specs found, and it doesn't read a plugin that ends up disabled or dropped, where lazy.nvim reads one already on disk. A spec with a source and animport,{ 'u/own', import = 'dist.one' }, looks only inu/own opts_extend: in lazy.nvim each spec uses the nearestopts_extendat or before it, and a spec with none in effect replaces the lists before it, so of the lists set before the firstopts_extendonly the last is kept, and a lateropts_extendthat leaves a key out stops that key from appending. lazy.nvim also keeps only the list entries of a table at anopts_extendkey, dropping its named keys. zpack combinesopts_extendfrom 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. Both append in import order, so with plain lists the two match when only the plugin's first spec setsopts_extend(LazyVim does this)
Known gotchas when using zpack:
- install/update feedback:
vim.packsurfaces install/update progress via:messages(e.g.vim.pack: Downloading updates (0/83)). These messages are hidden if you havevim.opt.cmdheight = 0— raise it, check:messages, or route them through a notifier like snacks.notifier, nvim-notify, or noice.nvim. Also see noice.nvim with vim.pack for compatibility notes - names without an owner:
{ 'mason.nvim', opts = ... }only refers to the plugin of that name, as in lazy.nvim. Another spec has to give its source; otherwise it is skipped with a warning - same plugin, two owners: specs deriving the same name (e.g.
williamboman/mason.nvimin adependencieslist andmason-org/mason.nvimat top level) are folded into one plugin; adev = truefragment wins, then an explicitsrc/url/dir, then a top-level spec over a dependency, and zpack warns unless the specs name a repo in common (a shared[1], or asrc/urlequal to the other's[1]). That picks only the source: fields merge in import order as usual, so the lastenableddecides whether the plugin is disabled. Point both at the current owner, or setnameon one to keep them apart vim.NILin opts: as in lazy.nvim, a key set tovim.NILis removed rather than passed tosetup()wherever specs merge, which includes every top-level key of table opts. A table that replaces the earlier value (nothing, a scalar or a list was there) keeps itsvim.NIL, and so do opts a function returns or edits. To hand a plugin a JSONnull(e.g. in LSP settings), nest it in a table no earlier spec sets, or set it from function-form opts
The default Snacks.nvim dashboard configuration includes a startup time section that has a hard dependency on lazy.nvim. This will cause errors with any other plugin manager, not just zpack.
To work around this, remove the startup section from your dashboard configuration:
require('snacks').setup({
dashboard = {
sections = {
{ section = "header" },
{ section = "keys", gap = 1, padding = 1 },
-- { section = "startup" }, -- Remove this line (depends on lazy.nvim)
},
}
})See snacks.nvim#1778 for more details.
noice.nvim filters out vim.pack messages by default, which means you won't see install/update notifications from your plugin manager.
To fix this, add a route that explicitly shows vim.pack messages:
require('noice').setup({
routes = {
{
filter = {
event = "msg_show",
find = "vim.pack",
},
},
},
})vim.pack writes its lockfile to $XDG_CONFIG_HOME/nvim/nvim-pack-lock.json — i.e. next to your init.lua. On Neovim 0.13+ the path is configurable through the 'packlockfile' option (see :h 'packlockfile'), so you can keep it out of your config repo:
vim.o.packlockfile = vim.fs.joinpath(vim.fn.stdpath('data'), 'nvim-pack-lock.json')Two caveats:
'packlockfile'must be set before the firstvim.packcall, which in practice means before thevim.pack.add()that installs zpack itself — put it at the very top of yourinit.lua.- Changing the path does not move the existing file. Move (or delete and regenerate with
:ZPack update) the old lockfile, otherwisevim.packstarts from an empty one.
zpack needs no configuration for this: :ZPack update, :ZPack restore and installing a plugin at its locked revision all follow whatever 'packlockfile' points at.
On Neovim 0.13+, the :ZPack update, restore, and delete subcommands have native vim.pack command equivalents (:packupdate, :packupdate ++lockfile, :packdel). zpack keeps its session state in sync either way, but the native commands don't know about specs, so :packupdate moves a pin = true plugin that :ZPack update leaves alone. See :h zpack-native-commands for the full mapping.
These changes can affect a working config, nearly all of them without any error. The release notes list the rest.
keys = { '<leader>a', 'dd' } used to map <leader>a to dd. As in lazy.nvim, it's now a list of two keys that only load the plugin, and nothing warns: <leader>a has no rhs, so it does nothing once the plugin is loaded, and a proxy sits over native dd until then.
Nest the key, which lazy.nvim reads the same way, or give it a field:
-- 2.x
keys = { '<leader>a', 'dd' }
-- 3.0
keys = { { '<leader>a', 'dd' } }
keys = { '<leader>a', 'dd', desc = 'Delete line' }A second string that can only be an rhs is still one key: empty, <Nop>, <cmd>..., <Plug>..., :..., or text ending in <CR>.
{ 'u/x', enabled = false } beside { 'u/x', enabled = true } used to leave the plugin out, whichever came first. Now the last one in merge order decides, so this installs it. Set enabled = false in the last spec, or drop the enabled = true.
{ 'u/x', cond = false } beside { 'u/x', cond = true } used to keep the plugin from loading, whichever came first. Now the last one in merge order decides, so this loads it, and of several functions only that spec's runs.
Two specs of a plugin mapping the same lhs in the same mode, ft and buffer used to get the first one's rhs. Now they get the last one's, as in lazy.nvim (see the next entry for the order).
A plugin's specs used to merge with every spec under some plugin's dependencies first and the top-level ones after, so a top-level spec's fields won. Now they merge in import order, as in lazy.nvim, with a spec under dependencies right after the spec listing it. A later spec under dependencies can now override a top-level field, clear its trigger with false or set enabled, and its function-form opts gets the top-level opts before it. AstroNvim's mason-tool-installer.lua relies on this: its opts function for astrocore reads the opts.mappings astrocore's own spec sets. opts_extend lists append in this order too. Which spec's source wins when specs of two owners fold into one plugin hasn't changed.
-- lua/plugins/a.lua
{ 'u/x', opts = { style = 'dark' } }
-- lua/plugins/b.lua: 2.x set up u/x with 'dark', 3.0 with 'light'
{ 'u/p', dependencies = { { 'u/x', opts = { style = 'light' } } } }To keep a top-level setting, set it in the spec that comes last.
If several are set, the order was version, sem_version, branch, tag, commit. It's now commit, tag, version, sem_version, branch, and version = false no longer overrides commit, tag or branch. So a plugin can end up on a different revision. Nothing moves at startup, since vim.pack leaves an installed plugin alone: this applies to an install with no lockfile entry, and to an installed plugin at :ZPack update.
-- 2.x: tip of main. 3.0: c1, so an update moves it back
{ 'u/x', version = 'main', commit = c1 }
-- 2.x: default branch. 3.0: tip of dev
{ 'u/x', version = false, branch = 'dev' }
-- 2.x: default branch. 3.0: an install raises in vim.pack and the plugin isn't added; an update leaves it where it is
{ 'u/x', version = false, branch = 'no-such-branch' }{ 'u/dist', import = 'dist.plugins' } used to be only an import. Now u/dist is also registered, so it's installed, loaded and kept by :ZPack clean. Add lazy = true if it should only supply specs.
{ 'u/p', cmd = 'P', dependencies = { { 'u/d', dependencies = { 'u/c' } } } } used to load u/c at startup, with u/p and u/d waiting for :P. Now u/c waits too. Set lazy = false on it to keep it eager.
A lazy plugin's plugin/ scripts used to run before its dependencies were loaded. Now they run after, as in lazy.nvim, so plugin/ can use a dependency, and a dependency's plugin/ can't count on its parent's having run.
{ 'u/p', dependencies = { { 'u/d', optional = true } } } used to install u/d. As in lazy.nvim, it's now left out unless another spec asks for it without optional, and :ZPack clean deletes an installed copy.
A plugin whose function-form opts threw used to stay unloaded: an eager one had no keys mapped and was left for :ZPack load, and a lazy one failed the require or key that loaded it, as did a lazy plugin listing it under dependencies. An install's build was skipped until :ZPack build. Now, as in lazy.nvim, the error is reported once and the plugin loads without its config or setup(): its keys are mapped, what loaded it goes on, and build runs. A dependency that fails to load in another way is reported and no longer stops a lazy parent, as in lazy.nvim.
{ 'u/p', dependencies = { { 'u/d', enabled = false } } } used to remove u/p too, with a warning. Now u/p stays, without u/d and without a warning. To remove both, disable the parent:
{ 'u/p', enabled = false, dependencies = { 'u/d' } }opts in a dependencies spec of a plugin that is disabled, or left out as optional, used to reach that dependency when another spec kept it. Now that spec goes with the plugin listing it. LazyVim's lang extras have this shape but lose nothing: an optional nvim-lint or nvim-dap carrying { 'mason-org/mason.nvim', opts = { ensure_installed = ... } } never added to mason's list in 2.x, which ignored opts_extend, so LazyVim's own mason spec replaced the list. 3.0 adds it when another spec lists that plugin without optional. Move what should stay to a spec that stays:
-- 2.x: u/d gets { a = 1, b = 2 }. 3.0: only { b = 2 }
{ 'u/p', enabled = false, dependencies = { { 'u/d', opts = { a = 1 } } } },
{ 'u/d', opts = { b = 2 } },
-- 3.0
{ 'u/p', enabled = false, dependencies = { 'u/d' } },
{ 'u/d', opts = { a = 1, b = 2 } },A plugin declared under specs goes the same way, whether its parent is disabled or left out as optional. { 'u/p', enabled = false, specs = { { 'u/s' } } } used to install u/s. Now it doesn't, and :ZPack clean deletes an installed copy. List u/s at the top level to keep it.
cond on a spec with an import used to be ignored. Now a false cond means the module isn't read and its plugins aren't registered, so they aren't installed and :ZPack clean and :ZPack sync delete the ones that are.
A function-form cond gets no plugin argument at an import, as in lazy.nvim, so cond = function(p) return p.name end throws there, which reports the error and skips the import. On { 'user/repo', import = '...' } it also runs for the plugin, with the plugin.
To keep the plugins installed but unloaded, set cond on the plugins themselves or use defaults.cond, which doesn't apply to the import:
-- 2.x, and deletes the plugins on :ZPack clean in 3.0
{ import = 'plugins.work', cond = is_work_machine }
-- lua/plugins/work.lua, for 3.0
return { { 'user/repo', cond = is_work_machine } }When setup() runs from init.lua, eager plugins now reach 'runtimepath' with the highest priority first and a plugin's dependencies ahead of it. In 2.x the order was the reverse. Two things follow:
plugin/scripts run in that order: a higherpriorityruns first, and an eager dependency runs before the eager plugin listing it. A lazy plugin that loads during startup, such as a lazy dependency or one arequireloads, has no set place among them.- When two plugins ship the same runtime file, such as
colors/x.lua, the first on 'runtimepath' wins. That used to be the lowerpriorityplugin and is now the higher one.
config order hasn't changed. Swapping the two plugins' priority brings back the old plugin/ order and the old winner of a shadowed file, but it also swaps their config order. For a plugin and its dependency, swapping changes nothing, since the dependency comes first either way.
When setup() runs once startup has passed plugin/, such as at VimEnter, eager plugins' plugin/ and after/plugin/ scripts now run. They used to be skipped.
A require of an eager plugin's module from an init now loads that plugin there, with its dependencies and config, as in lazy.nvim. It used to fail with "module not found", since eager plugins reach 'runtimepath' after every init. So an init guarded with pcall(require, ...) now gets the module, and that plugin's config runs before the other eager plugins', whatever their priority, and before its own init if that comes later. When setup() runs from init.lua, its plugin/ scripts still run in their usual place, but a lazy dependency it loads sources after the eager plugins.
A lazy plugin's cmd, event, ft and keys are now set up before any init runs, as in lazy.nvim. They used to be set up after the eager plugins' config, so vim.cmd('Foo') from an init failed with "Not an editor command" and an event, filetype or key an init triggered didn't load the plugin. Now these load it there. With them set up earlier:
- An event an eager plugin's
configtriggers, such asUser X, orColorSchemefromvim.cmd.colorscheme(), loads the lazy plugins waiting on it there. - An autocmd an
initor eagerconfigcreates runs after the lazy plugin waiting on the same event has loaded. It used to run before. - A mapping an
initor eagerconfigsets on a lazy plugin'skeys, or a command it defines with a lazy plugin'scmdname, replaces zpack's, so that key or command no longer loads the plugin. It used to be the other way round. When the plugin loads some other way, its own mapping or command replaces yours, except that a:commandwithout!in itsplugin/fails with E174 (lazy.nvim deletes your command first). Drop one of the two. - A function-form
cmd,event,ftorkeysis called before the inits too, so it doesn't see avim.ganinitor eagerconfigsets. Nor does anftplugin'sftdetect/when filetype detection is already on assetup()runs, atVimEnteror after afiletype onininit.lua. When it isn't on yet,ftdetect/runs after the eager plugins'config, or as the plugin loads if aninitor eagerconfigturned detection on before that. - A
requirein such a function loads the plugin holding the module right there: itsconfigruns before everyinit, its own included, and before the other eager plugins'config, whatever theirpriority. A lazy plugin used to load after the eager plugins'config, and an eager one in itspriorityturn; lazy.nvim loads it after the inits. Arequireof an eager plugin's module withmodule = falsefinds nothing there. Require inside the key's or command's function instead, as LazyVim's harpoon2keysdo. Aneventorftfunction has no such place: return a static list, and use the module in the plugin'sconfigor an autocmd's callback.
VeryLazy plugins still load once the eager plugins have.
When setup() runs from init.lua, a lazy plugin that an init or an eager config loads by its trigger runs its plugin/ scripts as it loads and again at native startup, as a plugin init.lua itself loads by its trigger already did. lazy.nvim runs them once.
A key set to vim.NIL is now removed at the top level of opts, even with only one spec, and in a table merged into one an earlier spec set at the same place. It used to reach the plugin as vim.NIL.
To hand a plugin a JSON null, as some LSP settings need, set it from function-form opts, or nest it in a table no earlier spec sets:
opts = function(_, opts)
opts.setting = vim.NIL
return opts
endPlugins sharing an ft load together in priority order and re-trigger BufReadPre, BufReadPost and FileType once, after the last of them. So a plugin's config no longer sees the ftplugin or BufRead handler effects of an earlier plugin in the group, such as a vim.b variable, even with dependencies declared. lazy.nvim behaves the same way.
Read such values from a FileType handler or the plugin's own ftplugin instead of from config.
The buffer usually ends up in the same state, with two exceptions:
- If several plugins ship an
ftpluginguarded byb:did_ftpluginthat don't setb:undo_ftplugin, the lowestpriorityplugin's now runs instead of the highest's. - If a plugin's
configloads another buffer of that filetype,FileTypehandlers of plugins still pending in the group miss that buffer. Declaring the pending plugin as a dependency avoids this.
When setup() runs from init.lua, an eager plugin normally goes on 'runtimepath' from the directory vim.pack installed it in, not through :packadd!. A copy with the same name elsewhere on 'packpath' no longer joins 'runtimepath', and a 'packpath' that doesn't include vim.pack's directory no longer stops the plugin loading. Lazy plugins still use :packadd.
To use a second copy on purpose, add it yourself with vim.opt.runtimepath:prepend(path). Otherwise remove the leftover copy.
:ZPack clean, and :ZPack sync after its update, list what they would delete and ask, defaulting to No. A headless run deletes nothing unless you add !, as in :ZPack! clean, which is for scripts. defaults.confirm = false doesn't skip this: it only means don't ask before installing.
A plugin zpack can't see looks unused, so read the list if startup reported an error: a spec module that throws or doesn't return a table, an import that finds no module, a module of fields with no plugin source, entries after [1] in a spec with fields, as in { 'a/b', 'c/d', opts = {} }, or a failed vim.pack.add: a typo in a url lists the plugin you meant, and a call that fails as a whole, such as for a src of the wrong form, lists every plugin. Only a module that throws or doesn't return a table was reported in 2.x. setup() with no spec is the one import that may find nothing: no lua/plugins/, or one that is empty or holds only dotfiles such as .gitkeep, is nothing to report.
:ZPack sync still updates when a spec can't be read. A plugin with pin = true in that spec moves, because pin exists only in zpack's specs. Its version or tag holds, since vim.pack takes an inactive plugin's version from the lockfile. :ZPack restore doesn't undo the move unless the lockfile is in git, because the update rewrites the revision in the lockfile.
An import whose gitignored module isn't on this machine warns at every start. To stop that, say so with enabled:
{
import = 'plugins.machine_local',
enabled = function()
return vim.uv.fs_stat(vim.fn.stdpath('config') .. '/lua/plugins/machine_local.lua') ~= nil
end,
}A helper in an imported directory, such as lua/plugins/util.lua returning { on_attach = fn }: it reads as a spec with no source. Move it out of the directory.
- An
importof a plugin's module, with the plugin listed apart as in{ { 'u/dist' }, { import = 'dist.plugins.extra' } }, used to import nothing. Now zpack finds the module in a listed plugin named after it, hereu/dist, so what it declares is installed and loaded. If you had such a line doing nothing, remove it to keep it that way. event = {}orcmd = {}used to leave a plugin lazy, with no trigger to load it. Now it's eager.lazy = truekeeps it lazy.- Plain tables in
optsare copied when handed toconfigandsetup(), soopts.xisn't the table you wrote. A table with a metatable other thanvim.empty_dict()'s is still passed by reference. lua/plugins/*.luaimport by sorted module name, where it used to be file system order, and later specs win:foocomes beforefoo-extra, sofoo-extra'soptsnow overridefoo's.- A table listed both at the top level and under a lazy plugin's
dependenciesused to stay lazy. Now it's a plugin of its own and loads at startup, inlocal d = { 'u/d' }with{ d, { 'u/p', cmd = 'P', dependencies = { d } } }.lazy = trueon the table keeps it lazy. - A
nilin a spec list used to end the list.{ { 'u/x' }, flag and { 'u/y' } or nil, { 'u/d' } }now installsu/dwhenflagis false. - A function-form
event,cmd,ftorkeysthat throws used to leave the plugin loading at startup. The error is still reported, but the plugin now stays lazy, and only its other triggers or arequireof its modules load it. - A lazy plugin now drops its key proxies as it loads, before its scripts run, however it loads (a
require, itscmd, one of its keys), as in lazy.nvim. A proxy another pending plugin also lists stays. They used to stay until each was pressed, so a plugin that maps a key only when it's free, as flash.nvim does for;and,, found zpack's proxy there and skipped it, and the first press then fell through to the native key. And a key that two lazy plugins list stays a proxy until both have loaded: when one loaded by arequire, its own mapping used to replace the proxy, so the other never loaded from that key. opts_extendused to be ignored. Now it applies, so lists that used to replace each other are joined:{ 'u/x', opts_extend = { 'l' }, opts = { l = { 'a' } } }beside{ 'u/x', opts = { l = { 'b' } } }gave{ 'b' }and now gives{ 'a', 'b' }.- A bare string in a spec list used to be reported as an invalid spec and skipped.
{ 'u/a', { 'u/b' } }now installs both. A string without an owner, as in a module returning{ 'lua_ls', 'rust_analyzer' }, is still skipped with a warning. - A
requireused to load a lazy plugin whose name ormainlooked like the module's name, or whoselua/had only the module's top-level name. So an optionalpcall(require, 'mini.icons')at startup loaded and configured a lazymini.pairs, andpcall(require, 'nvim-treesitter.configs')loaded nvim-treesitter's main branch, which has no such module. As in lazy.nvim, arequirenow loads only the plugin holding the module's file underlua/, and fails as usual when none does. When two do, the one named like the top-level module loads, sorequire('mini.ai')loadsmini.nvim, not a standalonemini.ailisted beside it. A plugin that makes a module at load time, fromplugin/orpackage.preload, needs a trigger of its own. :colorscheme xloads the plugin holdingcolors/x.vimorcolors/x.luafirst, with itsconfig, if it hasn't loaded, as in lazy.nvim. It used to source the file through 'packpath' and leave the plugin unloaded, so a Vim colors file calling its plugin'sautoload/, as gruvbox-material's, everforest's and sonokai's do, failed with E117 from aninit, and for a lazy plugin at any time. A lazy colorscheme plugin now loads when its scheme is set. So does an eager one set from an earlier plugin'sconfig(a higherpriority, or the same one listed first), as themery.nvim restores its saved scheme: it used to get only its colors file there and itsconfigin its turn, so itsoptswere lost at startup. Withsetup()atVimEnteror from aplugin/script it still gets only its colors file there; give it the higherpriority.- A lazy
ftplugin'sftdetect/is sourced once, as in lazy.nvim. It used to be sourced again as the plugin loaded, so anft 42D9 detect/that runsset filetype=, as cql-vim's and vim-godot's do, firedFileTypetwice for every later file of that type, running ftplugins and LSP attach twice. A lazy plugininit.lualoads after your config turned filetype detection on, as aninitrunningsyntax enabledoes, now has itsftdetect/sourced, as:packaddwould. It used to have none. - With
filetype on,filetype plugin indent onorsyntax onbeforesetup()ininit.lua, an eager plugin'sftdetect/now runs, as in lazy.nvim, so LazyVim's vimtex (lazy = false) opens a.clsastex, notst. It used to be lost, since Neovim sources noftdetect/once detection is on. One whoseset filetype=sets a type Neovim detects too, as vim-godot's.gd, then firesFileTypetwice per file, as a plain start package does. setup({ profiling = ... })is ignored without a word, andprint_profileandget_profileare gone fromzpack.module_loader.
2.0.0 removes every option and command that was deprecated during the 1.x series. The renamed options (confirm, disable_vim_loader, plugins_dir) emitted runtime warnings throughout 1.x; the legacy :Z* commands are removed in favor of the :ZPack <subcommand> dispatcher. Removed options are now ignored silently, so update your setup() call before upgrading:
| Removed in 2.0 | Replacement |
|---|---|
setup({ confirm = … }) |
setup({ defaults = { confirm = … } }) |
setup({ disable_vim_loader = true }) |
setup({ performance = { vim_loader = false } }) |
setup({ plugins_dir = 'dir' }) |
a { import = 'dir' } entry in your spec list |
setup({ cmd_prefix = 'Z' }) |
setup({ cmd_name = 'Z' }) |
setup({ auto_import = … }) |
removed — was already a no-op in 1.x |
require('zpack').add(…) |
removed — was already a no-op in 1.x |
The per-action :Z* commands are now subcommands of a single :ZPack command:
| 1.x | 2.0 |
|---|---|
:ZUpdate |
:ZPack update |
:ZRestore |
:ZPack restore |
:ZClean |
:ZPack clean |
:ZBuild |
:ZPack build |
:ZLoad |
:ZPack load |
:ZDelete |
:ZPack delete |
The command name is configurable via cmd_name (default ZPack). If you relied on the old cmd_prefix = 'Z', set cmd_name = 'Z' to type :Z update, :Z clean, etc. The ! bang is still supported where it was before (e.g. :ZPack! build, :ZPack! delete).