132 lines
3.4 KiB
Lua
132 lines
3.4 KiB
Lua
local M = {}
|
|
local async_install_started = false
|
|
|
|
local function has_valid_head(path)
|
|
if vim.fn.isdirectory(path .. "/.git") == 0 then
|
|
return true
|
|
end
|
|
|
|
vim.fn.system({ "git", "-C", path, "rev-parse", "--verify", "HEAD" })
|
|
return vim.v.shell_error == 0
|
|
end
|
|
|
|
local function prune_broken_pack_repos()
|
|
local root = vim.fn.stdpath("data") .. "/site/pack/core/opt"
|
|
if vim.fn.isdirectory(root) == 0 then
|
|
return
|
|
end
|
|
|
|
for _, dir in ipairs(vim.fn.readdir(root)) do
|
|
local path = root .. "/" .. dir
|
|
if vim.fn.isdirectory(path) == 1 and not has_valid_head(path) then
|
|
vim.fn.delete(path, "rf")
|
|
end
|
|
end
|
|
end
|
|
|
|
local function to_src(value)
|
|
if value:match("^[%w+.-]+://") or value:match("^git@") then
|
|
return value
|
|
end
|
|
|
|
return "https://github.com/" .. value
|
|
end
|
|
|
|
local function plugin_name(spec)
|
|
if type(spec) == "table" and type(spec.name) == "string" and spec.name ~= "" then
|
|
return spec.name
|
|
end
|
|
|
|
local src = type(spec) == "string" and spec or spec.src
|
|
local name = src:gsub("/$", ""):match("([^/]+)$") or src
|
|
return name:gsub("%.git$", "")
|
|
end
|
|
|
|
local function plugin_installed(spec)
|
|
local root = vim.fn.stdpath("data") .. "/site/pack/core/opt"
|
|
local path = root .. "/" .. plugin_name(spec)
|
|
return vim.fn.isdirectory(path) == 1
|
|
end
|
|
|
|
local function install_missing_async(specs)
|
|
if async_install_started or #specs == 0 then
|
|
return
|
|
end
|
|
|
|
async_install_started = true
|
|
|
|
local specs_json = vim.json.encode(specs)
|
|
local lua_expr = string.format(
|
|
"local s=vim.json.decode(%q); vim.pack.add(s,{confirm=false,load=false})",
|
|
specs_json
|
|
)
|
|
|
|
vim.system({
|
|
vim.v.progpath,
|
|
"--headless",
|
|
"-u",
|
|
"NONE",
|
|
"+lua " .. lua_expr,
|
|
"+qall",
|
|
}, { detach = true })
|
|
end
|
|
|
|
function M.add(specs)
|
|
local resolved = {}
|
|
|
|
for _, spec in ipairs(specs) do
|
|
if type(spec) == "string" then
|
|
table.insert(resolved, to_src(spec))
|
|
else
|
|
local plugin = vim.deepcopy(spec)
|
|
plugin.src = to_src(plugin.src)
|
|
table.insert(resolved, plugin)
|
|
end
|
|
end
|
|
|
|
local installed = {}
|
|
local missing = {}
|
|
|
|
for _, spec in ipairs(resolved) do
|
|
if plugin_installed(spec) then
|
|
table.insert(installed, spec)
|
|
else
|
|
table.insert(missing, spec)
|
|
end
|
|
end
|
|
|
|
if #missing > 0 then
|
|
install_missing_async(missing)
|
|
vim.schedule(function()
|
|
vim.notify(
|
|
"Missing plugins detected. Started background install; restart Neovim when it finishes.",
|
|
vim.log.levels.WARN
|
|
)
|
|
end)
|
|
end
|
|
|
|
if #installed == 0 then
|
|
return false
|
|
end
|
|
|
|
local ok, err = pcall(vim.pack.add, installed, { confirm = false, load = true })
|
|
|
|
if not ok and tostring(err):find("ambiguous argument 'HEAD'", 1, true) then
|
|
prune_broken_pack_repos()
|
|
vim.fn.delete(vim.fn.stdpath("config") .. "/nvim-pack-lock.json")
|
|
ok, err = pcall(vim.pack.add, installed, { confirm = false, load = true })
|
|
end
|
|
|
|
if not ok then
|
|
vim.schedule(function()
|
|
local msg = "vim.pack failed to load installed plugins."
|
|
vim.notify(msg .. "\n" .. tostring(err), vim.log.levels.WARN)
|
|
end)
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
return M
|