feat(pack): support file:// dev plugins via symlink
This commit is contained in:
+142
-11
@@ -79,6 +79,77 @@ local function is_url(src)
|
||||
return src:find("://") ~= nil
|
||||
end
|
||||
|
||||
---@param src string
|
||||
---@return string?
|
||||
local function file_url_to_path(src)
|
||||
local path = src:match("^file://(/.+)$")
|
||||
if not path then
|
||||
return nil
|
||||
end
|
||||
return vim.fs.normalize(path)
|
||||
end
|
||||
|
||||
---@param path string
|
||||
---@return string?
|
||||
local function plugin_name_from_path(path)
|
||||
local name = vim.fs.basename(path)
|
||||
if not name or name == "" or name == "." or name == ".." then
|
||||
return nil
|
||||
end
|
||||
return name
|
||||
end
|
||||
|
||||
local data_dir = vim.fn.stdpath("data")
|
||||
if type(data_dir) == "table" then
|
||||
data_dir = assert(data_dir[1])
|
||||
end
|
||||
local dev_opt_dir = vim.fs.joinpath(data_dir, "site", "pack", "dev", "opt")
|
||||
|
||||
---@param target string
|
||||
---@param name string
|
||||
---@return string? link
|
||||
---@return boolean changed
|
||||
local function ensure_dev_link(target, name)
|
||||
if not vim.uv.fs_stat(target) then
|
||||
log.error("pack: dev plugin path does not exist: %s", target)
|
||||
return nil, false
|
||||
end
|
||||
|
||||
local ok, info = pcall(vim.pack.get, { name })
|
||||
if ok and info and #info > 0 then
|
||||
pcall(vim.pack.del, { name }, { force = true })
|
||||
end
|
||||
|
||||
vim.fn.mkdir(dev_opt_dir, "p")
|
||||
local link = vim.fs.joinpath(dev_opt_dir, name)
|
||||
local lstat = vim.uv.fs_lstat(link)
|
||||
if lstat then
|
||||
if lstat.type == "link" then
|
||||
if vim.uv.fs_readlink(link) == target then
|
||||
return link, false
|
||||
end
|
||||
local ok_unlink, err = vim.uv.fs_unlink(link)
|
||||
if not ok_unlink then
|
||||
log.error("pack: failed to unlink %s: %s", link, err)
|
||||
return nil, false
|
||||
end
|
||||
else
|
||||
log.error(
|
||||
"pack: %s exists and is not a symlink; refusing to overwrite",
|
||||
link
|
||||
)
|
||||
return nil, false
|
||||
end
|
||||
end
|
||||
|
||||
local ok_link, err = vim.uv.fs_symlink(target, link)
|
||||
if not ok_link then
|
||||
log.error("pack: failed to symlink %s -> %s: %s", link, target, err)
|
||||
return nil, false
|
||||
end
|
||||
return link, true
|
||||
end
|
||||
|
||||
---@param spec string | ow.Pack.Spec
|
||||
---@return vim.pack.Spec
|
||||
local function to_pack_spec(spec)
|
||||
@@ -267,22 +338,59 @@ end
|
||||
function M.setup(specs)
|
||||
local pack_specs = {}
|
||||
local order = {}
|
||||
local dev_changed = {}
|
||||
for _, spec in ipairs(specs) do
|
||||
local spec_t = type(spec) == "table" and spec or nil
|
||||
local src = type(spec) == "string" and spec or spec.src
|
||||
table.insert(order, src)
|
||||
if is_url(src) then
|
||||
local dev_path = file_url_to_path(src)
|
||||
if dev_path then
|
||||
local name = (spec_t and spec_t.name)
|
||||
or plugin_name_from_path(dev_path)
|
||||
if not name then
|
||||
log.error("pack: invalid plugin name derived from %s", src)
|
||||
else
|
||||
local link, did_change = ensure_dev_link(dev_path, name)
|
||||
if link then
|
||||
local ok_add, add_err = pcall(vim.cmd.packadd, name)
|
||||
if not ok_add then
|
||||
log.error(
|
||||
"pack: failed to packadd %s: %s",
|
||||
name,
|
||||
add_err
|
||||
)
|
||||
else
|
||||
---@type ow.Pack.Plugin
|
||||
M.plugins[src] = {
|
||||
src = src,
|
||||
name = name,
|
||||
version = spec_t and spec_t.version,
|
||||
build = spec_t and spec_t.build,
|
||||
path = link,
|
||||
}
|
||||
if did_change then
|
||||
dev_changed[src] = { path = link }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif is_url(src) then
|
||||
table.insert(pack_specs, to_pack_spec(spec))
|
||||
else
|
||||
vim.cmd.packadd(src)
|
||||
local runtime =
|
||||
vim.api.nvim_get_runtime_file("pack/*/opt/" .. src, false)
|
||||
---@type ow.Pack.Plugin
|
||||
local plugin = {
|
||||
src = src,
|
||||
name = src,
|
||||
path = runtime[1] or "",
|
||||
}
|
||||
M.plugins[plugin.src] = plugin
|
||||
local ok_add, add_err = pcall(vim.cmd.packadd, src)
|
||||
if not ok_add then
|
||||
log.error("pack: failed to packadd %s: %s", src, add_err)
|
||||
else
|
||||
local runtime =
|
||||
vim.api.nvim_get_runtime_file("pack/*/opt/" .. src, false)
|
||||
---@type ow.Pack.Plugin
|
||||
local plugin = {
|
||||
src = src,
|
||||
name = src,
|
||||
path = runtime[1] or "",
|
||||
}
|
||||
M.plugins[plugin.src] = plugin
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -303,6 +411,9 @@ function M.setup(specs)
|
||||
M.plugins[plugin.src] = plugin
|
||||
vim.cmd.packadd(plugin.name)
|
||||
end)
|
||||
for src, data in pairs(dev_changed) do
|
||||
changed[src] = data
|
||||
end
|
||||
|
||||
for _, src in ipairs(order) do
|
||||
local plugin = M.plugins[src]
|
||||
@@ -343,6 +454,26 @@ end
|
||||
---@param names? string[]
|
||||
---@param opts? table
|
||||
function M.update(names, opts)
|
||||
if names then
|
||||
local managed = {}
|
||||
for _, plugin in pairs(M.plugins) do
|
||||
if not file_url_to_path(plugin.src) and is_url(plugin.src) then
|
||||
managed[plugin.name] = true
|
||||
end
|
||||
end
|
||||
local filtered = {}
|
||||
for _, name in ipairs(names) do
|
||||
if managed[name] then
|
||||
table.insert(filtered, name)
|
||||
else
|
||||
log.warning("pack: skipping %s (not managed by vim.pack)", name)
|
||||
end
|
||||
end
|
||||
if #filtered == 0 then
|
||||
return
|
||||
end
|
||||
names = filtered
|
||||
end
|
||||
vim.pack.update(names, opts)
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user