diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 0a6b99e0d0..e78ffe703b 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1478,7 +1478,9 @@ Tag Command Action ~ |:ounmap| :ou[nmap] like ":unmap" but for Operator-pending mode |:ounmenu| :ounme[nu] remove menu for Operator-pending mode |:packadd| :pa[ckadd] add a plugin from 'packpath' +|:packdel| :packd[el] remove |vim.pack| plugins |:packloadall| :packl[oadall] load all packages under 'packpath' +|:packupdate| :packu[pdate] update |vim.pack| plugins |:pbuffer| :pb[uffer] edit buffer in the preview window |:pclose| :pc[lose] close preview window |:pedit| :ped[it] edit file in the preview window diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 546db27ff6..fee0ef28d2 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -139,6 +139,7 @@ EDITOR • |:log| opens log files. • |ZR| restarts Nvim (|:restart|). • |:uptime| displays uptime. +• |:packupdate| and |:packdel| for managing |vim.pack|. EVENTS diff --git a/runtime/doc/pack.txt b/runtime/doc/pack.txt index 86db228f87..b5cb447d6f 100644 --- a/runtime/doc/pack.txt +++ b/runtime/doc/pack.txt @@ -270,8 +270,8 @@ Basic install and management ~ 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. + • Execute |:packupdate|. 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. @@ -293,7 +293,7 @@ Another approach is to utilize Git's `insteadOf` configuration: config on different machine will require the same Git configuration. Explore installed plugins ~ -• `vim.pack.update(nil, { offline = true })` +• `:packupdate ++offline` • Navigate between plugins with `[[` and `]]`. List them with `gO` (|vim.lsp.buf.document_symbol()|). @@ -302,11 +302,11 @@ Switch plugin's version and/or source ~ say, the switch is for plugin named 'plugin1'. • |:restart|. The plugin's state on disk (revision and/or tracked source) is not yet changed. Only plugin's `version` in |vim.pack-lockfile| is updated. -• Execute `vim.pack.update({ 'plugin1' })`. The plugin's source is updated. If - only switching version, use `{ offline = true }` option table. +• Execute `:packupdate plugin1`. The plugin's source is updated. If only + switching version, also pass the `++offline` argument. • Review changes and either confirm or discard them. If discarded, revert `version` change in 'init.lua' as well or you will be prompted again next - time you run |vim.pack.update()|. + time you run `:packupdate`. Freeze plugin from being updated ~ • Update 'init.lua' for plugin to have `version` set to current revision. Get @@ -325,8 +325,7 @@ Revert plugin after an update ~ |standard-path|), locate the revisions before the latest update, and (carefully) adjust current lockfile to have those revisions. • |:restart|. -• `vim.pack.update({ 'plugin' }, { offline = true, target = 'lockfile' })`. - Read and confirm. +• `:packupdate ++offline ++lockfile plugin`. Read and confirm. Synchronize config across machines ~ • On main machine: @@ -338,23 +337,41 @@ Synchronize config across machines ~ are installed at proper revision. If some installation has failed but you know it should not (like due to bad Internet connection), revert |vim.pack-lockfile| and |:restart| again. - • `vim.pack.update(nil, { target = 'lockfile' })`. Read and confirm. + • `:packupdate ++lockfile`. Read and confirm. • Manually delete outdated plugins (present locally, but were not present in - the lockfile prior to restart) with `vim.pack.del( { 'plugin' })`. They - can be located by examining the VCS difference of the lockfile + the lockfile prior to restart) with `:packdel plugin`. They can be located + by examining the VCS difference of the lockfile (`git diff -- nvim-pack-lock.json` for Git). Remove plugins from disk ~ • Remove plugin specs from |vim.pack.add()| calls in 'init.lua' or they will be reinstalled later. • |:restart|. -• Use |vim.pack.del()| with a list of plugin names to remove. Use - |vim.pack.get()| to get all non-active plugins: >lua - vim.iter(vim.pack.get()) - :filter(function(x) return not x.active end) - :map(function(x) return x.spec.name end) - :totable() -< +• Use |:packdel| with plugin names to remove. Use `:packdel ++all` to delete + all inactive plugins. + + +Commands *vim.pack-commands* *E5807* + + *:packu* *:packupdate* *E5808* +:packu[pdate][!] [++offline] [++lockfile] [name] + + Interactively update the specified plugins. Skips confirmation when `!` is + given. If no plugin names are provided, update all plugins. + + When `++offline` is given, skip downloading new updates. + + When `++lockfile` is given, use revisions from the lockfile. + + *:packd* *:packdel* *E5809* *E5810* *E5811* +:packd[el][!] {name} + + Remove the specified plugins. Can only remove inactive plugins unless `!` + is given. + +:packd[el][!] ++all + + Remove all inactive plugins. When `!` is given, instead remove all plugins. *vim.pack-events* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 94a63e4943..32fb4a49c4 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -317,6 +317,8 @@ Commands: - |:lsp| - |:Man| is available by default, with many improvements such as completion - |:match| can be invoked before highlight group is defined +- |:packdel| +- |:packupdate| - |:restart| - |:source| works with Lua - |:trust| diff --git a/runtime/lua/vim/_core/ex_cmd.lua b/runtime/lua/vim/_core/ex_cmd.lua index 2c3b73dd7e..9bba7aa0f8 100644 --- a/runtime/lua/vim/_core/ex_cmd.lua +++ b/runtime/lua/vim/_core/ex_cmd.lua @@ -25,11 +25,6 @@ function M.filter(filter, msg) return match == filter.force end ---- @param msg string -local function echo_err(msg) - api.nvim_echo({ { msg } }, true, { err = true }) -end - --- @return string[] local function get_client_names() return vim @@ -68,7 +63,7 @@ local function checked_enable(names, enable) if name:find('*') == nil and vim.lsp.config[name] ~= nil then vim.lsp.enable(name, enable) else - echo_err(N_('E5801: No client config named: %s'):format(name)) + util.echo_err(N_('E5801: No client config named: %s'):format(name)) end end end @@ -86,9 +81,9 @@ local function ex_lsp_enable(config_names) end if #config_names == 0 then if filetype == '' then - echo_err(N_('E5802: Current buffer has no filetype')) + util.echo_err(N_('E5802: Current buffer has no filetype')) else - echo_err(N_('E5803: No configs for filetype: %s'):format(filetype)) + util.echo_err(N_('E5803: No configs for filetype: %s'):format(filetype)) end return end @@ -111,7 +106,7 @@ local function ex_lsp_disable(config_names) end) :totable() if #config_names == 0 then - echo_err(N_('E5804: No configs with clients attached to current buffer')) + util.echo_err(N_('E5804: No configs with clients attached to current buffer')) return end end @@ -126,7 +121,7 @@ local function get_clients_from_names(client_names) if #client_names == 0 then local clients = vim.lsp.get_clients { bufnr = api.nvim_get_current_buf() } if #clients == 0 then - echo_err(N_('E5805: No clients attached to current buffer')) + util.echo_err(N_('E5805: No clients attached to current buffer')) end return clients else @@ -135,7 +130,7 @@ local function get_clients_from_names(client_names) :map(function(name) local clients = vim.lsp.get_clients { name = name } if #clients == 0 then - echo_err(N_('E5806: No active clients matching name: %s'):format(name)) + util.echo_err(N_('E5806: No active clients matching name: %s'):format(name)) end return clients end) @@ -176,7 +171,7 @@ local available_subcmds = vim.tbl_keys(actions) function M.ex_lsp(eap) local subcmd = eap.fargs[1] if not vim.list_contains(available_subcmds, subcmd) then - echo_err(N_('E5800: Invalid :lsp subcommand: %s'):format(subcmd)) + util.echo_err(N_('E5800: Invalid :lsp subcommand: %s'):format(subcmd)) return end @@ -186,7 +181,7 @@ end --- Completion logic for `:lsp` command --- @param line string content of the current command line --- @return string[] list of completions -function M.lsp_complete(line) +function M.lsp_complete(_, line) local split = vim.split(line, '%s+') if #split == 2 then return available_subcmds @@ -222,7 +217,7 @@ function M.ex_log(eap) path = fs.joinpath(log_dir, filename .. '.log') end if not vim.uv.fs_stat(path) then - echo_err(N_('E5200: No such log file: %s'):format(path)) + util.echo_err(N_('E5200: No such log file: %s'):format(path)) return end util.wrapped_edit(path, eap.smods) @@ -231,7 +226,7 @@ function M.ex_log(eap) end --- Completion logic for `:log` command ---- @return string[] list of completions +--- @return string[] completions function M.log_complete() local names = { 'nvim' } --- @type string[] for file, type in vim.fs.dir(log_dir, { depth = math.huge }) do @@ -313,4 +308,96 @@ function M.ex_oldfiles(eap) api.nvim_echo(lines, false, {}) end +--- Verify that all plugins in a list are installed. +--- @param names string[] +--- @return boolean success +local function verify_plugin_list(names) + local not_found = {} --- @type string[] + local installed_plugin_names = vim.pack._get_names() + for _, name in ipairs(names) do + if not vim.list_contains(installed_plugin_names, name) then + not_found[#not_found + 1] = name + end + end + if #not_found ~= 0 then + util.echo_err(N_('E5807: Plugin not installed: %s'):format(table.concat(not_found, ', '))) + return false + end + return true +end + +--- @param eap vim._core.ExCmdArgs +function M.ex_packupdate(eap) + local offline = false + local target = nil --- @type string? + local plugins = {} --- @type string[] + for _, arg in ipairs(eap.fargs) do + if not vim.startswith(arg, '++') then + plugins[#plugins + 1] = arg + elseif arg == '++offline' then + offline = true + elseif arg == '++lockfile' then + target = 'lockfile' + else + util.echo_err(N_('E474: Invalid argument')) + return + end + end + if verify_plugin_list(plugins) then + vim.pack.update( + #plugins ~= 0 and plugins or nil, + { force = eap.bang, offline = offline, target = target, _ex = true } + ) + end +end + +--- @param pattern string +--- @return string[] completions +function M.packupdate_complete(pattern) + if vim.startswith(pattern, '++') then + return { '++lockfile', '++offline' } + end + return vim.pack._get_names() +end + +--- @param eap vim._core.ExCmdArgs +function M.ex_packdel(eap) + local all = false + local plugins = {} --- @type string[] + for _, arg in ipairs(eap.fargs) do + if not vim.startswith(arg, '++') then + plugins[#plugins + 1] = arg + elseif arg == '++all' then + all = true + else + util.echo_err(N_('E474: Invalid argument')) + return + end + end + if all then + if #plugins ~= 0 then + util.echo_err(N_('E5811: Cannot specify plugin names when using ++all')) + return + end + plugins = vim.pack._get_names(not eap.bang) + end + if all or verify_plugin_list(plugins) then + vim.pack.del(plugins, { force = eap.bang, _ex = true }) + end +end + +--- @param pattern string +--- @param line string content of the current command line +--- @return string[] completions +function M.packdel_complete(pattern, line) + local cmd = api.nvim_parse_cmd(line, {}) + if #cmd.args == 1 and vim.startswith(pattern, '++') then + return { '++all' } + end + if vim.list_contains(cmd.args, '++all') then + return {} + end + return vim.pack._get_names(not cmd.bang) +end + return M diff --git a/runtime/lua/vim/_core/util.lua b/runtime/lua/vim/_core/util.lua index e5dd5c2068..57e31e3bd7 100644 --- a/runtime/lua/vim/_core/util.lua +++ b/runtime/lua/vim/_core/util.lua @@ -166,4 +166,10 @@ function M.get_forge_url(repo, target, target_type) return ('%s/%s/%s'):format(repo, middle, target) end +--- Utility function for displaying vim error codes (EXX) +--- @param msg string +function M.echo_err(msg) + vim.api.nvim_echo({ { msg } }, true, { err = true }) +end + return M diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua index 85a4814540..bd693904cd 100644 --- a/runtime/lua/vim/pack.lua +++ b/runtime/lua/vim/pack.lua @@ -66,7 +66,7 @@ ---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 +--- - Execute |:packupdate|. 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|. @@ -92,7 +92,7 @@ --- ---Explore installed plugins ~ --- ----- `vim.pack.update(nil, { offline = true })` +---- `:packupdate ++offline` ---- Navigate between plugins with `[[` and `]]`. List them with `gO` --- (|vim.lsp.buf.document_symbol()|). --- @@ -102,11 +102,11 @@ --- Let's say, the switch is for plugin named 'plugin1'. ---- |:restart|. The plugin's state on disk (revision and/or tracked source) --- is not yet changed. Only plugin's `version` in |vim.pack-lockfile| is updated. ----- Execute `vim.pack.update({ 'plugin1' })`. The plugin's source is updated. ---- If only switching version, use `{ offline = true }` option table. +---- Execute `:packupdate plugin1`. The plugin's source is updated. If only +--- switching version, also pass the `++offline` argument. ---- Review changes and either confirm or discard them. If discarded, revert --- `version` change in 'init.lua' as well or you will be prompted again next time ---- you run |vim.pack.update()|. +--- you run `:packupdate`. --- ---Freeze plugin from being updated ~ --- @@ -128,7 +128,7 @@ --- locate the revisions before the latest update, and (carefully) adjust --- current lockfile to have those revisions. ---- |:restart|. ----- `vim.pack.update({ 'plugin' }, { offline = true, target = 'lockfile' })`. +---- `:packupdate ++offline ++lockfile plugin`. --- Read and confirm. --- ---Synchronize config across machines ~ @@ -142,9 +142,9 @@ --- are installed at proper revision. If some installation has failed but --- you know it should not (like due to bad Internet connection), --- revert |vim.pack-lockfile| and |:restart| again. ---- - `vim.pack.update(nil, { target = 'lockfile' })`. Read and confirm. +--- - `:packupdate ++lockfile`. Read and confirm. --- - Manually delete outdated plugins (present locally, but were not present ---- in the lockfile prior to restart) with `vim.pack.del( { 'plugin' })`. +--- in the lockfile prior to restart) with `:packdel plugin`. --- They can be located by examining the VCS difference of the lockfile --- (`git diff -- nvim-pack-lock.json` for Git). --- @@ -153,14 +153,32 @@ ---- Remove plugin specs from |vim.pack.add()| calls in 'init.lua' or they will be --- reinstalled later. ---- |:restart|. ----- Use |vim.pack.del()| with a list of plugin names to remove. Use |vim.pack.get()| ---- to get all non-active plugins: ----```lua ----vim.iter(vim.pack.get()) ---- :filter(function(x) return not x.active end) ---- :map(function(x) return x.spec.name end) ---- :totable() ----``` +---- Use |:packdel| with plugin names to remove. Use `:packdel ++all` to delete +--- all inactive plugins. +--- +---
help
+--- Commands *vim.pack-commands* *E5807*
+---
+--- *:packu* *:packupdate* *E5808*
+--- :packu[pdate][!] [++offline] [++lockfile] [name]
+---
+--- Interactively update the specified plugins. Skips confirmation when `!` is
+--- given. If no plugin names are provided, update all plugins.
+---
+--- When `++offline` is given, skip downloading new updates.
+---
+--- When `++lockfile` is given, use revisions from the lockfile.
+---
+--- *:packd* *:packdel* *E5809* *E5810* *E5811*
+--- :packd[el][!] {name}
+---
+--- Remove the specified plugins. Can only remove inactive plugins unless `!`
+--- is given.
+---
+--- :packd[el][!] ++all
+---
+--- Remove all inactive plugins. When `!` is given, instead remove all plugins.
+---
---
---[vim.pack-events]()
---
@@ -207,6 +225,8 @@
local api = vim.api
local uv = vim.uv
local async = require('vim._async')
+local util = require('vim._core.util')
+local N_ = vim.fn.gettext
local M = {}
@@ -1268,7 +1288,9 @@ function M.update(names, opts)
local plug_list = plug_list_from_names(names)
if #plug_list == 0 then
- notify('Nothing to update', 'WARN')
+ if opts._ex then
+ util.echo_err(N_('E5808: Nothing to update'))
+ end
return
end
git_ensure_exec()
@@ -1367,7 +1389,9 @@ function M.del(names, opts)
local plug_list = plug_list_from_names(names)
if #plug_list == 0 then
- notify('Nothing to remove', 'WARN')
+ if opts._ex then
+ util.echo_err(N_('E5809: Nothing to remove'))
+ end
return
end
@@ -1394,9 +1418,14 @@ function M.del(names, opts)
if #fail_to_delete > 0 then
local plugs = table.concat(fail_to_delete, ', ')
- local msg = ('Some plugins are active and were not deleted: %s.'):format(plugs)
- .. ' Remove them from init.lua, restart, and try again.'
- error(msg)
+ if opts._ex then
+ util.echo_err(N_('E5810: Some plugins are active and were not deleted: %s'):format(plugs))
+ return
+ else
+ local msg = ('Some plugins are active and were not deleted: %s.'):format(plugs)
+ .. ' Remove them from init.lua, restart, and try again.'
+ error(msg)
+ end
end
end
@@ -1488,4 +1517,16 @@ function M.get(names, opts)
return res
end
+--- @param skip_inactive? boolean
+--- @return string[] plugin_names
+function M._get_names(skip_inactive)
+ local names = {} --- @type string[]
+ for _, plugin_data in ipairs(vim.pack.get(nil, { info = false })) do
+ if not (skip_inactive and plugin_data.active) then
+ names[#names + 1] = plugin_data.spec.name
+ end
+ end
+ return names
+end
+
return M
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
index 339379d8fd..7680cdd722 100644
--- a/src/nvim/cmdexpand.c
+++ b/src/nvim/cmdexpand.c
@@ -1334,6 +1334,8 @@ char *addstar(char *fname, size_t len, int context)
|| context == EXPAND_CHECKHEALTH
|| context == EXPAND_LSP
|| context == EXPAND_LOG
+ || context == EXPAND_PACKDEL
+ || context == EXPAND_PACKUPDATE
|| context == EXPAND_LUA) {
retval = xstrnsave(fname, len);
} else {
@@ -2356,6 +2358,14 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
xp->xp_context = EXPAND_LSP;
break;
+ case CMD_packdel:
+ xp->xp_context = EXPAND_PACKDEL;
+ break;
+
+ case CMD_packupdate:
+ xp->xp_context = EXPAND_PACKUPDATE;
+ break;
+
case CMD_retab:
xp->xp_context = EXPAND_RETAB;
xp->xp_pattern = (char *)arg;
@@ -2906,9 +2916,10 @@ static char *get_arg1_from_lua(char *lua, expand_T *xp, int idx)
|| last_gen != get_cmdline_last_prompt_id()) {
xfree(last_xp_line);
last_xp_line = xstrdup(xp->xp_line);
- MAXSIZE_TEMP_ARRAY(args, 1);
+ MAXSIZE_TEMP_ARRAY(args, 2);
Error err = ERROR_INIT;
+ ADD_C(args, CSTR_AS_OBJ(xp->xp_pattern));
ADD_C(args, CSTR_AS_OBJ(xp->xp_line));
// Build the current command line as a Lua string argument
Object res = nlua_exec(cstr_as_string(lua), NULL, args, kRetObject, NULL, &err);
@@ -2945,6 +2956,26 @@ static char *get_lsp_arg(expand_T *xp, int idx)
return get_arg1_from_lua("return require'vim._core.ex_cmd'.lsp_complete(...)", xp, idx);
}
+/// Completion for |:packdel| command.
+///
+/// Given to ExpandGeneric() to obtain `:packdel` completion.
+/// @param[in] xp Expandy thing.
+/// @param[in] idx Index of the item.
+static char *get_packdel_arg(expand_T *xp, int idx)
+{
+ return get_arg1_from_lua("return require'vim._core.ex_cmd'.packdel_complete(...)", xp, idx);
+}
+
+/// Completion for |:packupdate| command.
+///
+/// Given to ExpandGeneric() to obtain `:packupdate` completion.
+/// @param[in] xp Expandy thing.
+/// @param[in] idx Index of the item.
+static char *get_packupdate_arg(expand_T *xp, int idx)
+{
+ return get_arg1_from_lua("return require'vim._core.ex_cmd'.packupdate_complete(...)", xp, idx);
+}
+
/// Do the expansion based on xp->xp_context and "rmp".
static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches)
{
@@ -2989,6 +3020,8 @@ static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches
{ EXPAND_CHECKHEALTH, get_healthcheck_names, true, false },
{ EXPAND_LOG, get_log_arg, true, false },
{ EXPAND_LSP, get_lsp_arg, true, false },
+ { EXPAND_PACKDEL, get_packdel_arg, true, false },
+ { EXPAND_PACKUPDATE, get_packupdate_arg, true, false },
};
int ret = FAIL;
diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h
index c78edf50e0..cc7e9dd595 100644
--- a/src/nvim/cmdexpand_defs.h
+++ b/src/nvim/cmdexpand_defs.h
@@ -131,6 +131,8 @@ enum {
EXPAND_LUA,
EXPAND_LSP,
EXPAND_LOG,
+ EXPAND_PACKDEL,
+ EXPAND_PACKUPDATE,
};
/// Type used by ExpandGeneric()
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 25bba1eb17..d09e448e61 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -1970,12 +1970,24 @@ M.cmds = {
addr_type = 'ADDR_NONE',
func = 'ex_packadd',
},
+ {
+ command = 'packdel',
+ flags = bit.bor(BANG, EXTRA, NEEDARG, TRLBAR),
+ addr_type = 'ADDR_NONE',
+ func = 'ex_packdel',
+ },
{
command = 'packloadall',
flags = bit.bor(BANG, TRLBAR, SBOXOK, CMDWIN, LOCK_OK),
addr_type = 'ADDR_NONE',
func = 'ex_packloadall',
},
+ {
+ command = 'packupdate',
+ flags = bit.bor(BANG, EXTRA, TRLBAR),
+ addr_type = 'ADDR_NONE',
+ func = 'ex_packupdate',
+ },
{
command = 'pbuffer',
flags = bit.bor(BANG, RANGE, BUFNAME, BUFUNL, COUNT, EXTRA, CMDARG, TRLBAR),
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index a79d581d24..811ab1d479 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -8310,6 +8310,18 @@ static void ex_lsp(exarg_T *eap)
nlua_call_excmd("vim._core.ex_cmd", "ex_lsp", eap, &cmdmod, NULL);
}
+/// ":packdel {name}"
+static void ex_packdel(exarg_T *eap)
+{
+ nlua_call_excmd("vim._core.ex_cmd", "ex_packdel", eap, &cmdmod, NULL);
+}
+
+/// ":packupdate {name}"
+static void ex_packupdate(exarg_T *eap)
+{
+ nlua_call_excmd("vim._core.ex_cmd", "ex_packupdate", eap, &cmdmod, NULL);
+}
+
/// ":uptime"
static void ex_uptime(exarg_T *eap)
{
diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt
index efa85f2505..9f2efad723 100644
--- a/src/nvim/po/CMakeLists.txt
+++ b/src/nvim/po/CMakeLists.txt
@@ -61,6 +61,7 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG)
${NVIM_RELATIVE_SOURCES} ${PROJECT_SOURCE_DIR}/runtime/scripts/optwin.lua
${PROJECT_SOURCE_DIR}/runtime/lua/vim/_core/ex_cmd.lua
${PROJECT_SOURCE_DIR}/runtime/lua/vim/_core/time.lua
+ ${PROJECT_SOURCE_DIR}/runtime/lua/vim/pack.lua
VERBATIM
DEPENDS ${NVIM_SOURCES} nvim_bin nvim_runtime_deps)
diff --git a/test/functional/ex_cmds/pack_spec.lua b/test/functional/ex_cmds/pack_spec.lua
new file mode 100644
index 0000000000..950fc40365
--- /dev/null
+++ b/test/functional/ex_cmds/pack_spec.lua
@@ -0,0 +1,210 @@
+local t = require('test.testutil')
+local n = require('test.functional.testnvim')()
+
+local clear = n.clear
+local command = n.command
+local eq = t.eq
+local exec_lua = n.exec_lua
+local matches = t.matches
+local ok = t.ok
+local pcall_err = t.pcall_err
+
+local foo = {
+ active = true,
+ path = '/dev/null',
+ rev = '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
+ spec = {
+ name = 'foo',
+ src = 'https://foo.example/foo/foo',
+ },
+}
+local bar = {
+ active = false,
+ path = '/var/empty',
+ rev = '62cdb7020ff920e5aa642c3d4066950dd1f01f4d',
+ spec = {
+ name = 'bar',
+ src = 'https://foo.example/bar/bar',
+ version = vim.version.range('0.42.42rc1'),
+ },
+}
+
+local function assert_success(fn)
+ exec_lua(fn)
+ ok(exec_lua('return _G.success'), 'true', '_G.success')
+end
+
+local function assert_completion(pat, expected)
+ local completions = exec_lua(function()
+ return vim.fn.getcompletion(pat, 'cmdline')
+ end)
+ eq(expected, completions)
+end
+
+describe(':packupdate', function()
+ before_each(function()
+ clear()
+ exec_lua(function()
+ vim.pack.get = function(names, opts)
+ assert(opts.info == false)
+ return { foo, bar }
+ end
+ end)
+ end)
+
+ it('works', function()
+ assert_success(function()
+ vim.pack.update = function(names, opts)
+ success = names == nil and opts.force ~= true and opts._ex == true
+ end
+ vim.cmd.packupdate()
+
+ vim.pack.update = function(names, opts)
+ success = success
+ and names[1] == 'foo'
+ and names[2] == nil
+ and opts.force ~= true
+ and opts._ex == true
+ end
+ vim.cmd.packupdate { 'foo' }
+ end)
+ end)
+
+ it('handles !', function()
+ assert_success(function()
+ vim.pack.update = function(names, opts)
+ success = opts.force == true
+ end
+ vim.cmd.packupdate { bang = true }
+ end)
+ end)
+
+ it('handles args', function()
+ assert_success(function()
+ vim.pack.update = function(names, opts)
+ success = opts.offline == true
+ end
+ vim.cmd.packupdate { '++offline' }
+
+ vim.pack.update = function(names, opts)
+ success = success and opts.target == 'lockfile'
+ end
+ vim.cmd.packupdate { '++lockfile' }
+ end)
+ end)
+
+ it('shows E5807', function()
+ eq('Vim(packupdate):E5807: Plugin not installed: baz', pcall_err(command, 'packupdate baz'))
+ end)
+
+ it('shows E5808', function()
+ exec_lua(function()
+ vim.pack.get = function(names, opts)
+ return {}
+ end
+ end)
+ eq('Vim(packupdate):E5808: Nothing to update', pcall_err(command, 'packupdate'))
+ end)
+
+ it('has completion', function()
+ assert_completion('packupdate ', { 'bar', 'foo' })
+ assert_completion('packupdate b', { 'bar' })
+ assert_completion('packupdate ++', { '++lockfile', '++offline' })
+ end)
+
+ it('fails if runtime is missing/broken', function()
+ clear {
+ args_rm = { '-u' },
+ args = { '-u', 'NONE' },
+ env = { VIMRUNTIME = 'non-existent' },
+ }
+ matches(
+ [[.*module 'vim%.pack' not found:]],
+ vim.split(t.pcall_err(n.command, 'packupdate foo'), '\n')[1]
+ )
+ end)
+end)
+
+describe(':packdel', function()
+ before_each(function()
+ clear()
+ exec_lua(function()
+ vim.pack.get = function(names, opts)
+ assert(opts.info == false)
+ return { foo, bar }
+ end
+ end)
+ end)
+
+ it('works', function()
+ assert_success(function()
+ vim.pack.del = function(names, opts)
+ success = names[1] == 'bar' and names[2] == nil and opts.force ~= true and opts._ex == true
+ end
+ vim.cmd.packdel { 'bar' }
+ end)
+ end)
+
+ it('handles !', function()
+ assert_success(function()
+ vim.pack.del = function(names, opts)
+ success = opts.force == true
+ end
+ vim.cmd.packdel { 'bar', bang = true }
+ end)
+ end)
+
+ it('handles ++all', function()
+ assert_success(function()
+ vim.pack.del = function(names, opts)
+ success = vim.list_contains(names, 'bar') and not vim.list_contains(names, 'foo')
+ end
+ vim.cmd.packdel { '++all' }
+
+ vim.pack.del = function(names, opts)
+ success = success and vim.list_contains(names, 'foo') and vim.list_contains(names, 'bar')
+ end
+ vim.cmd.packdel { '++all', bang = true }
+ end)
+ end)
+
+ it('shows E5807', function()
+ eq('Vim(packdel):E5807: Plugin not installed: baz', pcall_err(command, 'packdel baz'))
+ end)
+
+ it('shows E5809', function()
+ exec_lua(function()
+ vim.pack.get = function(names, opts)
+ return {}
+ end
+ end)
+ eq('Vim(packdel):E5809: Nothing to remove', pcall_err(command, 'packdel ++all'))
+ end)
+
+ it('shows E5811', function()
+ eq(
+ 'Vim(packdel):E5811: Cannot specify plugin names when using ++all',
+ pcall_err(command, 'packdel bar ++all')
+ )
+ end)
+
+ it('has completion', function()
+ assert_completion('packdel ', { 'bar' })
+ assert_completion('packdel! ', { 'bar', 'foo' })
+ assert_completion('packdel! b', { 'bar' })
+ assert_completion('packdel ++', { '++all' })
+ assert_completion('packdel! ++', { '++all' })
+ end)
+
+ it('fails if runtime is missing/broken', function()
+ clear {
+ args_rm = { '-u' },
+ args = { '-u', 'NONE' },
+ env = { VIMRUNTIME = 'non-existent' },
+ }
+ matches(
+ [[.*module 'vim%.pack' not found:]],
+ vim.split(t.pcall_err(n.command, 'packdel foo'), '\n')[1]
+ )
+ end)
+end)
diff --git a/test/functional/plugin/pack_spec.lua b/test/functional/plugin/pack_spec.lua
index 8d11f96f25..646456a555 100644
--- a/test/functional/plugin/pack_spec.lua
+++ b/test/functional/plugin/pack_spec.lua
@@ -2060,12 +2060,12 @@ describe('vim.pack', function()
assert('Plugin `ccc` is not installed', { 'ccc', 'basic', 'aaa' })
- -- Empty list is allowed with warning
+ -- Empty list is allowed
n.exec('messages clear')
exec_lua(function()
vim.pack.update({})
end)
- eq('vim.pack: Nothing to update', n.exec_capture('messages'))
+ eq('', n.exec_capture('messages'))
end)
end)
@@ -2247,6 +2247,13 @@ describe('vim.pack', function()
local msg = "vim.pack: Removed plugin 'basic'\nvim.pack: Removed plugin 'plugindirs'"
eq(msg, n.exec_capture('messages'))
+ -- `:packdel` should output E5810 instead of the normal error
+ eq(
+ 'Vim(packdel):E5810: Some plugins are active and were not deleted: defbranch',
+ pcall_err(n.command, 'packdel defbranch')
+ )
+ assert_on_disk({ defbranch = true })
+
-- Should trigger relevant events in order as specified in `vim.pack.add()`
local log = exec_lua('return _G.event_log')
local find_event = make_find_packchanged(log)
@@ -2326,12 +2333,12 @@ describe('vim.pack', function()
assert('Plugin `ccc` is not installed', { 'ccc', 'basic', 'aaa' })
eq(true, pack_exists('basic'))
- -- Empty list is allowed with warning
+ -- Empty list is allowed
n.exec('messages clear')
exec_lua(function()
vim.pack.del({})
end)
- eq('vim.pack: Nothing to remove', n.exec_capture('messages'))
+ eq('', n.exec_capture('messages'))
end)
end)
end)