From c9965491d01f3da26c227053f9051011b43942f1 Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Fri, 26 Dec 2025 20:05:12 +0200 Subject: [PATCH] feat(pack): allow running `update()` without Internet connection Problem: There is now way to run `update()` without Internet connection while there are some workflows that do not require it. Like "switch plugin version" and "revert latest update". Solution: Add `opts.offline` to `update()`. This also allows now to treat `vim.pack.update(nil, { offline = true })` as a way to interactively explore currently installed plugins. --- runtime/doc/pack.txt | 13 +++++++++++-- runtime/lua/vim/pack.lua | 20 +++++++++++++++----- runtime/lua/vim/pack/_lsp.lua | 2 +- test/functional/plugin/pack_spec.lua | 22 ++++++++++++++++++++++ 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/runtime/doc/pack.txt b/runtime/doc/pack.txt index eb1faecc3d..8eaf0f4125 100644 --- a/runtime/doc/pack.txt +++ b/runtime/doc/pack.txt @@ -288,12 +288,18 @@ Another approach is to utilize Git's `insteadOf` configuration: These sources will be used verbatim in |vim.pack-lockfile|, so reusing the config on different machine will require the same Git configuration. +Explore installed plugins ~ +• `vim.pack.update(nil, { offline = true })` +• Navigate between plugins with `[[` and `]]`. List them with `gO` + (|vim.lsp.buf.document_symbol()|). + Switch plugin's version and/or source ~ • Update 'init.lua' for plugin to have desired `version` and/or `src`. 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. +• Execute `vim.pack.update({ 'plugin1' })`. The plugin's source is updated. If + only switching version, use `{ offline = true }` option table. • 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()|. @@ -315,7 +321,8 @@ Revert plugin after an update ~ locate the revisions before the latest update, and (carefully) adjust current lockfile to have those revisions. • |:restart|. -• `vim.pack.update({ 'plugin' }, { target = 'lockfile' })`. Read and confirm. +• `vim.pack.update({ 'plugin' }, { offline = true, target = 'lockfile' })`. + Read and confirm. Synchronize config across machines ~ • On main machine: @@ -491,6 +498,8 @@ update({names}, {opts}) *vim.pack.update()* • {opts} (`table?`) A table with the following fields: • {force}? (`boolean`) Whether to skip confirmation and make updates immediately. Default `false`. + • {offline}? (`boolean`) Whether to skip downloading new + updates. Default: `false`. • {target}? (`string`) How to compute a new plugin revision. One of: • "version" (default) - use latest revision matching diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua index a808ce8683..5ef7176d1a 100644 --- a/runtime/lua/vim/pack.lua +++ b/runtime/lua/vim/pack.lua @@ -88,6 +88,12 @@ --- These sources will be used verbatim in |vim.pack-lockfile|, so reusing --- the config on different machine will require the same Git configuration. --- +---Explore installed plugins ~ +--- +---- `vim.pack.update(nil, { offline = true })` +---- Navigate between plugins with `[[` and `]]`. List them with `gO` +--- (|vim.lsp.buf.document_symbol()|). +--- ---Switch plugin's version and/or source ~ --- ---- Update 'init.lua' for plugin to have desired `version` and/or `src`. @@ -95,6 +101,7 @@ ---- |: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. ---- 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()|. @@ -119,7 +126,8 @@ --- locate the revisions before the latest update, and (carefully) adjust --- current lockfile to have those revisions. ---- |:restart|. ----- `vim.pack.update({ 'plugin' }, { target = 'lockfile' })`. Read and confirm. +---- `vim.pack.update({ 'plugin' }, { offline = true, target = 'lockfile' })`. +--- Read and confirm. --- ---Synchronize config across machines ~ --- @@ -1152,6 +1160,8 @@ end --- @inlinedoc --- @field force? boolean Whether to skip confirmation and make updates immediately. Default `false`. --- +--- @field offline? boolean Whether to skip downloading new updates. Default: `false`. +--- --- How to compute a new plugin revision. One of: --- - "version" (default) - use latest revision matching `version` from plugin specification. --- - "lockfile" - use revision from the lockfile. Useful for reverting or performing controlled @@ -1191,7 +1201,7 @@ end --- @param opts? vim.pack.keyset.update function M.update(names, opts) vim.validate('names', names, vim.islist, true, 'list') - opts = vim.tbl_extend('force', { force = false, target = 'version' }, opts or {}) + opts = vim.tbl_extend('force', { force = false, offline = false, target = 'version' }, opts or {}) local plug_list = plug_list_from_names(names) if #plug_list == 0 then @@ -1217,7 +1227,7 @@ function M.update(names, opts) end -- Fetch - if not opts._offline then + if not opts.offline then -- Using '--tags --force' means conflicting tags will be synced with remote local args = { 'fetch', '--quiet', '--tags', '--force', '--recurse-submodules=yes', 'origin' } git_cmd(args, p.path) @@ -1237,8 +1247,8 @@ function M.update(names, opts) trigger_event(p, 'PackChanged', 'update') end end - local progress_title = opts.force and (opts._offline and 'Applying updates' or 'Updating') - or 'Downloading updates' + local progress_title = opts.force and (opts.offline and 'Applying updates' or 'Updating') + or (opts.offline and 'Computing updates' or 'Downloading updates') run_list(plug_list, do_update, progress_title) if needs_lock_write then diff --git a/runtime/lua/vim/pack/_lsp.lua b/runtime/lua/vim/pack/_lsp.lua index 04949ce6eb..33c6fb8060 100644 --- a/runtime/lua/vim/pack/_lsp.lua +++ b/runtime/lua/vim/pack/_lsp.lua @@ -157,7 +157,7 @@ end local commands = { update_plugin = function(plug_data) - vim.pack.update({ plug_data.name }, { force = true, _offline = true }) + vim.pack.update({ plug_data.name }, { force = true, offline = true }) end, skip_update_plugin = function(_) end, delete_plugin = function(plug_data) diff --git a/test/functional/plugin/pack_spec.lua b/test/functional/plugin/pack_spec.lua index ac5367a649..93f80e5caf 100644 --- a/test/functional/plugin/pack_spec.lua +++ b/test/functional/plugin/pack_spec.lua @@ -1765,6 +1765,28 @@ describe('vim.pack', function() assert_origin(basic_src) end) + it('can do offline update', function() + local defbranch_path = pack_get_plug_path('defbranch') + local defbranch_lua_file = vim.fs.joinpath(defbranch_path, 'lua', 'defbranch.lua') + + n.exec_lua(function() + vim.pack.add({ { src = repos_src.defbranch, version = 'main' } }) + end) + + track_nvim_echo() + + eq('return "defbranch dev"', fn.readblob(defbranch_lua_file)) + n.exec_lua(function() + vim.pack.update({ 'defbranch' }, { offline = true }) + end) + + -- There should be no progress report about downloading updates + assert_progress_report('Computing updates', { 'defbranch' }) + + n.exec('write') + eq('return "defbranch main"', fn.readblob(defbranch_lua_file)) + end) + it('shows progress report', function() track_nvim_echo() exec_lua(function()