mirror of
https://github.com/neovim/neovim.git
synced 2025-12-09 08:02:38 +00:00
Problem: Lockfile can become out of sync with what is actually installed
on disk when user performs (somewhat reasonable) manual actions like:
- Delete lockfile and expect it to regenerate.
- Delete plugin directory without `vim.pack.del()`.
- Manually edit lock data in a bad way.
Solution: Synchronize lockfile data with installed plugins on every
lockfile read. In particular:
1. Install immediately all missing plugins with valid lock data.
This helps with "manually delete plugin directory" case by
prompting user to figure out how to properly delete a plugin.
2. Repair lock data for properly installed plugins.
This helps with "manually deleted lockfile", "manually edited
lockfile in an unexpected way", "installation terminated due to
timeout" cases.
3. Remove unrepairable corrupted lock data and their plugins. This
includes bad lock data for missing plugins and any lock data
for corrupted plugins (right now this only means that plugin
path is not a directory, but can be built upon).
Step 1 also improves usability in case there are lazy loaded plugins
that are rarely loaded (like on `FileType` event, for example):
- Previously starting with config+lockfile on a new machine only
installs rare `vim.pack.add()` plugin after it is called (while
an entry in lockfile would still be present). This could be
problematic if there is no Internet connection, for example.
- Now all plugins from the lockfile are installed before actually
executing the first `vim.pack.add()` call in 'init.lua'. And later
they are only loaded on a rare `vim.pack.add()` call.
---
Synchronizing lockfile on its every read makes it work more robustly
if other `vim.pack` functions are called without any `vim.pack.add()`.
---
Performance for a regular startup (good lockfile, everything is
installed) is not affected and usually even increased. The bottleneck
in this area is figuring out which plugins need to be installed.
Previously the check was done by `vim.uv.fs_stat()` for every plugin
in `vim.pack.add()`. Now it is replaced with a single `vim.fs.dir()`
traversal during lockfile sync while later using lockfile data to
figure out if plugin needs to be installed.
The single `vim.fs.dir` approach scales better than `vim.uv.fs_stat`,
but might be less performant if there are many plugins that will be
not loaded via `vim.pack.add()` during startup.
Rough estimate of how long the same steps (read lockfile and normalize
plugin array) take with a single `vim.pack.add()` filled with 43
plugins benchmarking:
- Before commit: ~700 ms
- After commit: ~550 ms
474 lines
20 KiB
Plaintext
474 lines
20 KiB
Plaintext
*pack.txt* Nvim
|
|
|
|
NVIM REFERENCE MANUAL
|
|
|
|
Extending Nvim
|
|
|
|
|
|
Type |gO| to see the table of contents.
|
|
|
|
==============================================================================
|
|
Using Vim packages *packages*
|
|
|
|
A Vim "package" is a directory that contains |plugin|s. Compared to normal
|
|
plugins, a package can...
|
|
- be downloaded as an archive and unpacked in its own directory, so the files
|
|
are not mixed with files of other plugins.
|
|
- be a git, mercurial, etc. repository, thus easy to update.
|
|
- contain multiple plugins that depend on each other.
|
|
- contain plugins that are automatically loaded on startup ("start" packages,
|
|
located in "pack/*/start/*") and ones that are only loaded when needed with
|
|
|:packadd| ("opt" packages, located in "pack/*/opt/*").
|
|
|
|
*runtime-search-path*
|
|
Nvim searches for |:runtime| files in:
|
|
- 1. all paths in 'runtimepath'
|
|
- 2. all "pack/*/start/*" dirs
|
|
|
|
Note that the "pack/*/start/*" paths are not explicitly included in
|
|
'runtimepath', so they will not be reported by ":set rtp" or "echo &rtp".
|
|
Scripts can use |nvim_list_runtime_paths()| to list all used directories, and
|
|
|nvim_get_runtime_file()| to query for specific files or sub-folders within
|
|
the runtime path. Example: >
|
|
" List all runtime dirs and packages with Lua paths.
|
|
:echo nvim_get_runtime_file("lua/", v:true)
|
|
|
|
Using a package and loading automatically ~
|
|
|
|
Let's assume your Nvim files are in "~/.local/share/nvim/site" and you want to
|
|
add a package from a zip archive "/tmp/foopack.zip": >
|
|
% mkdir -p ~/.local/share/nvim/site/pack/foo
|
|
% cd ~/.local/share/nvim/site/pack/foo
|
|
% unzip /tmp/foopack.zip
|
|
|
|
The directory name "foo" is arbitrary, you can pick anything you like.
|
|
|
|
You would now have these files under ~/.local/share/nvim/site: >
|
|
pack/foo/README.txt
|
|
pack/foo/start/foobar/plugin/foo.vim
|
|
pack/foo/start/foobar/syntax/some.vim
|
|
pack/foo/opt/foodebug/plugin/debugger.vim
|
|
|
|
On startup after processing your |config|, Nvim scans all directories in
|
|
'packpath' for plugins in "pack/*/start/*", then loads the plugins.
|
|
|
|
To allow for calling into package functionality while parsing your |vimrc|,
|
|
|:colorscheme| and |autoload| will both automatically search under 'packpath'
|
|
as well in addition to 'runtimepath'. See the documentation for each for
|
|
details.
|
|
|
|
In the example Nvim will find "pack/foo/start/foobar/plugin/foo.vim" and load
|
|
it.
|
|
|
|
If the "foobar" plugin kicks in and sets the 'filetype' to "some", Nvim will
|
|
find the syntax/some.vim file, because its directory is in the runtime search
|
|
path.
|
|
|
|
Nvim will also load ftdetect files, if there are any.
|
|
|
|
Note that the files under "pack/foo/opt" are not loaded automatically, only
|
|
the ones under "pack/foo/start". See |pack-add| below for how the "opt"
|
|
directory is used.
|
|
|
|
Loading packages automatically will not happen if loading plugins is disabled,
|
|
see |load-plugins|.
|
|
|
|
To load packages earlier, so that plugin/ files are sourced:
|
|
:packloadall
|
|
This also works when loading plugins is disabled. The automatic loading will
|
|
only happen once.
|
|
|
|
If the package has an "after" directory, that directory is added to the end of
|
|
'runtimepath', so that anything there will be loaded later.
|
|
|
|
|
|
Using a single plugin and loading it automatically ~
|
|
|
|
If you don't have a package but a single plugin, you need to create the extra
|
|
directory level: >
|
|
% mkdir -p ~/.local/share/nvim/site/pack/foo/start/foobar
|
|
% cd ~/.local/share/nvim/site/pack/foo/start/foobar
|
|
% unzip /tmp/someplugin.zip
|
|
|
|
You would now have these files: >
|
|
pack/foo/start/foobar/plugin/foo.vim
|
|
pack/foo/start/foobar/syntax/some.vim
|
|
|
|
From here it works like above.
|
|
|
|
|
|
Optional plugins ~
|
|
*pack-add*
|
|
To load an optional plugin from a pack use the `:packadd` command: >
|
|
:packadd foodebug
|
|
This searches for "pack/*/opt/foodebug" in 'packpath' and will find
|
|
~/.local/share/nvim/site/pack/foo/opt/foodebug/plugin/debugger.vim and source
|
|
it.
|
|
|
|
This could be done if some conditions are met. For example, depending on
|
|
whether Nvim supports a feature or a dependency is missing.
|
|
|
|
You can also load an optional plugin at startup, by putting this command in
|
|
your |config|: >
|
|
:packadd! foodebug
|
|
The extra "!" is so that the plugin isn't loaded if Nvim was started with
|
|
|--noplugin|.
|
|
|
|
It is perfectly normal for a package to only have files in the "opt"
|
|
directory. You then need to load each plugin when you want to use it.
|
|
|
|
|
|
Where to put what ~
|
|
|
|
Since color schemes, loaded with `:colorscheme`, are found below
|
|
"pack/*/start" and "pack/*/opt", you could put them anywhere. We recommend
|
|
you put them below "pack/*/opt", for example
|
|
"~/.config/nvim/pack/mycolors/opt/dark/colors/very_dark.vim".
|
|
|
|
Filetype plugins should go under "pack/*/start", so that they are always
|
|
found. Unless you have more than one plugin for a file type and want to
|
|
select which one to load with `:packadd`. E.g. depending on the compiler
|
|
version: >
|
|
if foo_compiler_version > 34
|
|
packadd foo_new
|
|
else
|
|
packadd foo_old
|
|
endif
|
|
|
|
The "after" directory is most likely not useful in a package. It's not
|
|
disallowed though.
|
|
|
|
==============================================================================
|
|
Creating Vim packages *package-create*
|
|
|
|
This assumes you write one or more plugins that you distribute as a package.
|
|
|
|
If you have two unrelated plugins you would use two packages, so that Vim
|
|
users can choose what they include or not. Or you can decide to use one
|
|
package with optional plugins, and tell the user to add the preferred ones
|
|
with `:packadd`.
|
|
|
|
Decide how you want to distribute the package. You can create an archive or
|
|
you could use a repository. An archive can be used by more users, but is a
|
|
bit harder to update to a new version. A repository can usually be kept
|
|
up-to-date easily, but it requires a program like "git" to be available.
|
|
You can do both, github can automatically create an archive for a release.
|
|
|
|
Your directory layout would be like this: >
|
|
start/foobar/plugin/foo.vim " always loaded, defines commands
|
|
start/foobar/plugin/bar.vim " always loaded, defines commands
|
|
start/foobar/autoload/foo.vim " loaded when foo command used
|
|
start/foobar/doc/foo.txt " help for foo.vim
|
|
start/foobar/doc/tags " help tags
|
|
opt/fooextra/plugin/extra.vim " optional plugin, defines commands
|
|
opt/fooextra/autoload/extra.vim " loaded when extra command used
|
|
opt/fooextra/doc/extra.txt " help for extra.vim
|
|
opt/fooextra/doc/tags " help tags
|
|
<
|
|
This allows for the user to do: >
|
|
mkdir ~/.local/share/nvim/site/pack
|
|
cd ~/.local/share/nvim/site/pack
|
|
git clone https://github.com/you/foobar.git myfoobar
|
|
|
|
Here "myfoobar" is a name that the user can choose, the only condition is that
|
|
it differs from other packages.
|
|
|
|
In your documentation you explain what the plugins do, and tell the user how
|
|
to load the optional plugin: >
|
|
:packadd! fooextra
|
|
|
|
You could add this packadd command in one of your plugins, to be executed when
|
|
the optional plugin is needed.
|
|
|
|
Run the `:helptags` command to generate the doc/tags file. Including this
|
|
generated file in the package means that the user can drop the package in the
|
|
pack directory and the help command works right away. Don't forget to re-run
|
|
the command after changing the plugin help: >
|
|
:helptags path/start/foobar/doc
|
|
:helptags path/opt/fooextra/doc
|
|
|
|
|
|
Dependencies between plugins ~
|
|
*packload-two-steps*
|
|
Suppose you have two plugins that depend on the same functionality. You can
|
|
put the common functionality in an autoload directory, so that it will be
|
|
found automatically. Your package would have these files:
|
|
|
|
pack/foo/start/one/plugin/one.vim >
|
|
call foolib#getit()
|
|
pack/foo/start/two/plugin/two.vim >
|
|
call foolib#getit()
|
|
pack/foo/start/lib/autoload/foolib.vim >
|
|
func foolib#getit()
|
|
|
|
This works, because start packages will be searched for autoload files, when
|
|
sourcing the plugins.
|
|
|
|
==============================================================================
|
|
Plugin manager *vim.pack*
|
|
|
|
WORK IN PROGRESS built-in plugin manager! Early testing of existing features
|
|
is appreciated, but expect breaking changes without notice.
|
|
|
|
Manages plugins only in a dedicated *vim.pack-directory* (see |packages|):
|
|
`$XDG_DATA_HOME/nvim/site/pack/core/opt`. `$XDG_DATA_HOME/nvim/site` needs to
|
|
be part of 'packpath'. It usually is, but might not be in cases like |--clean|
|
|
or setting |$XDG_DATA_HOME| during startup. Plugin's subdirectory name matches
|
|
plugin's name in specification. It is assumed that all plugins in the
|
|
directory are managed exclusively by `vim.pack`.
|
|
|
|
Uses Git to manage plugins and requires present `git` executable. Target
|
|
plugins should be Git repositories with versions as named tags following
|
|
semver convention `v<major>.<minor>.<patch>`.
|
|
|
|
The latest state of all managed plugins is stored inside a *vim.pack-lockfile*
|
|
located at `$XDG_CONFIG_HOME/nvim/nvim-pack-lock.json`. It is a JSON file that
|
|
is used to persistently track data about plugins. For a more robust config
|
|
treat lockfile like its part: put under version control, etc. In this case all
|
|
plugins from the lockfile will be installed at once and at lockfile's revision
|
|
(instead of inferring from `version`). Should not be edited by hand. Corrupted
|
|
data for installed plugins is repaired (including after deleting whole file),
|
|
but `version` fields will be missing for not yet added plugins.
|
|
|
|
Example workflows ~
|
|
|
|
Basic install and management:
|
|
• Add |vim.pack.add()| call(s) to 'init.lua': >lua
|
|
|
|
vim.pack.add({
|
|
-- Install "plugin1" and use default branch (usually `main` or `master`)
|
|
'https://github.com/user/plugin1',
|
|
|
|
-- Same as above, but using a table (allows setting other options)
|
|
{ src = 'https://github.com/user/plugin1' },
|
|
|
|
-- Specify plugin's name (here the plugin will be called "plugin2"
|
|
-- instead of "generic-name")
|
|
{ src = 'https://github.com/user/generic-name', name = 'plugin2' },
|
|
|
|
-- Specify version to follow during install and update
|
|
{
|
|
src = 'https://github.com/user/plugin3',
|
|
-- Version constraint, see |vim.version.range()|
|
|
version = vim.version.range('1.0'),
|
|
},
|
|
{
|
|
src = 'https://github.com/user/plugin4',
|
|
-- Git branch, tag, or commit hash
|
|
version = 'main',
|
|
},
|
|
})
|
|
|
|
-- Plugin's code can be used directly after `add()`
|
|
plugin1 = require('plugin1')
|
|
<
|
|
• Restart Nvim (for example, with |:restart|). Plugins that were not yet
|
|
installed will be available on disk after `add()` call. Their revision is
|
|
taken from |vim.pack-lockfile| (if present) or inferred from the `version`.
|
|
• To update all plugins with new changes:
|
|
• Execute |vim.pack.update()|. This will download updates from source and
|
|
show confirmation buffer in a separate tabpage.
|
|
• Review changes. To confirm all updates execute |:write|. To discard
|
|
updates execute |:quit|.
|
|
• (Optionally) |:restart| to start using code from updated plugins.
|
|
|
|
Switch plugin's version:
|
|
• Update 'init.lua' for plugin to have desired `version`. Let's say, plugin
|
|
named 'plugin1' has changed to `vim.version.range('*')`.
|
|
• |:restart|. The plugin's actual revision on disk is not yet changed. Only
|
|
plugin's `version` in |vim.pack-lockfile| is updated.
|
|
• Execute `vim.pack.update({ 'plugin1' })`.
|
|
• Review changes and either confirm or discard them. If discarded, revert any
|
|
changes in 'init.lua' as well or you will be prompted again next time you
|
|
run |vim.pack.update()|.
|
|
|
|
Switch plugin's source:
|
|
• Update 'init.lua' for plugin to have desired `src`.
|
|
• |:restart|. This will cleanly reinstall plugin from the new source.
|
|
|
|
Freeze plugin from being updated:
|
|
• Update 'init.lua' for plugin to have `version` set to current revision. Get
|
|
it from |vim.pack-lockfile| (plugin's field `rev`; looks like `abc12345`).
|
|
• |:restart|.
|
|
|
|
Unfreeze plugin to start receiving updates:
|
|
• Update 'init.lua' for plugin to have `version` set to whichever version you
|
|
want it to be updated.
|
|
• |:restart|.
|
|
|
|
Revert plugin after an update:
|
|
• Locate plugin's revision at working state. For example:
|
|
• If there is a previous version of |vim.pack-lockfile| (like from version
|
|
control history), use it to get plugin's `rev` field.
|
|
• If there is a log file ("nvim-pack.log" at "log" |stdpath()|), open it and
|
|
navigate to latest updates (at the bottom). Locate lines about plugin
|
|
update details and use revision from "State before".
|
|
• Freeze plugin to target revision (set `version` and |:restart|).
|
|
• Run `vim.pack.update({ 'plugin-name' }, { force = true })` to make plugin
|
|
state on disk follow target revision. |:restart|.
|
|
• When ready to deal with updating plugin, unfreeze it.
|
|
|
|
Remove plugins from disk:
|
|
• Use |vim.pack.del()| with a list of plugin names to remove. Make sure their
|
|
specs are not included in |vim.pack.add()| call in 'init.lua' or they will
|
|
be reinstalled.
|
|
|
|
Available events to hook into ~
|
|
• *PackChangedPre* - before trying to change plugin's state.
|
|
• *PackChanged* - after plugin's state has changed.
|
|
|
|
Each event populates the following |event-data| fields:
|
|
• `active` - whether plugin was added via |vim.pack.add()| to current session.
|
|
• `kind` - one of "install" (install on disk; before loading), "update"
|
|
(update already installed plugin; might be not loaded), "delete" (delete
|
|
from disk).
|
|
• `spec` - plugin's specification with defaults made explicit.
|
|
• `path` - full path to plugin's directory.
|
|
|
|
These events can be used to execute plugin hooks. For example: >lua
|
|
local hooks = function(ev)
|
|
-- Use available |event-data|
|
|
local name, kind = ev.data.spec.name, ev.data.kind
|
|
|
|
-- Run build script after plugin's code has changed
|
|
if name == 'plug-1' and (kind == 'install' or kind == 'update') then
|
|
vim.system({ 'make' }, { cwd = ev.data.path })
|
|
end
|
|
|
|
-- If action relies on code from the plugin (like user command or
|
|
-- Lua code), make sure to explicitly load it first
|
|
if name == 'plug-2' and kind == 'update' then
|
|
if not ev.data.active then
|
|
vim.cmd.packadd('plug-2')
|
|
end
|
|
vim.cmd('PlugTwoUpdate')
|
|
require('plug2').after_update()
|
|
end
|
|
end
|
|
|
|
-- If hooks need to run on install, run this before `vim.pack.add()`
|
|
vim.api.nvim_create_autocmd('PackChanged', { callback = hooks })
|
|
<
|
|
|
|
|
|
*vim.pack.Spec*
|
|
|
|
Fields: ~
|
|
• {src} (`string`) URI from which to install and pull updates. Any
|
|
format supported by `git clone` is allowed.
|
|
• {name}? (`string`) Name of plugin. Will be used as directory name.
|
|
Default: `src` repository name.
|
|
• {version}? (`string|vim.VersionRange`) Version to use for install and
|
|
updates. Can be:
|
|
• `nil` (no value, default) to use repository's default
|
|
branch (usually `main` or `master`).
|
|
• String to use specific branch, tag, or commit hash.
|
|
• Output of |vim.version.range()| to install the
|
|
greatest/last semver tag inside the version constraint.
|
|
• {data}? (`any`) Arbitrary data associated with a plugin.
|
|
|
|
|
|
add({specs}, {opts}) *vim.pack.add()*
|
|
Add plugin to current session
|
|
• For each specification check that plugin exists on disk in
|
|
|vim.pack-directory|:
|
|
• If exists, check if its `src` is the same as input. If not - delete
|
|
immediately to clean install from the new source. Otherwise do
|
|
nothing.
|
|
• If doesn't exist, install it by downloading from `src` into `name`
|
|
subdirectory (via partial blobless `git clone`) and update revision to
|
|
match `version` (via `git checkout`). Plugin will not be on disk if
|
|
any step resulted in an error.
|
|
• For each plugin execute |:packadd| (or customizable `load` function)
|
|
making it reachable by Nvim.
|
|
|
|
Notes:
|
|
• Installation is done in parallel, but waits for all to finish before
|
|
continuing next code execution.
|
|
• If plugin is already present on disk, there are no checks about its
|
|
current revision. The specified `version` can be not the one actually
|
|
present on disk. Execute |vim.pack.update()| to synchronize.
|
|
• Adding plugin second and more times during single session does nothing:
|
|
only the data from the first adding is registered.
|
|
|
|
Parameters: ~
|
|
• {specs} (`(string|vim.pack.Spec)[]`) List of plugin specifications.
|
|
String item is treated as `src`.
|
|
• {opts} (`table?`) A table with the following fields:
|
|
• {load}?
|
|
(`boolean|fun(plug_data: {spec: vim.pack.Spec, path: string})`)
|
|
Load `plugin/` files and `ftdetect/` scripts. If `false`,
|
|
works like `:packadd!`. If function, called with plugin
|
|
data and is fully responsible for loading plugin. Default
|
|
`false` during |init.lua| sourcing and `true` afterwards.
|
|
• {confirm}? (`boolean`) Whether to ask user to confirm
|
|
initial install. Default `true`.
|
|
|
|
del({names}) *vim.pack.del()*
|
|
Remove plugins from disk
|
|
|
|
Parameters: ~
|
|
• {names} (`string[]`) List of plugin names to remove from disk. Must
|
|
be managed by |vim.pack|, not necessarily already added to
|
|
current session.
|
|
|
|
get({names}, {opts}) *vim.pack.get()*
|
|
Gets |vim.pack| plugin info, optionally filtered by `names`.
|
|
|
|
Parameters: ~
|
|
• {names} (`string[]?`) List of plugin names. Default: all plugins
|
|
managed by |vim.pack|.
|
|
• {opts} (`table?`) A table with the following fields:
|
|
• {info} (`boolean`) Whether to include extra plugin info.
|
|
Default `true`.
|
|
|
|
Return: ~
|
|
(`table[]`) A list of objects with the following fields:
|
|
• {active} (`boolean`) Whether plugin was added via |vim.pack.add()|
|
|
to current session.
|
|
• {branches}? (`string[]`) Available Git branches (first is default).
|
|
Missing if `info=false`.
|
|
• {path} (`string`) Plugin's path on disk.
|
|
• {rev} (`string`) Current Git revision.
|
|
• {spec} (`vim.pack.SpecResolved`) A |vim.pack.Spec| with resolved
|
|
`name`.
|
|
• {tags}? (`string[]`) Available Git tags. Missing if `info=false`.
|
|
|
|
update({names}, {opts}) *vim.pack.update()*
|
|
Update plugins
|
|
• Download new changes from source.
|
|
• Infer update info (current/target revisions, changelog, etc.).
|
|
• Depending on `force`:
|
|
• If `false`, show confirmation buffer. It lists data about all set to
|
|
update plugins. Pending changes starting with `>` will be applied
|
|
while the ones starting with `<` will be reverted. It has dedicated
|
|
buffer-local mappings:
|
|
• |]]| and |[[| to navigate through plugin sections.
|
|
Some features are provided via LSP:
|
|
• 'textDocument/documentSymbol' (`gO` via |lsp-defaults| or
|
|
|vim.lsp.buf.document_symbol()|) - show structure of the buffer.
|
|
• 'textDocument/hover' (`K` via |lsp-defaults| or
|
|
|vim.lsp.buf.hover()|) - show more information at cursor. Like
|
|
details of particular pending change or newer tag.
|
|
• 'textDocument/codeAction' (`gra` via |lsp-defaults| or
|
|
|vim.lsp.buf.code_action()|) - show code actions available for
|
|
"plugin at cursor". Like "delete", "update", or "skip updating".
|
|
Execute |:write| to confirm update, execute |:quit| to discard the
|
|
update.
|
|
• If `true`, make updates right away.
|
|
|
|
Notes:
|
|
• Every actual update is logged in "nvim-pack.log" file inside "log"
|
|
|stdpath()|.
|
|
|
|
Parameters: ~
|
|
• {names} (`string[]?`) List of plugin names to update. Must be managed
|
|
by |vim.pack|, not necessarily already added to current
|
|
session. Default: names of all plugins managed by |vim.pack|.
|
|
• {opts} (`table?`) A table with the following fields:
|
|
• {force}? (`boolean`) Whether to skip confirmation and make
|
|
updates immediately. Default `false`.
|
|
|
|
|
|
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
|