commit 4129fa5bac3f42b9107714e1fae37e5ee216f9b0
parent 67832710a5d32a19aaf1b2934c428f02816ea22e
Author: Justin M. Keyes <justinkz@gmail.com>
Date: Thu, 23 Oct 2025 18:29:24 -0400
Merge #36299 improve PackChanged event
Diffstat:
3 files changed, 306 insertions(+), 237 deletions(-)
diff --git a/runtime/doc/pack.txt b/runtime/doc/pack.txt
@@ -300,11 +300,38 @@ Available events to hook into ~
• *PackChanged* - after plugin's state has changed.
Each event populates the following |event-data| fields:
-• `kind` - one of "install" (install on disk), "update" (update existing
- plugin), "delete" (delete from disk).
+• `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*
diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua
@@ -91,16 +91,44 @@
---- 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 ~
+---Available events to hook into ~
---
---- - [PackChangedPre]() - before trying to change plugin's state.
---- - [PackChanged]() - after plugin's state has changed.
+---- [PackChangedPre]() - before trying to change plugin's state.
+---- [PackChanged]() - after plugin's state has changed.
---
---- Each event populates the following |event-data| fields:
---- - `kind` - one of "install" (install on disk), "update" (update existing
---- plugin), "delete" (delete from disk).
---- - `spec` - plugin's specification with defaults made explicit.
---- - `path` - full path to plugin's directory.
+---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 })
+---```
local api = vim.api
local uv = vim.uv
@@ -408,11 +436,18 @@ local function plug_list_from_names(names)
return plugs
end
+--- Map from plugin path to its data.
+--- Use map and not array to avoid linear lookup during startup.
+--- @type table<string, { plug: vim.pack.Plug, id: integer }?>
+local active_plugins = {}
+local n_active_plugins = 0
+
--- @param p vim.pack.Plug
--- @param event_name 'PackChangedPre'|'PackChanged'
--- @param kind 'install'|'update'|'delete'
local function trigger_event(p, event_name, kind)
- local data = { kind = kind, spec = vim.deepcopy(p.spec), path = p.path }
+ 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 })
end
@@ -580,14 +615,8 @@ end
--- @async
--- @param p vim.pack.Plug
--- @param timestamp string
---- @param skip_same_sha boolean
-local function checkout(p, timestamp, skip_same_sha)
+local function checkout(p, timestamp)
infer_states(p)
- if skip_same_sha and p.info.sha_head == p.info.sha_target then
- return
- end
-
- trigger_event(p, 'PackChangedPre', 'update')
local msg = ('vim.pack: %s Stash before checkout'):format(timestamp)
git_cmd({ 'stash', '--quiet', '--message', msg }, p.path)
@@ -596,8 +625,6 @@ local function checkout(p, timestamp, skip_same_sha)
plugin_lock.plugins[p.spec.name].rev = p.info.sha_target
- trigger_event(p, 'PackChanged', 'update')
-
-- (Re)Generate help tags according to the current help files.
-- Also use `pcall()` because `:helptags` errors if there is no 'doc/'
-- directory or if it is empty.
@@ -630,12 +657,8 @@ local function install_list(plug_list, confirm)
-- Prefer revision from the lockfile instead of using `version`
p.info.sha_target = (plugin_lock.plugins[p.spec.name] or {}).rev
- -- Do not skip checkout even if HEAD and target have same commit hash to
- -- have new repo in expected detached HEAD state and generated help files.
- checkout(p, timestamp, false)
+ checkout(p, timestamp)
- -- "Install" event is triggered after "update" event intentionally to have
- -- it indicate "plugin is installed in its correct initial version"
trigger_event(p, 'PackChanged', 'install')
end
run_list(plug_list, do_install, 'Installing plugins')
@@ -686,12 +709,6 @@ local function infer_update_details(p)
p.info.update_details = table.concat(newer_semver_tags, '\n')
end
---- Map from plugin path to its data.
---- Use map and not array to avoid linear lookup during startup.
---- @type table<string, { plug: vim.pack.Plug, id: integer }?>
-local active_plugins = {}
-local n_active_plugins = 0
-
--- @param plug vim.pack.Plug
--- @param load boolean|fun(plug_data: {spec: vim.pack.Spec, path: string})
local function pack_add(plug, load)
@@ -1035,9 +1052,11 @@ function M.update(names, opts)
-- Compute change info: changelog if any, new tags if nothing to update
infer_update_details(p)
- -- Checkout immediately if not need to confirm
- if opts.force then
- checkout(p, timestamp, true)
+ -- Checkout immediately if no need to confirm
+ if opts.force and p.info.sha_head ~= p.info.sha_target then
+ trigger_event(p, 'PackChangedPre', 'update')
+ checkout(p, timestamp)
+ trigger_event(p, 'PackChanged', 'update')
end
end
local progress_title = opts.force and (opts._offline and 'Applying updates' or 'Updating')
@@ -1068,7 +1087,9 @@ function M.update(names, opts)
--- @async
--- @param p vim.pack.Plug
local function do_checkout(p)
- checkout(p, timestamp2, true)
+ trigger_event(p, 'PackChangedPre', 'update')
+ checkout(p, timestamp2)
+ trigger_event(p, 'PackChanged', 'update')
end
run_list(plugs_to_checkout, do_checkout, 'Applying updates')
diff --git a/test/functional/plugin/pack_spec.lua b/test/functional/plugin/pack_spec.lua
@@ -195,7 +195,7 @@ end
function repos_setup.semver()
init_test_repo('semver')
- local add_tag = function(name)
+ local function add_tag(name)
repo_write_file('semver', 'lua/semver.lua', 'return "semver ' .. name .. '"')
git_add_commit('Add version ' .. name, 'semver')
git_cmd({ 'tag', name }, 'semver')
@@ -231,22 +231,25 @@ local function watch_events(event)
end
--- @param log table[]
-local function find_in_log(log, event, kind, repo_name, version)
- local path = pack_get_plug_path(repo_name)
- local spec = { name = repo_name, src = repos_src[repo_name], version = version }
- local data = { kind = kind, path = path, spec = spec }
- local entry = { event = event, match = vim.fs.abspath(path), data = data }
-
- local res = 0
- for i, tbl in ipairs(log) do
- if vim.deep_equal(tbl, entry) then
- res = i
- break
+local function make_find_packchanged(log)
+ --- @param suffix string
+ return function(suffix, kind, repo_name, version, active)
+ local path = pack_get_plug_path(repo_name)
+ local spec = { name = repo_name, src = repos_src[repo_name], version = version }
+ local data = { active = active, kind = kind, path = path, spec = spec }
+ local entry = { event = 'PackChanged' .. suffix, match = vim.fs.abspath(path), data = data }
+
+ local res = 0
+ for i, tbl in ipairs(log) do
+ if vim.deep_equal(tbl, entry) then
+ res = i
+ break
+ end
end
- end
- eq(true, res > 0)
+ eq(true, res > 0)
- return res
+ return res
+ end
end
local function track_nvim_echo()
@@ -261,7 +264,7 @@ local function track_nvim_echo()
end)
end
-local function validate_progress_report(action, step_names)
+local function assert_progress_report(action, step_names)
-- NOTE: Assume that `nvim_echo` mocked log has only progress report messages
local echo_log = exec_lua('return _G.echo_log') ---@type table[]
local n_steps = #step_names
@@ -366,7 +369,7 @@ describe('vim.pack', function()
it('passes `data` field through to `opts.load`', function()
local out = exec_lua(function()
local map = {} ---@type table<string,boolean>
- local load = function(p)
+ local function load(p)
local name = p.spec.name ---@type string
map[name] = name == 'basic' and (p.spec.data.test == 'value') or (p.spec.data == 'value')
end
@@ -447,41 +450,52 @@ describe('vim.pack', function()
local plugindirs_rev = git_get_hash('HEAD', 'plugindirs')
local semver_rev = git_get_hash('v1.0.0', 'semver')
- -- Should properly format as indented JSON
- local ref_lockfile_lines = {
- '{',
- ' "plugins": {',
- ' "basic": {',
- ' "rev": "' .. basic_rev .. '",',
- ' "src": "' .. repos_src.basic .. '",',
- -- Branch, tag, and commit should be serialized like `'value'` to be
- -- distinguishable from version ranges
- ' "version": "\'some-tag\'"',
- ' },',
- ' "defbranch": {',
- ' "rev": "' .. defbranch_rev .. '",',
- ' "src": "' .. repos_src.defbranch .. '",',
- ' "version": "\'main\'"',
- ' },',
- ' "helptags": {',
- ' "rev": "' .. helptags_rev .. '",',
- ' "src": "' .. repos_src.helptags .. '",',
- ' "version": "\'' .. helptags_rev .. '\'"',
- ' },',
- ' "plugindirs": {',
- ' "rev": "' .. plugindirs_rev .. '",',
- ' "src": "' .. repos_src.plugindirs .. '"',
- -- Absent `version` should be missing and not autoresolved
- ' },',
- ' "semver": {',
- ' "rev": "' .. semver_rev .. '",',
- ' "src": "' .. repos_src.semver .. '",',
- ' "version": ">=0.0.0"',
- ' }',
- ' }',
- '}',
- }
- eq(ref_lockfile_lines, fn.readfile(get_lock_path()))
+ -- Should properly format as indented JSON. Notes:
+ -- - Branch, tag, and commit should be serialized like `'value'` to be
+ -- distinguishable from version ranges.
+ -- - Absent `version` should be missing and not autoresolved.
+ local ref_lockfile_lines = ([[
+ {
+ "plugins": {
+ "basic": {
+ "rev": "%s",
+ "src": "%s",
+ "version": "'some-tag'"
+ },
+ "defbranch": {
+ "rev": "%s",
+ "src": "%s",
+ "version": "'main'"
+ },
+ "helptags": {
+ "rev": "%s",
+ "src": "%s",
+ "version": "'%s'"
+ },
+ "plugindirs": {
+ "rev": "%s",
+ "src": "%s"
+ },
+ "semver": {
+ "rev": "%s",
+ "src": "%s",
+ "version": ">=0.0.0"
+ }
+ }
+ }]]):format(
+ basic_rev,
+ repos_src.basic,
+ defbranch_rev,
+ repos_src.defbranch,
+ helptags_rev,
+ repos_src.helptags,
+ helptags_rev,
+ plugindirs_rev,
+ repos_src.plugindirs,
+ semver_rev,
+ repos_src.semver
+ )
+ eq(vim.text.indent(0, ref_lockfile_lines), fn.readblob(get_lock_path()))
end)
it('updates lockfile', function()
@@ -528,13 +542,13 @@ describe('vim.pack', function()
vim.pack.add({ { src = repos_src.basic, version = 'main' } })
end)
local basic_lua_file = vim.fs.joinpath(pack_get_plug_path('basic'), 'lua', 'basic.lua')
- eq({ 'return "basic feat-branch"' }, fn.readfile(basic_lua_file))
+ eq('return "basic feat-branch"', fn.readblob(basic_lua_file))
-- Running `update()` should still update to use `main`
exec_lua(function()
vim.pack.update(nil, { force = true })
end)
- eq({ 'return "basic main"' }, fn.readfile(basic_lua_file))
+ eq('return "basic main"', fn.readblob(basic_lua_file))
ref_lockfile.plugins.basic.rev = git_get_hash('main', 'basic')
ref_lockfile.plugins.basic.version = "'main'"
@@ -615,7 +629,7 @@ describe('vim.pack', function()
local pack_add_cmd = ('vim.pack.add({ %s })'):format(vim.inspect(repos_src.plugindirs))
fn.writefile({ pack_add_cmd, '_G.done = true' }, init_lua)
- local validate_loaded = function()
+ local function assert_loaded()
eq('plugindirs main', exec_lua('return require("plugindirs")'))
-- Should source 'plugin/' and 'after/plugin/' exactly once
@@ -626,11 +640,11 @@ describe('vim.pack', function()
-- Should auto-install but wait before executing code after it
n.clear({ args_rm = { '-u' } })
n.exec_lua('vim.wait(500, function() return _G.done end, 50)')
- validate_loaded()
+ assert_loaded()
-- Should only `:packadd!` already installed plugin
n.clear({ args_rm = { '-u' } })
- validate_loaded()
+ assert_loaded()
-- Should not load plugins if `--noplugin`, only adjust 'runtimepath'
n.clear({ args = { '--noplugin' }, args_rm = { '-u' } })
@@ -645,7 +659,7 @@ describe('vim.pack', function()
exec_lua(function()
vim.pack.add({ repos_src.basic, repos_src.defbranch })
end)
- validate_progress_report('Installing plugins', { 'basic', 'defbranch' })
+ assert_progress_report('Installing plugins', { 'basic', 'defbranch' })
end)
it('triggers relevant events', function()
@@ -657,25 +671,16 @@ describe('vim.pack', function()
end)
local log = exec_lua('return _G.event_log')
- local installpre_basic = find_in_log(log, 'PackChangedPre', 'install', 'basic', 'feat-branch')
- local installpre_defbranch = find_in_log(log, 'PackChangedPre', 'install', 'defbranch', nil)
- local updatepre_basic = find_in_log(log, 'PackChangedPre', 'update', 'basic', 'feat-branch')
- local updatepre_defbranch = find_in_log(log, 'PackChangedPre', 'update', 'defbranch', nil)
- local update_basic = find_in_log(log, 'PackChanged', 'update', 'basic', 'feat-branch')
- local update_defbranch = find_in_log(log, 'PackChanged', 'update', 'defbranch', nil)
- local install_basic = find_in_log(log, 'PackChanged', 'install', 'basic', 'feat-branch')
- local install_defbranch = find_in_log(log, 'PackChanged', 'install', 'defbranch', nil)
- eq(8, #log)
+ local find_event = make_find_packchanged(log)
+ local installpre_basic = find_event('Pre', 'install', 'basic', 'feat-branch', false)
+ local installpre_defbranch = find_event('Pre', 'install', 'defbranch', nil, false)
+ local install_basic = find_event('', 'install', 'basic', 'feat-branch', false)
+ local install_defbranch = find_event('', 'install', 'defbranch', nil, false)
+ eq(4, #log)
-- NOTE: There is no guaranteed installation order among separate plugins (as it is async)
- eq(true, installpre_basic < updatepre_basic)
- eq(true, updatepre_basic < update_basic)
- -- NOTE: "Install" is after "update" to indicate installation at correct version
- eq(true, update_basic < install_basic)
-
- eq(true, installpre_defbranch < updatepre_defbranch)
- eq(true, updatepre_defbranch < update_defbranch)
- eq(true, update_defbranch < install_defbranch)
+ eq(true, installpre_basic < install_basic)
+ eq(true, installpre_defbranch < install_defbranch)
end)
it('recognizes several `version` types', function()
@@ -694,7 +699,7 @@ describe('vim.pack', function()
end)
it('respects plugin/ and after/plugin/ scripts', function()
- local function validate(load, ref)
+ local function assert(load, ref)
local opts = { load = load }
local out = exec_lua(function()
-- Should handle bad plugin directory name
@@ -721,17 +726,17 @@ describe('vim.pack', function()
eq(true, vim.tbl_contains(rtp, after_dir))
end
- validate(nil, { true, true, true, true, true, true, true, true })
+ assert(nil, { true, true, true, true, true, true, true, true })
n.clear()
- validate(false, {})
+ assert(false, {})
end)
it('can use function `opts.load`', function()
- local validate = function()
+ local function assert()
n.exec_lua(function()
_G.load_log = {}
- local load = function(...)
+ local function load(...)
table.insert(_G.load_log, { ... })
end
vim.pack.add({ repos_src.plugindirs, repos_src.basic }, { load = load })
@@ -763,11 +768,11 @@ describe('vim.pack', function()
end
-- Works on initial install
- validate()
+ assert()
-- Works when loading already installed plugin
n.clear()
- validate()
+ assert()
end)
it('generates help tags', function()
@@ -781,7 +786,7 @@ describe('vim.pack', function()
it('reports install/load errors after loading all input', function()
t.skip(not is_jit(), "Non LuaJIT reports errors differently due to 'coxpcall'")
- local validate = function(err_pat)
+ local function assert(err_pat)
local err = pcall_err(exec_lua, function()
vim.pack.add({
{ src = repos_src.basic, version = 'wrong-version' }, -- Error during initial checkout
@@ -815,13 +820,13 @@ describe('vim.pack', function()
'`pluginerr`:\n',
'Wow, an error',
}
- validate(table.concat(err_pat_parts, '.*'))
+ assert(table.concat(err_pat_parts, '.*'))
-- During loading already installed plugin.
n.clear()
-- NOTE: There is no error for wrong `version`, because there is no check
-- for already installed plugins. Might change in the future.
- validate('vim%.pack.*`pluginerr`:\n.*Wow, an error')
+ assert('vim%.pack.*`pluginerr`:\n.*Wow, an error')
end)
it('normalizes each spec', function()
@@ -864,21 +869,21 @@ describe('vim.pack', function()
end)
it('validates input', function()
- local validate = function(err_pat, input)
- local add_input = function()
+ local function assert(err_pat, input)
+ local function add_input()
vim.pack.add(input)
end
matches(err_pat, pcall_err(exec_lua, add_input))
end
-- Separate spec entries
- validate('list', repos_src.basic)
- validate('spec:.*table', { 1 })
- validate('spec%.src:.*string', { { src = 1 } })
- validate('spec%.src:.*non%-empty string', { { src = '' } })
- validate('spec%.name:.*string', { { src = repos_src.basic, name = 1 } })
- validate('spec%.name:.*non%-empty string', { { src = repos_src.basic, name = '' } })
- validate(
+ assert('list', repos_src.basic)
+ assert('spec:.*table', { 1 })
+ assert('spec%.src:.*string', { { src = 1 } })
+ assert('spec%.src:.*non%-empty string', { { src = '' } })
+ assert('spec%.name:.*string', { { src = repos_src.basic, name = 1 } })
+ assert('spec%.name:.*non%-empty string', { { src = repos_src.basic, name = '' } })
+ assert(
'spec%.version:.*string or vim%.VersionRange',
{ { src = repos_src.basic, version = 1 } }
)
@@ -888,13 +893,13 @@ describe('vim.pack', function()
{ src = repos_src.basic, version = 'feat-branch' },
{ src = repos_src.basic, version = 'main' },
}
- validate('Conflicting `version` for `basic`.*feat%-branch.*main', version_conflict)
+ assert('Conflicting `version` for `basic`.*feat%-branch.*main', version_conflict)
local src_conflict = {
{ src = repos_src.basic, name = 'my-plugin' },
{ src = repos_src.semver, name = 'my-plugin' },
}
- validate('Conflicting `src` for `my%-plugin`.*basic.*semver', src_conflict)
+ assert('Conflicting `src` for `my%-plugin`.*basic.*semver', src_conflict)
end)
end)
@@ -959,7 +964,7 @@ describe('vim.pack', function()
{ src = repos_src.defbranch, version = 'does-not-exist' },
})
end)
- eq({ 'return "fetch main"' }, fn.readfile(fetch_lua_file))
+ eq('return "fetch main"', fn.readblob(fetch_lua_file))
exec_lua(function()
-- Enable highlighting of special filetype
@@ -1095,7 +1100,7 @@ describe('vim.pack', function()
n.exec('write')
-- - Apply changes immediately
- eq({ 'return "fetch new 2"' }, fn.readfile(fetch_lua_file))
+ eq('return "fetch new 2"', fn.readblob(fetch_lua_file))
-- - Clean up buffer+window+tabpage
eq(false, api.nvim_buf_is_valid(confirm_bufnr))
@@ -1104,24 +1109,31 @@ describe('vim.pack', function()
-- - Write to log file
local log_path = vim.fs.joinpath(fn.stdpath('log'), 'nvim-pack.log')
- local log_lines = fn.readfile(log_path)
- matches('========== Update %d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d ==========', log_lines[1])
- local ref_log_lines = {
- '# Update ───────────────────────────────────────────────────────────────────────',
- '',
- '## fetch',
- 'Path: ' .. fetch_path,
- 'Source: ' .. fetch_src,
- 'State before: ' .. hashes.fetch_head,
- 'State after: ' .. hashes.fetch_new .. ' (main)',
- '',
- 'Pending updates:',
- '< ' .. hashes.fetch_head .. ' │ Commit from `main` to be removed',
- '> ' .. hashes.fetch_new .. ' │ Commit to be added 2',
- '> ' .. hashes.fetch_new_prev .. ' │ Commit to be added 1 (tag: dev-tag)',
- '',
- }
- eq(ref_log_lines, vim.list_slice(log_lines, 2))
+ local log_text = fn.readblob(log_path)
+ local log_1, log_rest = log_text:match('^(.-)\n(.*)$') --- @type string, string
+ matches('========== Update %d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d ==========', log_1)
+ local ref_log_lines = ([[
+ # Update ───────────────────────────────────────────────────────────────────────
+
+ ## fetch
+ Path: %s
+ Source: %s
+ State before: %s
+ State after: %s (main)
+
+ Pending updates:
+ < %s │ Commit from `main` to be removed
+ > %s │ Commit to be added 2
+ > %s │ Commit to be added 1 (tag: dev-tag)]]):format(
+ fetch_path,
+ fetch_src,
+ hashes.fetch_head,
+ hashes.fetch_new,
+ hashes.fetch_head,
+ hashes.fetch_new,
+ hashes.fetch_new_prev
+ )
+ eq(vim.text.indent(0, ref_log_lines), vim.trim(log_rest))
end)
it('can be dismissed with `:quit`', function()
@@ -1133,7 +1145,7 @@ describe('vim.pack', function()
-- Should not apply updates
n.exec('quit')
- eq({ 'return "fetch main"' }, fn.readfile(fetch_lua_file))
+ eq('return "fetch main"', fn.readblob(fetch_lua_file))
end)
it('closes full tabpage', function()
@@ -1208,7 +1220,7 @@ describe('vim.pack', function()
-- textDocument/hover
local confirm_winnr = api.nvim_get_current_win()
- local validate_hover = function(pos, commit_msg)
+ local function assert_hover(pos, commit_msg)
api.nvim_win_set_cursor(0, pos)
exec_lua(function()
vim.lsp.buf.hover()
@@ -1230,15 +1242,15 @@ describe('vim.pack', function()
matches(ref_pattern, text)
end
- validate_hover({ 14, 0 }, 'Commit from `main` to be removed')
- validate_hover({ 15, 0 }, 'Commit to be added 2')
- validate_hover({ 18, 0 }, 'Commit from `main` to be removed')
- validate_hover({ 19, 0 }, 'Commit to be added 2')
- validate_hover({ 20, 0 }, 'Commit to be added 1')
- validate_hover({ 27, 0 }, 'Add version v0.3.0')
- validate_hover({ 30, 0 }, 'Add version v1.0.0')
- validate_hover({ 31, 0 }, 'Add version v0.4')
- validate_hover({ 32, 0 }, 'Add version 0.3.1')
+ assert_hover({ 14, 0 }, 'Commit from `main` to be removed')
+ assert_hover({ 15, 0 }, 'Commit to be added 2')
+ assert_hover({ 18, 0 }, 'Commit from `main` to be removed')
+ assert_hover({ 19, 0 }, 'Commit to be added 2')
+ assert_hover({ 20, 0 }, 'Commit to be added 1')
+ assert_hover({ 27, 0 }, 'Add version v0.3.0')
+ assert_hover({ 30, 0 }, 'Add version v1.0.0')
+ assert_hover({ 31, 0 }, 'Add version v0.4')
+ assert_hover({ 32, 0 }, 'Add version 0.3.1')
-- textDocument/codeAction
n.exec_lua(function()
@@ -1259,7 +1271,7 @@ describe('vim.pack', function()
local ref_lockfile = get_lock_tbl() --- @type vim.pack.Lock
- local function validate_action(pos, action_titles, select_idx)
+ local function assert_action(pos, action_titles, select_idx)
api.nvim_win_set_cursor(0, pos)
local lines = api.nvim_buf_get_lines(0, 0, -1, false)
@@ -1280,27 +1292,27 @@ describe('vim.pack', function()
end
-- - Should not include "namespace" header as "plugin at cursor"
- validate_action({ 1, 1 }, {}, 0)
- validate_action({ 2, 0 }, {}, 0)
+ assert_action({ 1, 1 }, {}, 0)
+ assert_action({ 2, 0 }, {}, 0)
-- - Only deletion should be available on errored plugin
- validate_action({ 3, 1 }, { 'Delete `defbranch`' }, 0)
- validate_action({ 7, 0 }, { 'Delete `defbranch`' }, 0)
+ assert_action({ 3, 1 }, { 'Delete `defbranch`' }, 0)
+ assert_action({ 7, 0 }, { 'Delete `defbranch`' }, 0)
-- - Should not include separator blank line as "plugin at cursor"
- validate_action({ 8, 0 }, {}, 0)
- validate_action({ 9, 0 }, {}, 0)
- validate_action({ 10, 0 }, {}, 0)
+ assert_action({ 8, 0 }, {}, 0)
+ assert_action({ 9, 0 }, {}, 0)
+ assert_action({ 10, 0 }, {}, 0)
-- - Should also suggest updating related actions if updates available
local fetch_actions = { 'Update `fetch`', 'Skip updating `fetch`', 'Delete `fetch`' }
- validate_action({ 11, 0 }, fetch_actions, 0)
- validate_action({ 14, 0 }, fetch_actions, 0)
- validate_action({ 20, 0 }, fetch_actions, 0)
- validate_action({ 21, 0 }, {}, 0)
- validate_action({ 22, 0 }, {}, 0)
- validate_action({ 23, 0 }, {}, 0)
+ assert_action({ 11, 0 }, fetch_actions, 0)
+ assert_action({ 14, 0 }, fetch_actions, 0)
+ assert_action({ 20, 0 }, fetch_actions, 0)
+ assert_action({ 21, 0 }, {}, 0)
+ assert_action({ 22, 0 }, {}, 0)
+ assert_action({ 23, 0 }, {}, 0)
-- - Only deletion should be available on plugins without update
- validate_action({ 24, 0 }, { 'Delete `semver`' }, 0)
- validate_action({ 28, 0 }, { 'Delete `semver`' }, 0)
- validate_action({ 32, 0 }, { 'Delete `semver`' }, 0)
+ assert_action({ 24, 0 }, { 'Delete `semver`' }, 0)
+ assert_action({ 28, 0 }, { 'Delete `semver`' }, 0)
+ assert_action({ 32, 0 }, { 'Delete `semver`' }, 0)
-- - Should correctly perform action and remove plugin's lines
local function line_match(lnum, pattern)
@@ -1308,7 +1320,7 @@ describe('vim.pack', function()
end
-- - Delete. Should remove from disk and update lockfile.
- validate_action({ 3, 0 }, { 'Delete `defbranch`' }, 1)
+ assert_action({ 3, 0 }, { 'Delete `defbranch`' }, 1)
eq(false, pack_exists('defbranch'))
line_match(1, '^# Error')
line_match(2, '^$')
@@ -1318,8 +1330,8 @@ describe('vim.pack', function()
eq(ref_lockfile, get_lock_tbl())
-- - Skip udating
- validate_action({ 5, 0 }, fetch_actions, 2)
- eq({ 'return "fetch main"' }, fn.readfile(fetch_lua_file))
+ assert_action({ 5, 0 }, fetch_actions, 2)
+ eq('return "fetch main"', fn.readblob(fetch_lua_file))
line_match(3, '^# Update')
line_match(4, '^$')
line_match(5, '^# Same')
@@ -1335,10 +1347,10 @@ describe('vim.pack', function()
repo_write_file('fetch', 'lua/fetch.lua', 'return "fetch new 3"')
git_add_commit('Commit to be added 3', 'fetch')
- validate_action({ 3, 0 }, fetch_actions, 1)
+ assert_action({ 3, 0 }, fetch_actions, 1)
- eq({ 'return "fetch new 2"' }, fn.readfile(fetch_lua_file))
- validate_progress_report('Applying updates', { 'fetch' })
+ eq('return "fetch new 2"', fn.readblob(fetch_lua_file))
+ assert_progress_report('Applying updates', { 'fetch' })
line_match(1, '^# Update')
line_match(2, '^$')
line_match(3, '^# Same')
@@ -1365,24 +1377,24 @@ describe('vim.pack', function()
end)
-- Plugin sections navigation
- local validate = function(keys, ref_cursor)
+ local function assert(keys, ref_cursor)
n.feed(keys)
eq(ref_cursor, api.nvim_win_get_cursor(0))
end
api.nvim_win_set_cursor(0, { 1, 1 })
- validate(']]', { 3, 0 })
- validate(']]', { 11, 0 })
- validate(']]', { 24, 0 })
+ assert(']]', { 3, 0 })
+ assert(']]', { 11, 0 })
+ assert(']]', { 24, 0 })
-- - Should not wrap around the edge
- validate(']]', { 24, 0 })
+ assert(']]', { 24, 0 })
api.nvim_win_set_cursor(0, { 32, 1 })
- validate('[[', { 24, 0 })
- validate('[[', { 11, 0 })
- validate('[[', { 3, 0 })
+ assert('[[', { 24, 0 })
+ assert('[[', { 11, 0 })
+ assert('[[', { 3, 0 })
-- - Should not wrap around the edge
- validate('[[', { 3, 0 })
+ assert('[[', { 3, 0 })
end)
it('suggests newer versions when on non-tagged commit', function()
@@ -1421,9 +1433,9 @@ describe('vim.pack', function()
-- By default should also include not active plugins
vim.pack.update()
end)
- eq({ 'return "fetch main"' }, fn.readfile(fetch_lua_file))
+ eq('return "fetch main"', fn.readblob(fetch_lua_file))
n.exec('write')
- eq({ 'return "fetch new 2"' }, fn.readfile(fetch_lua_file))
+ eq('return "fetch new 2"', fn.readblob(fetch_lua_file))
end)
it('can force update', function()
@@ -1435,7 +1447,7 @@ describe('vim.pack', function()
-- Apply changes immediately
local fetch_src = repos_src.fetch
local fetch_path = pack_get_plug_path('fetch')
- eq({ 'return "fetch new 2"' }, fn.readfile(fetch_lua_file))
+ eq('return "fetch new 2"', fn.readblob(fetch_lua_file))
-- No special buffer/window/tabpage
eq(1, #api.nvim_list_tabpages())
@@ -1447,24 +1459,31 @@ describe('vim.pack', function()
hashes.fetch_new_prev = git_get_hash('main~', 'fetch')
local log_path = vim.fs.joinpath(fn.stdpath('log'), 'nvim-pack.log')
- local log_lines = fn.readfile(log_path)
- matches('========== Update %d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d ==========', log_lines[1])
- local ref_log_lines = {
- '# Update ───────────────────────────────────────────────────────────────────────',
- '',
- '## fetch',
- 'Path: ' .. fetch_path,
- 'Source: ' .. fetch_src,
- 'State before: ' .. hashes.fetch_head,
- 'State after: ' .. hashes.fetch_new .. ' (main)',
- '',
- 'Pending updates:',
- '< ' .. hashes.fetch_head .. ' │ Commit from `main` to be removed',
- '> ' .. hashes.fetch_new .. ' │ Commit to be added 2',
- '> ' .. hashes.fetch_new_prev .. ' │ Commit to be added 1 (tag: dev-tag)',
- '',
- }
- eq(ref_log_lines, vim.list_slice(log_lines, 2))
+ local log_text = fn.readblob(log_path)
+ local log_1, log_rest = log_text:match('^(.-)\n(.*)$') --- @type string, string
+ matches('========== Update %d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d ==========', log_1)
+ local ref_log_lines = ([[
+ # Update ───────────────────────────────────────────────────────────────────────
+
+ ## fetch
+ Path: %s
+ Source: %s
+ State before: %s
+ State after: %s (main)
+
+ Pending updates:
+ < %s │ Commit from `main` to be removed
+ > %s │ Commit to be added 2
+ > %s │ Commit to be added 1 (tag: dev-tag)]]):format(
+ fetch_path,
+ fetch_src,
+ hashes.fetch_head,
+ hashes.fetch_new,
+ hashes.fetch_head,
+ hashes.fetch_new,
+ hashes.fetch_new_prev
+ )
+ eq(vim.text.indent(0, ref_log_lines), vim.trim(log_rest))
-- Should update lockfile
eq(hashes.fetch_new, get_lock_tbl().plugins.fetch.rev)
@@ -1479,12 +1498,12 @@ describe('vim.pack', function()
end)
-- During initial download
- validate_progress_report('Downloading updates', { 'fetch', 'defbranch', 'semver' })
+ assert_progress_report('Downloading updates', { 'fetch', 'defbranch', 'semver' })
exec_lua('_G.echo_log = {}')
-- During application (only for plugins that have updates)
n.exec('write')
- validate_progress_report('Applying updates', { 'fetch' })
+ assert_progress_report('Applying updates', { 'fetch' })
-- During force update
n.clear()
@@ -1496,7 +1515,7 @@ describe('vim.pack', function()
vim.pack.add({ repos_src.fetch, repos_src.defbranch })
vim.pack.update(nil, { force = true })
end)
- validate_progress_report('Updating', { 'fetch', 'defbranch', 'semver' })
+ assert_progress_report('Updating', { 'fetch', 'defbranch', 'semver' })
end)
it('triggers relevant events', function()
@@ -1511,8 +1530,9 @@ describe('vim.pack', function()
-- Should trigger relevant events only for actually updated plugins
n.exec('write')
local log = exec_lua('return _G.event_log')
- eq(1, find_in_log(log, 'PackChangedPre', 'update', 'fetch', nil))
- eq(2, find_in_log(log, 'PackChanged', 'update', 'fetch', nil))
+ local find_event = make_find_packchanged(log)
+ eq(1, find_event('Pre', 'update', 'fetch', nil, true))
+ eq(2, find_event('', 'update', 'fetch', nil, true))
eq(2, #log)
end)
@@ -1529,7 +1549,7 @@ describe('vim.pack', function()
matches('vim%.pack: %d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d Stash before checkout', stash_list)
-- Update should still be applied
- eq({ 'return "fetch new 2"' }, fn.readfile(fetch_lua_file))
+ eq('return "fetch new 2"', fn.readblob(fetch_lua_file))
end)
it('is not affected by special environment variables', function()
@@ -1541,20 +1561,20 @@ describe('vim.pack', function()
vim.pack.add({ repos_src.fetch })
vim.pack.update({ 'fetch' }, { force = true })
end)
- eq({ 'return "fetch new 2"' }, fn.readfile(fetch_lua_file))
+ eq('return "fetch new 2"', fn.readblob(fetch_lua_file))
eq(ref_environ, fn.environ())
end)
it('validates input', function()
- local validate = function(err_pat, input)
- local update_input = function()
+ local function assert(err_pat, input)
+ local function update_input()
vim.pack.update(input)
end
matches(err_pat, pcall_err(exec_lua, update_input))
end
- validate('list', 1)
+ assert('list', 1)
-- Should first check if every plugin name represents installed plugin
-- If not - stop early before any update
@@ -1562,7 +1582,7 @@ describe('vim.pack', function()
vim.pack.add({ repos_src.basic })
end)
- validate('Plugin `ccc` is not installed', { 'ccc', 'basic', 'aaa' })
+ assert('Plugin `ccc` is not installed', { 'ccc', 'basic', 'aaa' })
-- Empty list is allowed with warning
n.exec('messages clear')
@@ -1574,7 +1594,7 @@ describe('vim.pack', function()
end)
describe('get()', function()
- local make_basic_data = function(active, info)
+ local function make_basic_data(active, info)
local spec = { name = 'basic', src = repos_src.basic, version = 'feat-branch' }
local path = pack_get_plug_path('basic')
local rev = git_get_hash('feat-branch', 'basic')
@@ -1586,7 +1606,7 @@ describe('vim.pack', function()
return res
end
- local make_defbranch_data = function(active, info)
+ local function make_defbranch_data(active, info)
local spec = { name = 'defbranch', src = repos_src.defbranch }
local path = pack_get_plug_path('defbranch')
local rev = git_get_hash('dev', 'defbranch')
@@ -1598,7 +1618,7 @@ describe('vim.pack', function()
return res
end
- local make_plugindirs_data = function(active, info)
+ local function make_plugindirs_data(active, info)
local spec =
{ name = 'plugindirs', src = repos_src.plugindirs, version = vim.version.range('*') }
local path = pack_get_plug_path('plugindirs')
@@ -1725,10 +1745,11 @@ describe('vim.pack', function()
-- Should trigger relevant events in order as specified in `vim.pack.add()`
local log = exec_lua('return _G.event_log')
- eq(1, find_in_log(log, 'PackChangedPre', 'delete', 'basic', 'feat-branch'))
- eq(2, find_in_log(log, 'PackChanged', 'delete', 'basic', 'feat-branch'))
- eq(3, find_in_log(log, 'PackChangedPre', 'delete', 'plugindirs', nil))
- eq(4, find_in_log(log, 'PackChanged', 'delete', 'plugindirs', nil))
+ local find_event = make_find_packchanged(log)
+ eq(1, find_event('Pre', 'delete', 'basic', 'feat-branch', true))
+ eq(2, find_event('', 'delete', 'basic', 'feat-branch', false))
+ eq(3, find_event('Pre', 'delete', 'plugindirs', nil, true))
+ eq(4, find_event('', 'delete', 'plugindirs', nil, false))
eq(4, #log)
-- Should update lockfile
@@ -1750,14 +1771,14 @@ describe('vim.pack', function()
end)
it('validates input', function()
- local validate = function(err_pat, input)
- local del_input = function()
+ local function assert(err_pat, input)
+ local function del_input()
vim.pack.del(input)
end
matches(err_pat, pcall_err(exec_lua, del_input))
end
- validate('list', nil)
+ assert('list', nil)
-- Should first check if every plugin name represents installed plugin
-- If not - stop early before any delete
@@ -1765,7 +1786,7 @@ describe('vim.pack', function()
vim.pack.add({ repos_src.basic })
end)
- validate('Plugin `ccc` is not installed', { 'ccc', 'basic', 'aaa' })
+ assert('Plugin `ccc` is not installed', { 'ccc', 'basic', 'aaa' })
eq(true, pack_exists('basic'))
-- Empty list is allowed with warning