dotfiles/.config/nvim/lua/pack.lua

293 lines
7.9 KiB
Lua

local M = {}
local function normalize_url(src)
-- Check for nil first
if not src or type(src) ~= "string" or src == "" then
error("Plugin source must be a non-empty string, got: " .. tostring(src))
end
-- Already a full URL (http/https/git)
if src:match("^https?://") or src:match("^git@") then
return src
end
-- SSH format: git@github.com:user/repo.git
if src:match("^git@[%w%.%-]+:") then
return src
end
-- GitHub shorthand: user/repo or user/repo.git
if src:match("^[%w-_%.]+/[%w-_%.]+%.?g?i?t?$") then
local clean_src = src:gsub("%.git$", "")
return "https://github.com/" .. clean_src .. ".git"
end
-- Local path or other format - pass through
return src
end
local function extract_plugin_name(original_src)
if not original_src then return nil end
-- Extract repo name from various formats
local repo = original_src:match("([^/]+)$") -- Get last part after /
if repo then
-- Remove common suffixes
repo = repo:gsub("%.git$", "")
repo = repo:gsub("%.nvim$", "")
repo = repo:gsub("%.lua$", "")
repo = repo:gsub("^nvim%-", "")
return repo
end
return original_src
end
local function is_single_plugin_spec(plugins)
-- Check if it's a single plugin spec vs array of plugin specs
if type(plugins) ~= "table" then
return false
end
-- If plugins[1] is a string, it's a single spec: {"user/repo", setup = {...}}
if type(plugins[1]) == "string" then
return true
end
-- If it has src but plugins[1] is not a table, it's a single spec: {src = "user/repo"}
if plugins.src and type(plugins[1]) ~= "table" then
return true
end
-- Otherwise it's an array: {{src = "..."}, {src = "..."}} or {"user/repo", "user/repo2"}
return false
end
function M._do_setup(spec, src)
local function do_setup()
-- vim.notify("Setting up " .. src)
if spec.setup then
local plugin_name = spec.module or extract_plugin_name(src)
if not plugin_name then
error("Could not determine plugin name for setup")
end
if spec.setup == true then
-- Auto-setup with defaults
require(plugin_name).setup()
elseif type(spec.setup) == "table" then
-- Setup with config
require(plugin_name).setup(spec.setup)
end
end
if spec.config then
if type(spec.config) == "function" then
spec.config()
end
end
end
if spec.config or spec.setup or spec.build then
local setup_ok, setup_err = pcall(do_setup)
if not setup_ok then
vim.notify("Failed to setup " .. (src or "unknown") .. ": " .. setup_err, vim.log.levels.WARN)
end
end
end
-- Main function - handles single plugin or multiple plugins
function M.add(plugins)
-- Single string plugin
if type(plugins) == "string" then
if plugins == "" then
error("Plugin source cannot be empty string")
end
local ok, err = pcall(vim.pack.add, normalize_url(plugins))
if not ok then
vim.notify("Failed to add plugin " .. plugins .. ": " .. err, vim.log.levels.ERROR)
end
return
end
if type(plugins) == "table" then
-- Check if it's a single plugin spec
if is_single_plugin_spec(plugins) then
local plugin = plugins
local src = plugin[1] or plugin.src
if not src or src == "" then
error("Plugin src is required and cannot be empty")
end
-- Normalize and modify in place
local normalized_src = normalize_url(src)
local original_src = src
-- Store build spec for event handling
if plugin.build then
plugin.data = plugin.data or {}
plugin.data.build = plugin.build
end
-- Create clean spec for vim.pack.add
local pack_spec = {}
for k, v in pairs(plugins) do
if k ~= "config" and k ~= "setup" and k ~= "module" and k ~= "build" and k ~= 1 then
pack_spec[k] = v
end
end
pack_spec.src = normalized_src
local ok, err = pcall(vim.pack.add, pack_spec)
if not ok then
vim.notify("Failed to add plugin " .. src .. ": " .. err, vim.log.levels.ERROR)
return
end
-- Do setup if needed
M._do_setup(plugin, original_src)
return
end
-- Multiple plugins - modify in place and collect setup specs
local setup_specs = {}
for i, plugin in ipairs(plugins) do
if type(plugin) == "string" then
if not plugin or plugin == "" then
vim.notify("Plugin " .. i .. " is empty or nil, skipping", vim.log.levels.WARN)
-- Replace with nil to skip
plugins[i] = nil
goto continue
end
-- Convert string to table
plugins[i] = { src = normalize_url(plugin) }
elseif type(plugin) == "table" then
local src = plugin[1] or plugin.src
if not src or type(src) ~= "string" or src == "" then
vim.notify("Plugin " .. i .. " missing or invalid src, skipping", vim.log.levels.WARN)
plugins[i] = nil
goto continue
end
local original_src = src
local normalized_src = normalize_url(src)
-- Store build spec for event handling
if plugin.build then
plugin.data = plugin.data or {}
plugin.data.build = plugin.build
end
-- Cache setup info if needed
if plugin.config or plugin.setup then
setup_specs[#setup_specs + 1] = {
spec = {
config = plugin.config, -- Save the actual function/table
setup = plugin.setup,
module = plugin.module,
},
original_src = original_src
}
end
-- Normalize src and remove our custom keys
plugin.src = normalized_src
plugin.config = nil
plugin.setup = nil
plugin.module = nil
plugin.build = nil
plugin[1] = nil -- Remove positional src
else
vim.notify("Plugin " .. i .. " must be string or table, got " .. type(plugin), vim.log.levels.WARN)
plugins[i] = nil
end
::continue::
end
-- Remove nil entries
local clean_plugins = {}
for _, plugin in ipairs(plugins) do
if plugin then
clean_plugins[#clean_plugins + 1] = plugin
end
end
-- Download all plugins in one call
if #clean_plugins > 0 then
local ok, err = pcall(vim.pack.add, clean_plugins)
if not ok then
vim.notify("Failed to add plugin batch: " .. err, vim.log.levels.ERROR)
return
end
end
-- Now do setup for plugins that need it
for _, setup_info in ipairs(setup_specs) do
M._do_setup(setup_info.spec, setup_info.original_src)
end
else
error("plugins must be a string or table, got: " .. type(plugins))
end
end
-- Alias for convenience
M.use = M.add
function M._run_build_command(build_cmd, plugin_dir, plugin_name, on_success)
if not build_cmd then return end
local cmd_type = type(build_cmd)
if cmd_type == "string" then
vim.notify("Building " .. plugin_name .. "...", vim.log.levels.INFO)
vim.fn.jobstart({
"sh", "-c",
string.format("cd '%s' && %s", plugin_dir, build_cmd)
}, {
on_exit = function(_, exit_code)
if exit_code == 0 then
vim.notify("Build completed for " .. plugin_name, vim.log.levels.INFO)
if on_success then on_success() end
else
vim.notify("Build failed for " .. plugin_name, vim.log.levels.ERROR)
end
end
})
elseif cmd_type == "function" then
local ok, err = pcall(build_cmd, plugin_dir)
if ok and on_success then
on_success()
elseif not ok then
vim.notify("Build function failed for " .. plugin_name .. ": " .. err, vim.log.levels.ERROR)
end
end
end
--buf = 1,
--data = {
-- kind = "delete",
-- path = "/Users/zoomiti/.local/share/nvim/site/pack/core/opt/blink.cmp",
-- spec = {
-- name = "blink.cmp",
-- src = "https://github.com/Saghen/blink.cmp.git"
-- }
--},
--event = "PackChanged",
--file = "/Users/zoomiti/.local/share/nvim/site/pack/core/opt/blink.cmp",
--id = 14,
--match = "/Users/zoomiti/.local/share/nvim/site/pack/core/opt/blink.cmp"
local augroup = vim.api.nvim_create_augroup('most_basic_build_system', { clear = false })
vim.api.nvim_create_autocmd("PackChanged", {
group = augroup,
pattern = "*",
callback = function(ev)
if ev.data.kind ~= 'delete' and ev.data.spec.data and ev.data.spec.data.build and type(ev.data.spec.data.build) == "string" then
vim.system({ "sh", "-c" }, { stdin = 'cd' .. ev.data.path .. ' && ' .. ev.data.spec.data.build })
end
end
})
return M