aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/pack.lua
diff options
context:
space:
mode:
authorEvgeni Chasnovski <evgeni.chasnovski@gmail.com>2026-07-31 15:17:00 +0300
committerEvgeni Chasnovski <evgeni.chasnovski@gmail.com>2026-07-31 19:20:34 +0300
commit9d12df0398499d22bb509ad5c209dd608095596d (patch)
treec885120cf28814654dc92727a1c934f6164e22e1 /runtime/lua/vim/pack.lua
parent4cdd6d76c5ad2714aa5c056c8b16fca408120e61 (diff)
downloadrneovim-9d12df0398499d22bb509ad5c209dd608095596d.tar.gz
rneovim-9d12df0398499d22bb509ad5c209dd608095596d.tar.bz2
rneovim-9d12df0398499d22bb509ad5c209dd608095596d.zip
feat(pack): add support for sourcing manifest scripts
Problem: No way for plugins to define hooks that would be executed during plugin lifecycle. Like after install/update or before update/delete. Solution: Automatically source scripts defined in plugin manifest file (if any) after triggering corresponding `PackChanged{,Pre}` events. During sourcing make some termporary adjustments: - Current directory is set to plugin's root to simplify execution of CLI calls like `make build`. - Plugin's path is ensured to be inside 'runtimepath' to allow using `require('plugin-module')` inside manifest scripts. The reason to execute after triggering event is so that there is a possibility for users to execute code both before the script (exactly on event) and after the script (more-or-less via `vim.schedule` called on the event).
Diffstat (limited to 'runtime/lua/vim/pack.lua')
-rw-r--r--runtime/lua/vim/pack.lua53
1 files changed, 53 insertions, 0 deletions
diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua
index a69684de75..0e986d3196 100644
--- a/runtime/lua/vim/pack.lua
+++ b/runtime/lua/vim/pack.lua
@@ -236,6 +236,14 @@
---
---Plugins can come with a special top level `pkg.json` manifest file with extra
---information. If present, `vim.pack` uses it for improved user experience:
+---- Apply |:source| for scripts after triggering corresponding |vim.pack-events|.
+--- This allows plugins to define hooks that will be executed during plugin's lifetime.
+--- Sourcing is done with special context:
+--- - The |current-directory| is temporarily set to plugin's root (to make
+--- it easier to run |vim.system()| commands).
+--- - Plugin's path is temporarily ensured to be inside |'runtimepath'| (so script
+--- can use |require()| with plugin's module, possibly with explicit |package.loaded|
+--- reset inside `"update"` scripts).
---- Running |:checkhealth| for `vim.pack` will perform extra checks to ensure
--- healthy plugin installation.
---
@@ -248,6 +256,12 @@
--- "engines": {
--- "nvim": ">=0.13.0",
--- "vim": ">=9.1.0"
+--- },
+--- "scripts": {
+--- "install": "scripts/install.lua",
+--- "preupdate": "scripts/preupdate.vim",
+--- "update": "scripts/update.vim",
+--- "preuninstall": "scripts/preuninstall.lua"
--- }
---}
---```
@@ -540,11 +554,21 @@ local n_active_plugins = 0
--- @field nvim? string Version range for Nvim.
--- @field vim? string Version range for Vim.
+--- @class vim.pack.ManifestScripts
+--- @inlinedoc
+--- @field install? string Post install script.
+--- @field preupdate? string Pre update script.
+--- @field update? string Post update script.
+--- @field preuninstall? string Pre delete script.
+
--- @class vim.pack.Manifest
--- @field name? string Plugin name
--- @field description? string Plugin description
--- Supported engine versions. Values should be |vim.version.range()| compatible specs.
--- @field engines? vim.pack.ManifestEngines
+--- Script locations (relative to plugin's root) to |:source| after triggering
+--- corresponding |vim.pack-events|.
+--- @field scripts? vim.pack.ManifestScripts
--- @param path string
--- @return vim.pack.Manifest?
@@ -566,14 +590,43 @@ local function manifest_read(path)
return (ok and type(res) == 'table') and res or {}
end
+--- @param p vim.pack.Plug
+--- @param name string
+local function source_manifest_script(p, name)
+ local manifest = manifest_read(p.path)
+ if not (type(manifest) == 'table' and (manifest.scripts or {})[name]) then
+ return
+ end
+
+ local script_path = vim.fs.joinpath(p.path, (manifest.scripts or {})[name])
+ vim._with({ cwd = p.path, o = { runtimepath = vim.o.runtimepath } }, function()
+ vim.cmd.packadd({ p.spec.name, bang = true })
+ ---@diagnostic disable-next-line: no-unknown
+ local ok, err = pcall(vim.cmd.source, { script_path, magic = { file = false, bar = false } })
+ if not ok then
+ notify(err, 'WARN')
+ end
+ end)
+end
+
+local manifest_script_name_map = {
+ PackChangedPre = { update = 'preupdate', delete = 'preuninstall' },
+ PackChanged = { install = 'install', update = 'update' },
+}
+
--- @param plugs vim.pack.Plug[]
--- @param event_name 'PackChangedPre'|'PackChanged'
--- @param kind 'install'|'update'|'delete'
local function trigger_events(plugs, event_name, kind)
+ local manifest_script_name = manifest_script_name_map[event_name][kind]
for _, p in ipairs(plugs) do
local active = active_plugins[p.path] ~= nil
local data = { active = active, kind = kind, spec = vim.deepcopy(p.spec), path = p.path }
api.nvim_exec_autocmds(event_name, { pattern = p.path, data = data })
+
+ if manifest_script_name then
+ source_manifest_script(p, manifest_script_name)
+ end
end
end