1043 lines
34 KiB
Lua
1043 lines
34 KiB
Lua
-- TODO: Performance analysis/tuning
|
|
-- TODO: Merge start plugins?
|
|
local util = require 'packer.util'
|
|
|
|
local join_paths = util.join_paths
|
|
local stdpath = vim.fn.stdpath
|
|
|
|
-- Config
|
|
local packer = {}
|
|
local config_defaults = {
|
|
ensure_dependencies = true,
|
|
snapshot = nil,
|
|
snapshot_path = join_paths(stdpath 'cache', 'packer.nvim'),
|
|
package_root = join_paths(stdpath 'data', 'site', 'pack'),
|
|
compile_path = join_paths(stdpath 'config', 'plugin', 'packer_compiled.lua'),
|
|
plugin_package = 'packer',
|
|
max_jobs = nil,
|
|
auto_clean = true,
|
|
compile_on_sync = true,
|
|
disable_commands = false,
|
|
opt_default = false,
|
|
transitive_opt = true,
|
|
transitive_disable = true,
|
|
auto_reload_compiled = true,
|
|
preview_updates = false,
|
|
git = {
|
|
mark_breaking_changes = true,
|
|
cmd = 'git',
|
|
subcommands = {
|
|
update = 'pull --ff-only --progress --rebase=false',
|
|
update_head = 'merge FETCH_HEAD',
|
|
install = 'clone --depth %i --no-single-branch --progress',
|
|
fetch = 'fetch --depth 999999 --progress',
|
|
checkout = 'checkout %s --',
|
|
update_branch = 'merge --ff-only @{u}',
|
|
current_branch = 'rev-parse --abbrev-ref HEAD',
|
|
diff = 'log --color=never --pretty=format:FMT --no-show-signature %s...%s',
|
|
diff_fmt = '%%h %%s (%%cr)',
|
|
git_diff_fmt = 'show --no-color --pretty=medium %s',
|
|
get_rev = 'rev-parse --short HEAD',
|
|
get_header = 'log --color=never --pretty=format:FMT --no-show-signature HEAD -n 1',
|
|
get_bodies = 'log --color=never --pretty=format:"===COMMIT_START===%h%n%s===BODY_START===%b" --no-show-signature HEAD@{1}...HEAD',
|
|
get_fetch_bodies = 'log --color=never --pretty=format:"===COMMIT_START===%h%n%s===BODY_START===%b" --no-show-signature HEAD...FETCH_HEAD',
|
|
submodules = 'submodule update --init --recursive --progress',
|
|
revert = 'reset --hard HEAD@{1}',
|
|
revert_to = 'reset --hard %s --',
|
|
tags_expand_fmt = 'tag -l %s --sort -version:refname',
|
|
},
|
|
depth = 1,
|
|
clone_timeout = 60,
|
|
default_url_format = 'https://github.com/%s.git',
|
|
},
|
|
display = {
|
|
non_interactive = false,
|
|
compact = false,
|
|
open_fn = nil,
|
|
open_cmd = '65vnew',
|
|
working_sym = '⟳',
|
|
error_sym = '✗',
|
|
done_sym = '✓',
|
|
removed_sym = '-',
|
|
moved_sym = '→',
|
|
item_sym = '•',
|
|
header_sym = '━',
|
|
header_lines = 2,
|
|
title = 'packer.nvim',
|
|
show_all_info = true,
|
|
prompt_border = 'double',
|
|
keybindings = {
|
|
quit = 'q',
|
|
toggle_update = 'u',
|
|
continue = 'c',
|
|
toggle_info = '<CR>',
|
|
diff = 'd',
|
|
prompt_revert = 'r',
|
|
retry = 'R',
|
|
},
|
|
},
|
|
luarocks = { python_cmd = 'python' },
|
|
log = { level = 'warn' },
|
|
profile = { enable = false },
|
|
autoremove = false,
|
|
}
|
|
|
|
--- Initialize global namespace for use for callbacks and other data generated whilst packer is
|
|
--- running
|
|
_G._packer = _G._packer or {}
|
|
|
|
local config = vim.tbl_extend('force', {}, config_defaults)
|
|
local plugins = nil
|
|
local plugin_specifications = nil
|
|
local rocks = nil
|
|
|
|
local configurable_modules = {
|
|
clean = false,
|
|
compile = false,
|
|
display = false,
|
|
handlers = false,
|
|
install = false,
|
|
plugin_types = false,
|
|
plugin_utils = false,
|
|
update = false,
|
|
luarocks = false,
|
|
log = false,
|
|
snapshot = false,
|
|
}
|
|
|
|
local function require_and_configure(module_name)
|
|
local fully_qualified_name = 'packer.' .. module_name
|
|
local module = require(fully_qualified_name)
|
|
if not configurable_modules[module_name] and module.cfg then
|
|
configurable_modules[module_name] = true
|
|
module.cfg(config)
|
|
return module
|
|
end
|
|
|
|
return module
|
|
end
|
|
|
|
--- Initialize packer
|
|
-- Forwards user configuration to sub-modules, resets the set of managed plugins, and ensures that
|
|
-- the necessary package directories exist
|
|
packer.init = function(user_config)
|
|
user_config = user_config or {}
|
|
config = util.deep_extend('force', config, user_config)
|
|
packer.reset()
|
|
config.package_root = vim.fn.fnamemodify(config.package_root, ':p')
|
|
local _
|
|
config.package_root, _ = string.gsub(config.package_root, util.get_separator() .. '$', '', 1)
|
|
config.pack_dir = join_paths(config.package_root, config.plugin_package)
|
|
config.opt_dir = join_paths(config.pack_dir, 'opt')
|
|
config.start_dir = join_paths(config.pack_dir, 'start')
|
|
if #vim.api.nvim_list_uis() == 0 then
|
|
config.display.non_interactive = true
|
|
end
|
|
|
|
local plugin_utils = require_and_configure 'plugin_utils'
|
|
plugin_utils.ensure_dirs()
|
|
|
|
require_and_configure 'snapshot'
|
|
|
|
if not config.disable_commands then
|
|
packer.make_commands()
|
|
end
|
|
|
|
if vim.fn.mkdir(config.snapshot_path, 'p') ~= 1 then
|
|
require_and_configure('log').warn("Couldn't create " .. config.snapshot_path)
|
|
end
|
|
end
|
|
|
|
packer.make_commands = function()
|
|
vim.cmd [[command! -nargs=+ -complete=customlist,v:lua.require'packer.snapshot'.completion.create PackerSnapshot lua require('packer').snapshot(<f-args>)]]
|
|
vim.cmd [[command! -nargs=+ -complete=customlist,v:lua.require'packer.snapshot'.completion.rollback PackerSnapshotRollback lua require('packer').rollback(<f-args>)]]
|
|
vim.cmd [[command! -nargs=+ -complete=customlist,v:lua.require'packer.snapshot'.completion.snapshot PackerSnapshotDelete lua require('packer.snapshot').delete(<f-args>)]]
|
|
vim.cmd [[command! -nargs=* -complete=customlist,v:lua.require'packer'.plugin_complete PackerInstall lua require('packer').install(<f-args>)]]
|
|
vim.cmd [[command! -nargs=* -complete=customlist,v:lua.require'packer'.plugin_complete PackerUpdate lua require('packer').update(<f-args>)]]
|
|
vim.cmd [[command! -nargs=* -complete=customlist,v:lua.require'packer'.plugin_complete PackerSync lua require('packer').sync(<f-args>)]]
|
|
vim.cmd [[command! PackerClean lua require('packer').clean()]]
|
|
vim.cmd [[command! -nargs=* PackerCompile lua require('packer').compile(<q-args>)]]
|
|
vim.cmd [[command! PackerStatus lua require('packer').status()]]
|
|
vim.cmd [[command! PackerProfile lua require('packer').profile_output()]]
|
|
vim.cmd [[command! -bang -nargs=+ -complete=customlist,v:lua.require'packer'.loader_complete PackerLoad lua require('packer').loader(<f-args>, '<bang>' == '!')]]
|
|
end
|
|
|
|
packer.reset = function()
|
|
plugins = {}
|
|
plugin_specifications = {}
|
|
rocks = {}
|
|
end
|
|
|
|
--- Add a Luarocks package to be managed
|
|
packer.use_rocks = function(rock)
|
|
if type(rock) == 'string' then
|
|
rock = { rock }
|
|
end
|
|
if not vim.tbl_islist(rock) and type(rock[1]) == 'string' then
|
|
rocks[rock[1]] = rock
|
|
else
|
|
for _, r in ipairs(rock) do
|
|
local rock_name = (type(r) == 'table') and r[1] or r
|
|
rocks[rock_name] = r
|
|
end
|
|
end
|
|
end
|
|
|
|
--- The main logic for adding a plugin (and any dependencies) to the managed set
|
|
-- Can be invoked with (1) a single plugin spec as a string, (2) a single plugin spec table, or (3)
|
|
-- a list of plugin specs
|
|
-- TODO: This should be refactored into its own module and the various keys should be implemented
|
|
-- (as much as possible) as ordinary handlers
|
|
local manage = nil
|
|
manage = function(plugin_data)
|
|
local plugin_spec = plugin_data.spec
|
|
local spec_line = plugin_data.line
|
|
local spec_type = type(plugin_spec)
|
|
if spec_type == 'string' then
|
|
plugin_spec = { plugin_spec }
|
|
elseif spec_type == 'table' and #plugin_spec > 1 then
|
|
for _, spec in ipairs(plugin_spec) do
|
|
manage { spec = spec, line = spec_line }
|
|
end
|
|
return
|
|
end
|
|
|
|
local log = require_and_configure 'log'
|
|
if plugin_spec[1] == vim.NIL or plugin_spec[1] == nil then
|
|
log.warn('No plugin name provided at line ' .. spec_line .. '!')
|
|
return
|
|
end
|
|
|
|
local name, path = util.get_plugin_short_name(plugin_spec)
|
|
|
|
if name == '' then
|
|
log.warn('"' .. plugin_spec[1] .. '" is an invalid plugin name!')
|
|
return
|
|
end
|
|
|
|
if plugins[name] and not plugins[name].from_requires then
|
|
log.warn('Plugin "' .. name .. '" is used twice! (line ' .. spec_line .. ')')
|
|
return
|
|
end
|
|
|
|
if plugin_spec.as and plugins[plugin_spec.as] then
|
|
log.error(
|
|
'The alias '
|
|
.. plugin_spec.as
|
|
.. ', specified for '
|
|
.. path
|
|
.. ' at '
|
|
.. spec_line
|
|
.. ' is already used as another plugin name!'
|
|
)
|
|
return
|
|
end
|
|
|
|
-- Handle aliases
|
|
plugin_spec.short_name = name
|
|
plugin_spec.name = path
|
|
plugin_spec.path = path
|
|
|
|
-- Some config keys modify a plugin type
|
|
if plugin_spec.opt then
|
|
plugin_spec.manual_opt = true
|
|
elseif plugin_spec.opt == nil and config.opt_default then
|
|
plugin_spec.manual_opt = true
|
|
plugin_spec.opt = true
|
|
end
|
|
|
|
local compile = require_and_configure 'compile'
|
|
for _, key in ipairs(compile.opt_keys) do
|
|
if plugin_spec[key] ~= nil then
|
|
plugin_spec.opt = true
|
|
break
|
|
end
|
|
end
|
|
|
|
plugin_spec.install_path = join_paths(plugin_spec.opt and config.opt_dir or config.start_dir, plugin_spec.short_name)
|
|
|
|
local plugin_utils = require_and_configure 'plugin_utils'
|
|
local plugin_types = require_and_configure 'plugin_types'
|
|
local handlers = require_and_configure 'handlers'
|
|
if not plugin_spec.type then
|
|
plugin_utils.guess_type(plugin_spec)
|
|
end
|
|
if plugin_spec.type ~= plugin_utils.custom_plugin_type then
|
|
plugin_types[plugin_spec.type].setup(plugin_spec)
|
|
end
|
|
for k, v in pairs(plugin_spec) do
|
|
if handlers[k] then
|
|
handlers[k](plugins, plugin_spec, v)
|
|
end
|
|
end
|
|
plugins[plugin_spec.short_name] = plugin_spec
|
|
if plugin_spec.rocks then
|
|
packer.use_rocks(plugin_spec.rocks)
|
|
end
|
|
|
|
-- Add the git URL for displaying in PackerStatus and PackerSync.
|
|
plugins[plugin_spec.short_name].url = util.remove_ending_git_url(plugin_spec.url)
|
|
|
|
if plugin_spec.requires and config.ensure_dependencies then
|
|
-- Handle single plugins given as strings or single plugin specs given as tables
|
|
if
|
|
type(plugin_spec.requires) == 'string'
|
|
or (
|
|
type(plugin_spec.requires) == 'table'
|
|
and not vim.tbl_islist(plugin_spec.requires)
|
|
and #plugin_spec.requires == 1
|
|
)
|
|
then
|
|
plugin_spec.requires = { plugin_spec.requires }
|
|
end
|
|
for _, req in ipairs(plugin_spec.requires) do
|
|
if type(req) == 'string' then
|
|
req = { req }
|
|
end
|
|
local req_name_segments = vim.split(req[1], '/')
|
|
local req_name = req_name_segments[#req_name_segments]
|
|
-- this flag marks a plugin as being from a require which we use to allow
|
|
-- multiple requires for a plugin without triggering a duplicate warning *IF*
|
|
-- the plugin is from a `requires` field and the full specificaiton has not been called yet.
|
|
-- @see: https://github.com/wbthomason/packer.nvim/issues/258#issuecomment-876568439
|
|
req.from_requires = true
|
|
if not plugins[req_name] then
|
|
if config.transitive_opt and plugin_spec.manual_opt then
|
|
req.opt = true
|
|
if type(req.after) == 'string' then
|
|
req.after = { req.after, plugin_spec.short_name }
|
|
elseif type(req.after) == 'table' then
|
|
local already_after = false
|
|
for _, name in ipairs(req.after) do
|
|
already_after = already_after or (name == plugin_spec.short_name)
|
|
end
|
|
if not already_after then
|
|
table.insert(req.after, plugin_spec.short_name)
|
|
end
|
|
elseif req.after == nil then
|
|
req.after = plugin_spec.short_name
|
|
end
|
|
end
|
|
|
|
if config.transitive_disable and plugin_spec.disable then
|
|
req.disable = true
|
|
end
|
|
|
|
manage { spec = req, line = spec_line }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Add a new keyword handler
|
|
packer.set_handler = function(name, func)
|
|
require_and_configure('handlers')[name] = func
|
|
end
|
|
|
|
--- Add a plugin to the managed set
|
|
packer.use = function(plugin_spec)
|
|
plugin_specifications[#plugin_specifications + 1] = {
|
|
spec = plugin_spec,
|
|
line = debug.getinfo(2, 'l').currentline,
|
|
}
|
|
end
|
|
|
|
local function manage_all_plugins()
|
|
local log = require_and_configure 'log'
|
|
log.debug 'Processing plugin specs'
|
|
if plugins == nil or next(plugins) == nil then
|
|
for _, spec in ipairs(plugin_specifications) do
|
|
manage(spec)
|
|
end
|
|
end
|
|
end
|
|
|
|
packer.__manage_all = manage_all_plugins
|
|
|
|
--- Hook to fire events after packer operations
|
|
packer.on_complete = vim.schedule_wrap(function()
|
|
vim.cmd [[doautocmd User PackerComplete]]
|
|
end)
|
|
|
|
--- Hook to fire events after packer compilation
|
|
packer.on_compile_done = function()
|
|
local log = require_and_configure 'log'
|
|
|
|
vim.cmd [[doautocmd User PackerCompileDone]]
|
|
log.debug 'packer.compile: Complete'
|
|
end
|
|
|
|
--- Clean operation:
|
|
-- Finds plugins present in the `packer` package but not in the managed set
|
|
packer.clean = function(results)
|
|
local plugin_utils = require_and_configure 'plugin_utils'
|
|
local a = require 'packer.async'
|
|
local async = a.sync
|
|
local await = a.wait
|
|
local luarocks = require_and_configure 'luarocks'
|
|
local clean = require_and_configure 'clean'
|
|
require_and_configure 'display'
|
|
|
|
manage_all_plugins()
|
|
async(function()
|
|
local luarocks_clean_task = luarocks.clean(rocks, results, nil)
|
|
if luarocks_clean_task ~= nil then
|
|
await(luarocks_clean_task)
|
|
end
|
|
local fs_state = await(plugin_utils.get_fs_state(plugins))
|
|
await(clean(plugins, fs_state, results))
|
|
packer.on_complete()
|
|
end)()
|
|
end
|
|
|
|
--- Install operation:
|
|
-- Takes an optional list of plugin names as an argument. If no list is given, operates on all
|
|
-- managed plugins.
|
|
-- Installs missing plugins, then updates helptags and rplugins
|
|
packer.install = function(...)
|
|
local log = require_and_configure 'log'
|
|
log.debug 'packer.install: requiring modules'
|
|
local plugin_utils = require_and_configure 'plugin_utils'
|
|
local a = require 'packer.async'
|
|
local async = a.sync
|
|
local await = a.wait
|
|
local luarocks = require_and_configure 'luarocks'
|
|
local clean = require_and_configure 'clean'
|
|
local install = require_and_configure 'install'
|
|
local display = require_and_configure 'display'
|
|
|
|
manage_all_plugins()
|
|
local install_plugins
|
|
if ... then
|
|
install_plugins = { ... }
|
|
end
|
|
async(function()
|
|
local fs_state = await(plugin_utils.get_fs_state(plugins))
|
|
if not install_plugins then
|
|
install_plugins = vim.tbl_keys(fs_state.missing)
|
|
end
|
|
if #install_plugins == 0 then
|
|
log.info 'All configured plugins are installed'
|
|
packer.on_complete()
|
|
return
|
|
end
|
|
|
|
await(a.main)
|
|
local start_time = vim.fn.reltime()
|
|
local results = {}
|
|
await(clean(plugins, fs_state, results))
|
|
await(a.main)
|
|
log.debug 'Gathering install tasks'
|
|
local tasks, display_win = install(plugins, install_plugins, results)
|
|
if next(tasks) then
|
|
log.debug 'Gathering Luarocks tasks'
|
|
local luarocks_ensure_task = luarocks.ensure(rocks, results, display_win)
|
|
if luarocks_ensure_task ~= nil then
|
|
table.insert(tasks, luarocks_ensure_task)
|
|
end
|
|
table.insert(tasks, 1, function()
|
|
return not display.status.running
|
|
end)
|
|
table.insert(tasks, 1, config.max_jobs and config.max_jobs or (#tasks - 1))
|
|
log.debug 'Running tasks'
|
|
display_win:update_headline_message('installing ' .. #tasks - 2 .. ' / ' .. #tasks - 2 .. ' plugins')
|
|
a.interruptible_wait_pool(unpack(tasks))
|
|
local install_paths = {}
|
|
for plugin_name, r in pairs(results.installs) do
|
|
if r.ok then
|
|
table.insert(install_paths, results.plugins[plugin_name].install_path)
|
|
end
|
|
end
|
|
|
|
await(a.main)
|
|
plugin_utils.update_helptags(install_paths)
|
|
plugin_utils.update_rplugins()
|
|
local delta = string.gsub(vim.fn.reltimestr(vim.fn.reltime(start_time)), ' ', '')
|
|
display_win:final_results(results, delta)
|
|
packer.on_complete()
|
|
else
|
|
log.info 'Nothing to install!'
|
|
packer.on_complete()
|
|
end
|
|
end)()
|
|
end
|
|
|
|
-- Filter out options specified as the first argument to update or sync
|
|
-- returns the options table and the plugin names
|
|
local filter_opts_from_plugins = function(...)
|
|
local args = { ... }
|
|
local opts = {}
|
|
if not vim.tbl_isempty(args) then
|
|
local first = args[1]
|
|
if type(first) == 'table' then
|
|
table.remove(args, 1)
|
|
opts = first
|
|
elseif first == '--preview' then
|
|
table.remove(args, 1)
|
|
opts = { preview_updates = true }
|
|
end
|
|
end
|
|
if opts.preview_updates == nil and config.preview_updates then
|
|
opts.preview_updates = true
|
|
end
|
|
return opts, util.nonempty_or(args, vim.tbl_keys(plugins))
|
|
end
|
|
|
|
--- Update operation:
|
|
-- Takes an optional list of plugin names as an argument. If no list is given, operates on all
|
|
-- managed plugins.
|
|
-- Fixes plugin types, installs missing plugins, then updates installed plugins and updates helptags
|
|
-- and rplugins
|
|
-- Options can be specified in the first argument as either a table or explicit `'--preview'`.
|
|
packer.update = function(...)
|
|
local log = require_and_configure 'log'
|
|
log.debug 'packer.update: requiring modules'
|
|
local plugin_utils = require_and_configure 'plugin_utils'
|
|
local a = require 'packer.async'
|
|
local async = a.sync
|
|
local await = a.wait
|
|
local luarocks = require_and_configure 'luarocks'
|
|
local clean = require_and_configure 'clean'
|
|
local install = require_and_configure 'install'
|
|
local display = require_and_configure 'display'
|
|
local update = require_and_configure 'update'
|
|
|
|
manage_all_plugins()
|
|
|
|
local opts, update_plugins = filter_opts_from_plugins(...)
|
|
async(function()
|
|
local start_time = vim.fn.reltime()
|
|
local results = {}
|
|
local fs_state = await(plugin_utils.get_fs_state(plugins))
|
|
local missing_plugins, installed_plugins = util.partition(vim.tbl_keys(fs_state.missing), update_plugins)
|
|
update.fix_plugin_types(plugins, missing_plugins, results, fs_state)
|
|
await(clean(plugins, fs_state, results))
|
|
local _
|
|
_, missing_plugins = util.partition(vim.tbl_keys(results.moves), missing_plugins)
|
|
log.debug 'Gathering install tasks'
|
|
await(a.main)
|
|
local tasks, display_win = install(plugins, missing_plugins, results)
|
|
local update_tasks
|
|
log.debug 'Gathering update tasks'
|
|
await(a.main)
|
|
update_tasks, display_win = update(plugins, installed_plugins, display_win, results, opts)
|
|
vim.list_extend(tasks, update_tasks)
|
|
log.debug 'Gathering luarocks tasks'
|
|
local luarocks_ensure_task = luarocks.ensure(rocks, results, display_win)
|
|
if luarocks_ensure_task ~= nil then
|
|
table.insert(tasks, luarocks_ensure_task)
|
|
end
|
|
|
|
if #tasks == 0 then
|
|
return
|
|
end
|
|
|
|
table.insert(tasks, 1, function()
|
|
return not display.status.running
|
|
end)
|
|
table.insert(tasks, 1, config.max_jobs and config.max_jobs or (#tasks - 1))
|
|
display_win:update_headline_message('updating ' .. #tasks - 2 .. ' / ' .. #tasks - 2 .. ' plugins')
|
|
log.debug 'Running tasks'
|
|
a.interruptible_wait_pool(unpack(tasks))
|
|
local install_paths = {}
|
|
for plugin_name, r in pairs(results.installs) do
|
|
if r.ok then
|
|
table.insert(install_paths, results.plugins[plugin_name].install_path)
|
|
end
|
|
end
|
|
|
|
for plugin_name, r in pairs(results.updates) do
|
|
if r.ok then
|
|
table.insert(install_paths, results.plugins[plugin_name].install_path)
|
|
end
|
|
end
|
|
|
|
await(a.main)
|
|
plugin_utils.update_helptags(install_paths)
|
|
plugin_utils.update_rplugins()
|
|
local delta = string.gsub(vim.fn.reltimestr(vim.fn.reltime(start_time)), ' ', '')
|
|
display_win:final_results(results, delta, opts)
|
|
packer.on_complete()
|
|
end)()
|
|
end
|
|
|
|
--- Sync operation:
|
|
-- Takes an optional list of plugin names as an argument. If no list is given, operates on all
|
|
-- managed plugins.
|
|
-- Runs (in sequence):
|
|
-- - Update plugin types
|
|
-- - Clean stale plugins
|
|
-- - Install missing plugins and update installed plugins
|
|
-- - Update helptags and rplugins
|
|
packer.sync = function(...)
|
|
local log = require_and_configure 'log'
|
|
log.debug 'packer.sync: requiring modules'
|
|
local plugin_utils = require_and_configure 'plugin_utils'
|
|
local a = require 'packer.async'
|
|
local async = a.sync
|
|
local await = a.wait
|
|
local luarocks = require_and_configure 'luarocks'
|
|
local clean = require_and_configure 'clean'
|
|
local install = require_and_configure 'install'
|
|
local display = require_and_configure 'display'
|
|
local update = require_and_configure 'update'
|
|
|
|
manage_all_plugins()
|
|
|
|
local opts, sync_plugins = filter_opts_from_plugins(...)
|
|
async(function()
|
|
local start_time = vim.fn.reltime()
|
|
local results = {}
|
|
local fs_state = await(plugin_utils.get_fs_state(plugins))
|
|
local missing_plugins, installed_plugins = util.partition(vim.tbl_keys(fs_state.missing), sync_plugins)
|
|
|
|
await(a.main)
|
|
update.fix_plugin_types(plugins, missing_plugins, results, fs_state)
|
|
local _
|
|
_, missing_plugins = util.partition(vim.tbl_keys(results.moves), missing_plugins)
|
|
if config.auto_clean then
|
|
await(clean(plugins, fs_state, results))
|
|
_, installed_plugins = util.partition(vim.tbl_keys(results.removals), installed_plugins)
|
|
end
|
|
|
|
await(a.main)
|
|
log.debug 'Gathering install tasks'
|
|
local tasks, display_win = install(plugins, missing_plugins, results)
|
|
local update_tasks
|
|
log.debug 'Gathering update tasks'
|
|
await(a.main)
|
|
update_tasks, display_win = update(plugins, installed_plugins, display_win, results, opts)
|
|
vim.list_extend(tasks, update_tasks)
|
|
log.debug 'Gathering luarocks tasks'
|
|
local luarocks_clean_task = luarocks.clean(rocks, results, display_win)
|
|
if luarocks_clean_task ~= nil then
|
|
table.insert(tasks, luarocks_clean_task)
|
|
end
|
|
|
|
local luarocks_ensure_task = luarocks.ensure(rocks, results, display_win)
|
|
if luarocks_ensure_task ~= nil then
|
|
table.insert(tasks, luarocks_ensure_task)
|
|
end
|
|
if #tasks == 0 then
|
|
return
|
|
end
|
|
|
|
table.insert(tasks, 1, function()
|
|
return not display.status.running
|
|
end)
|
|
table.insert(tasks, 1, config.max_jobs and config.max_jobs or (#tasks - 1))
|
|
log.debug 'Running tasks'
|
|
display_win:update_headline_message('syncing ' .. #tasks - 2 .. ' / ' .. #tasks - 2 .. ' plugins')
|
|
a.interruptible_wait_pool(unpack(tasks))
|
|
local install_paths = {}
|
|
for plugin_name, r in pairs(results.installs) do
|
|
if r.ok then
|
|
table.insert(install_paths, results.plugins[plugin_name].install_path)
|
|
end
|
|
end
|
|
|
|
for plugin_name, r in pairs(results.updates) do
|
|
if r.ok then
|
|
table.insert(install_paths, results.plugins[plugin_name].install_path)
|
|
end
|
|
end
|
|
|
|
await(a.main)
|
|
if config.compile_on_sync then
|
|
packer.compile(nil, false)
|
|
end
|
|
plugin_utils.update_helptags(install_paths)
|
|
plugin_utils.update_rplugins()
|
|
local delta = string.gsub(vim.fn.reltimestr(vim.fn.reltime(start_time)), ' ', '')
|
|
display_win:final_results(results, delta, opts)
|
|
packer.on_complete()
|
|
end)()
|
|
end
|
|
|
|
packer.status = function()
|
|
local async = require('packer.async').sync
|
|
local display = require_and_configure 'display'
|
|
local log = require_and_configure 'log'
|
|
manage_all_plugins()
|
|
async(function()
|
|
local display_win = display.open(config.display.open_fn or config.display.open_cmd)
|
|
if _G.packer_plugins ~= nil then
|
|
display_win:status(_G.packer_plugins)
|
|
else
|
|
log.warn 'packer_plugins table is nil! Cannot run packer.status()!'
|
|
end
|
|
end)()
|
|
end
|
|
|
|
local function reload_module(name)
|
|
if name then
|
|
package.loaded[name] = nil
|
|
return require(name)
|
|
end
|
|
end
|
|
|
|
--- Search through all the loaded packages for those that
|
|
--- return a function, then cross reference them with all
|
|
--- the plugin configs and setups and if there are any matches
|
|
--- reload the user module.
|
|
local function refresh_configs(plugs)
|
|
local reverse_index = {}
|
|
for k, v in pairs(package.loaded) do
|
|
if type(v) == 'function' then
|
|
reverse_index[v] = k
|
|
end
|
|
end
|
|
for _, plugin in pairs(plugs) do
|
|
local cfg = reload_module(reverse_index[plugin.config])
|
|
local setup = reload_module(reverse_index[plugin.setup])
|
|
if cfg then
|
|
plugin.config = cfg
|
|
end
|
|
if setup then
|
|
plugin.setup = setup
|
|
end
|
|
end
|
|
end
|
|
|
|
local function parse_value(value)
|
|
if value == 'true' then
|
|
return true
|
|
end
|
|
if value == 'false' then
|
|
return false
|
|
end
|
|
return value
|
|
end
|
|
|
|
local function parse_args(args)
|
|
local result = {}
|
|
if args then
|
|
local parts = vim.split(args, ' ')
|
|
for _, part in ipairs(parts) do
|
|
if part then
|
|
if part:find '=' then
|
|
local key, value = unpack(vim.split(part, '='))
|
|
result[key] = parse_value(value)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
--- Update the compiled lazy-loader code
|
|
--- Takes an optional argument of a path to which to output the resulting compiled code
|
|
packer.compile = function(raw_args, move_plugins)
|
|
local compile = require_and_configure 'compile'
|
|
local log = require_and_configure 'log'
|
|
local a = require 'packer.async'
|
|
local async = a.sync
|
|
local await = a.wait
|
|
|
|
manage_all_plugins()
|
|
async(function()
|
|
if move_plugins ~= false then
|
|
local update = require_and_configure 'update'
|
|
local plugin_utils = require_and_configure 'plugin_utils'
|
|
local fs_state = await(plugin_utils.get_fs_state(plugins))
|
|
await(a.main)
|
|
update.fix_plugin_types(plugins, vim.tbl_keys(fs_state.missing), {}, fs_state)
|
|
end
|
|
local args = parse_args(raw_args)
|
|
local output_path = args.output_path or config.compile_path
|
|
local output_lua = vim.fn.fnamemodify(output_path, ':e') == 'lua'
|
|
local should_profile = args.profile
|
|
-- the user might explicitly choose for this value to be false in which case
|
|
-- an or operator will not work
|
|
if should_profile == nil then
|
|
should_profile = config.profile.enable
|
|
end
|
|
refresh_configs(plugins)
|
|
-- NOTE: we copy the plugins table so the in memory value is not mutated during compilation
|
|
local compiled_loader = compile(vim.deepcopy(plugins), output_lua, should_profile)
|
|
output_path = vim.fn.expand(output_path, true)
|
|
vim.fn.mkdir(vim.fn.fnamemodify(output_path, ':h'), 'p')
|
|
local output_file = io.open(output_path, 'w')
|
|
output_file:write(compiled_loader)
|
|
output_file:close()
|
|
if config.auto_reload_compiled then
|
|
local configs_to_run = {}
|
|
if _G.packer_plugins ~= nil then
|
|
for plugin_name, plugin_info in pairs(_G.packer_plugins) do
|
|
if plugin_info.loaded and plugin_info.config and plugins[plugin_name] and plugins[plugin_name].cmd then
|
|
configs_to_run[plugin_name] = plugin_info.config
|
|
end
|
|
end
|
|
end
|
|
|
|
vim.cmd('source ' .. output_path)
|
|
for plugin_name, plugin_config in pairs(configs_to_run) do
|
|
for _, config_line in ipairs(plugin_config) do
|
|
local success, err = pcall(loadstring(config_line), plugin_name, _G.packer_plugins[plugin_name])
|
|
if not success then
|
|
log.error('Error running config for ' .. plugin_name .. ': ' .. vim.inspect(err))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
log.info 'Finished compiling lazy-loaders!'
|
|
packer.on_compile_done()
|
|
end)()
|
|
end
|
|
|
|
packer.profile_output = function()
|
|
local async = require('packer.async').sync
|
|
local display = require_and_configure 'display'
|
|
local log = require_and_configure 'log'
|
|
|
|
manage_all_plugins()
|
|
if _G._packer.profile_output then
|
|
async(function()
|
|
local display_win = display.open(config.display.open_fn or config.display.open_cmd)
|
|
display_win:profile_output(_G._packer.profile_output)
|
|
end)()
|
|
else
|
|
log.warn 'You must run PackerCompile with profiling enabled first e.g. PackerCompile profile=true'
|
|
end
|
|
end
|
|
|
|
-- Load plugins
|
|
-- @param plugins string String of space separated plugins names
|
|
-- intended for PackerLoad command
|
|
-- or list of plugin names as independent strings
|
|
packer.loader = function(...)
|
|
local plugin_names = { ... }
|
|
local force = plugin_names[#plugin_names] == true
|
|
if type(plugin_names[#plugin_names]) == 'boolean' then
|
|
plugin_names[#plugin_names] = nil
|
|
end
|
|
|
|
-- We make a new table here because it's more convenient than expanding a space-separated string
|
|
-- into the existing plugin_names
|
|
local plugin_list = {}
|
|
for _, plugin_name in ipairs(plugin_names) do
|
|
vim.list_extend(
|
|
plugin_list,
|
|
vim.tbl_filter(function(name)
|
|
return #name > 0
|
|
end, vim.split(plugin_name, ' '))
|
|
)
|
|
end
|
|
|
|
require 'packer.load'(plugin_list, {}, _G.packer_plugins, force)
|
|
end
|
|
|
|
-- Completion for not yet loaded plugins
|
|
-- Intended to provide completion for PackerLoad command
|
|
packer.loader_complete = function(lead, _, _)
|
|
local completion_list = {}
|
|
for name, plugin in pairs(_G.packer_plugins) do
|
|
if vim.startswith(name, lead) and not plugin.loaded then
|
|
table.insert(completion_list, name)
|
|
end
|
|
end
|
|
table.sort(completion_list)
|
|
return completion_list
|
|
end
|
|
|
|
-- Completion user plugins
|
|
-- Intended to provide completion for PackerUpdate/Sync/Install command
|
|
packer.plugin_complete = function(lead, _, _)
|
|
local completion_list = vim.tbl_filter(function(name)
|
|
return vim.startswith(name, lead)
|
|
end, vim.tbl_keys(_G.packer_plugins))
|
|
table.sort(completion_list)
|
|
return completion_list
|
|
end
|
|
|
|
---Snapshots installed plugins
|
|
---@param snapshot_name string absolute path or just a snapshot name
|
|
packer.snapshot = function(snapshot_name, ...)
|
|
local async = require('packer.async').sync
|
|
local await = require('packer.async').wait
|
|
local snapshot = require 'packer.snapshot'
|
|
local log = require_and_configure 'log'
|
|
local args = { ... }
|
|
snapshot_name = snapshot_name or require('os').date '%Y-%m-%d'
|
|
local snapshot_path = vim.fn.expand(snapshot_name)
|
|
|
|
local fmt = string.format
|
|
log.debug(fmt('Taking snapshots of currently installed plugins to %s...', snapshot_name))
|
|
if vim.fn.fnamemodify(snapshot_name, ':p') ~= snapshot_path then -- is not absolute path
|
|
if config.snapshot_path == nil then
|
|
log.warn 'config.snapshot_path is not set'
|
|
return
|
|
else
|
|
snapshot_path = util.join_paths(config.snapshot_path, snapshot_path) -- set to default path
|
|
end
|
|
end
|
|
|
|
manage_all_plugins()
|
|
|
|
local target_plugins = plugins
|
|
if next(args) ~= nil then -- provided extra args
|
|
target_plugins = vim.tbl_filter( -- filter plugins
|
|
function(plugin)
|
|
for k, plugin_shortname in pairs(args) do
|
|
if plugin_shortname == plugin.short_name then
|
|
args[k] = nil
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end,
|
|
plugins
|
|
)
|
|
end
|
|
|
|
local write_snapshot = true
|
|
|
|
if vim.fn.filereadable(snapshot_path) == 1 then
|
|
vim.ui.select(
|
|
{ 'Replace', 'Cancel' },
|
|
{ prompt = fmt("Do you want to replace '%s'?", snapshot_path) },
|
|
function(_, idx)
|
|
write_snapshot = idx == 1
|
|
end
|
|
)
|
|
end
|
|
|
|
async(function()
|
|
if write_snapshot then
|
|
await(snapshot.create(snapshot_path, target_plugins))
|
|
:map_ok(function(ok)
|
|
log.info(ok.message)
|
|
if next(ok.failed) then
|
|
log.warn("Couldn't snapshot " .. vim.inspect(ok.failed))
|
|
end
|
|
end)
|
|
:map_err(function(err)
|
|
log.warn(err.message)
|
|
end)
|
|
end
|
|
end)()
|
|
end
|
|
|
|
---Instantly rolls back plugins to a previous state specified by `snapshot_name`
|
|
---If `snapshot_name` doesn't exist an error will be displayed
|
|
---@param snapshot_name string @name of the snapshot or the absolute path to the snapshot
|
|
---@vararg string @ if provided, the only plugins to be rolled back,
|
|
---otherwise all the plugins will be rolled back
|
|
packer.rollback = function(snapshot_name, ...)
|
|
local args = { ... }
|
|
local a = require 'packer.async'
|
|
local async = a.sync
|
|
local await = a.wait
|
|
local wait_all = a.wait_all
|
|
local snapshot = require 'packer.snapshot'
|
|
local log = require_and_configure 'log'
|
|
local fmt = string.format
|
|
|
|
async(function()
|
|
manage_all_plugins()
|
|
|
|
local snapshot_path = vim.loop.fs_realpath(util.join_paths(config.snapshot_path, snapshot_name))
|
|
or vim.loop.fs_realpath(snapshot_name)
|
|
|
|
if snapshot_path == nil then
|
|
local warn = fmt("Snapshot '%s' is wrong or doesn't exist", snapshot_name)
|
|
log.warn(warn)
|
|
return
|
|
end
|
|
|
|
local target_plugins = plugins
|
|
|
|
if next(args) ~= nil then -- provided extra args
|
|
target_plugins = vim.tbl_filter(function(plugin)
|
|
for _, plugin_sname in pairs(args) do
|
|
if plugin_sname == plugin.short_name then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end, plugins)
|
|
end
|
|
|
|
await(snapshot.rollback(snapshot_path, target_plugins))
|
|
:map_ok(function(ok)
|
|
await(a.main)
|
|
log.info('Rollback to "' .. snapshot_path .. '" completed')
|
|
if next(ok.failed) then
|
|
log.warn("Couldn't rollback " .. vim.inspect(ok.failed))
|
|
end
|
|
end)
|
|
:map_err(function(err)
|
|
await(a.main)
|
|
log.error(err)
|
|
end)
|
|
|
|
packer.on_complete()
|
|
end)()
|
|
end
|
|
|
|
packer.config = config
|
|
|
|
--- Convenience function for simple setup
|
|
-- Can be invoked as follows:
|
|
-- spec can be a function:
|
|
-- packer.startup(function() use 'tjdevries/colorbuddy.vim' end)
|
|
--
|
|
-- spec can be a table with a function as its first element and config overrides as another
|
|
-- element:
|
|
-- packer.startup({function() use 'tjdevries/colorbuddy.vim' end, config = { ... }})
|
|
--
|
|
-- spec can be a table with a table of plugin specifications as its first element, config overrides
|
|
-- as another element, and an optional table of Luarocks rock specifications as another element:
|
|
-- packer.startup({{'tjdevries/colorbuddy.vim'}, config = { ... }, rocks = { ... }})
|
|
packer.startup = function(spec)
|
|
local log = require 'packer.log'
|
|
local user_func = nil
|
|
local user_config = nil
|
|
local user_plugins = nil
|
|
local user_rocks = nil
|
|
if type(spec) == 'function' then
|
|
user_func = spec
|
|
elseif type(spec) == 'table' then
|
|
if type(spec[1]) == 'function' then
|
|
user_func = spec[1]
|
|
elseif type(spec[1]) == 'table' then
|
|
user_plugins = spec[1]
|
|
user_rocks = spec.rocks
|
|
else
|
|
log.error 'You must provide a function or table of specifications as the first element of the argument to startup!'
|
|
return
|
|
end
|
|
|
|
-- NOTE: It might be more convenient for users to allow arbitrary config keys to be specified
|
|
-- and to merge them, but I feel that only avoids a single layer of nesting and adds more
|
|
-- complication here, so I'm not sure if the benefit justifies the cost
|
|
user_config = spec.config
|
|
end
|
|
|
|
packer.init(user_config)
|
|
packer.reset()
|
|
log = require_and_configure 'log'
|
|
|
|
if user_func then
|
|
setfenv(user_func, vim.tbl_extend('force', getfenv(), { use = packer.use, use_rocks = packer.use_rocks }))
|
|
local status, err = pcall(user_func, packer.use, packer.use_rocks)
|
|
if not status then
|
|
log.error('Failure running setup function: ' .. vim.inspect(err))
|
|
error(err)
|
|
end
|
|
else
|
|
packer.use(user_plugins)
|
|
if user_rocks then
|
|
packer.use_rocks(user_rocks)
|
|
end
|
|
end
|
|
|
|
if config.snapshot ~= nil then
|
|
packer.rollback(config.snapshot)
|
|
end
|
|
|
|
return packer
|
|
end
|
|
|
|
return packer
|