switching from handling plugin downloading on our own to use vim.pack

This commit is contained in:
Squibid 2026-01-01 14:34:49 -05:00
parent 70853bd01e
commit 9b19b61372
Signed by: squibid
GPG key ID: BECE5684D3C4005D
14 changed files with 26 additions and 802 deletions

View file

@ -1,19 +1,12 @@
local logger = require("dep.log")
local git = require("dep.git")
local fs = require("dep.fs")
local packager = require("dep.package")
local modules = require("dep.modules")
local bench = require("dep.bench")
local lazy = require("dep.lazy")
local ui = require("dep.ui")
-- all functions for convenience
local M = {}
-- TODO: maybe add the ability to get a lockfile? it's useful to make a config
-- rebuildable, but idk if it's actually useful for a neovim config
-- (look into how ofter people who use lazy.nvim us it)
--- sync a tree of plugins
---@param tree package[] tree of plugins
---@param cb function? callback
@ -32,9 +25,8 @@ local function synctree(tree, cb)
logger:log("update", "synchronized %s %s", #tree, #tree == 1 and "package" or "packages")
end
fs:clean(packager)
for _, package in pairs(tree) do
package:reload()
for _, p in pairs(tree) do
p:reload()
end
if cb then
@ -43,18 +35,20 @@ local function synctree(tree, cb)
end
end
for _, package in pairs(tree) do
local co = coroutine.create(function()
-- if the package provided prefers a local source then use the local
-- source instead of the git repository
if package.path then
fs:sync(package, done)
else
git.sync(package, done)
end
end)
coroutine.resume(co)
-- convert our spec to vim.pack.Spec
local vimspecs = {}
for _, p in ipairs(tree) do
table.insert(vimspecs, {
name = p.name,
src = p.path or p.url,
version = p.commit or p.branch
})
end
vim.pack.add(vimspecs, {
load = done,
confirm = false,
})
end
--- check if a package should be synced
@ -100,7 +94,7 @@ return function(opts)
local root = packager:new({
"squibid/dep",
url = "https://git.squi.bid/squibid/dep.git",
branch = "lazy"
branch = "pack"
})
if not root then
logger:log("error", "couldn't register root package")
@ -157,21 +151,4 @@ return function(opts)
package:reload()
end
end, {})
vim.api.nvim_create_user_command("DepClean", function()
-- clean AND reload to make sure that all old packages are gone
fs:clean(packager)
end, {})
vim.api.nvim_create_user_command("DepUi", function()
ui.open(packager)
ui.set_page("P")
end, {})
vim.api.nvim_create_user_command("DepLog", function()
ui.open(packager)
ui.set_page("L")
end, {})
logger:cleanup()
end

View file

@ -1,78 +0,0 @@
local h = require('dep.helpers')
local logger = require('dep.log')
local fs = {}
--- abstract away fs:link to make calling more intuitive
---@param package package package to update
---@param cb function callback on success
function fs:sync(package, cb)
if not package.exists then
fs:link(package, cb)
end
end
--- create a symlink to a local package
---@param package package package to link
---@param cb function callback on success
function fs:link(package, cb)
h.uv.fs_symlink(package.path, package.dir, nil, function(err, _)
if err then
logger:log("error", "failed to symlink %s; reason: %s", package.id, err)
else
cb(err)
end
end)
end
--- clean out old packages
---@param package package any package
function fs:clean(package)
h.uv.fs_scandir(
package.get_base_dir(),
vim.schedule_wrap(function(err, handle)
if err then
logger:log("error", "failed to clean; reason: %s", err)
else
local queue = {}
while handle do
local name = h.uv.fs_scandir_next(handle)
if name and name ~= package.get_root().name then
queue[name] = package.get_base_dir().."/"..name
elseif name == package.get_root().name then
-- we need to ensure that there is no chance of nuking dep
goto continue
elseif name == nil then
break
else
-- if there's a single error bail out
logger:log("error", "failed to run clean uv.fs_scandir_next failed")
return
end
::continue::
end
-- keep packages that still exist
for _, pkg in pairs(package.get_packages()) do
queue[pkg.name] = nil
end
-- start deleting all of the packages which are chosen for deletion
for name, dir in pairs(queue) do
local co = coroutine.create(function()
local ok = vim.fn.delete(dir, "rf")
if ok then
logger:log("clean", "deleted %s", name)
else
logger:log("error", "failed to delete %s", name)
end
end)
coroutine.resume(co)
end
end
end)
)
end
return fs

View file

@ -1,161 +0,0 @@
-- TODO: clean this up, it's a mess
-- the nesting of all the proc calls is really annoying, and I need to find a
-- cleaner way to do it
local logger = require('dep.log')
local proc = require('dep.proc')
local git = {}
--- install or update a given package
---@param package package package to update/install
---@param cb function callback
function git.sync(package, cb)
local function sync()
-- update or install
if package.exists then
git.update(package, cb)
else
git.install(package, cb)
end
end
-- handle arbitrary branches here
if package.branch then
proc.git_resolve_branch(package.url, package.branch, function(err, message)
if not err then
package.branch = message
sync()
end
end)
else
sync()
end
end
--- configure a package
---@param package table package spec
local function configurepkg(package)
package:runhooks("on_config")
logger:log("config", "package: %s configured", package.id)
package.configured = true
end
--- install a given package
---@param package package package to install
---@param cb function callback
function git.install(package, cb)
if not package.enabled then
cb()
return
end
proc.git_clone(package.dir, package.url, package.branch, function(err, message)
if err then
logger:log("error", "failed to install %s; reason: %s",
package.id, message)
else
if package.commit then
proc.git_checkout(package.dir, package.branch, package.commit, function(err, message)
if err then
logger:log("error", "failed to checkout %s; reason: %s", package.id, message)
else
package.exists = true
package:unconfiguretree()
logger:log("install", "installed %s", package.id)
configurepkg(package)
end
end)
else
package.exists = true
package:unconfiguretree()
logger:log("install", "installed %s", package.id)
configurepkg(package)
end
end
cb(err)
end)
end
--- update a package
---@param package package package to update
---@param cb function callback
function git.update(package, cb)
if not package.enabled then
cb()
return
end
local function log_err(err)
logger:log("error", "failed to update %s; reason: %s", package.id, err)
end
if package.pin then
cb()
return
end
proc.git_rev_parse(package.dir, "HEAD", function(err, before)
if err then
log_err(before)
cb(err)
else
if package.commit then
proc.git_checkout(package.dir, package.branch, package.commit, function(err, message)
if err then
log_err(message)
cb(err)
else
proc.git_rev_parse(package.dir, package.commit, function(err, after)
if err then
log_err(after)
cb(err)
elseif before == after then
logger:log("skip", "skipped %s", package.id)
cb(err)
else
package:unconfiguretree()
logger:log("update", "updated %s; %s -> %s", package.id, before, after)
configurepkg(package)
end
end)
end
end)
else
proc.git_fetch(package.dir, "origin", package.branch or "HEAD", function(err, message)
if err then
log_err(message)
cb(err)
else
proc.git_rev_parse(package.dir, "FETCH_HEAD^{commit}", function(err, after)
if err then
log_err(after)
cb(err)
elseif before == after then
logger:log("skip", "skipped %s", package.id)
cb(err)
else
proc.git_reset(package.dir, after, function(err, message)
if err then
log_err(message)
else
package:unconfiguretree()
logger:log("update", "updated %s; %s -> %s", package.id, before, after)
configurepkg(package)
end
cb(err)
end)
end
end)
end
end)
end
end
end)
end
return git

View file

@ -1,4 +0,0 @@
return {
-- vim.loop was depricated in nvim 0.10
uv = vim.uv or vim.loop
}

View file

@ -1,4 +1,3 @@
local h = require("dep.helpers")
local packager = require("dep.package")
---@class lazy
@ -16,7 +15,7 @@ local function colorscheme()
if not p.loaded then
for _, ext in ipairs({ ".lua", ".vim" }) do
local path = p.dir.."/colors/"..e.match..ext
if h.uv.fs_stat(path) then
if vim.uv.fs_stat(path) then
p:ensureadded(true)
-- break out of here, we've loaded the colorscheme
return

View file

@ -1,5 +1,3 @@
local h = require('dep.helpers')
local logger = {}
logger.stage_colors = {
@ -16,8 +14,8 @@ logger.stage_colors = {
local function default_log_path()
-- create cache directory and chmod it if it doesn't already exist
local path = vim.fn.stdpath("cache")
if not h.uv.fs_stat(path) then
h.uv.fs_mkdir(path, 0x1ff) -- 0777
if not vim.uv.fs_stat(path) then
vim.uv.fs_mkdir(path, 0x1ff) -- 0777
end
return vim.fs.normalize(path).."/dep.log"
@ -39,8 +37,8 @@ function logger:setup(path)
logger.path = path or default_log_path()
local pipe
logger.handle = assert(h.uv.fs_open(logger.path, "w", 0x1a4)) -- 0644
pipe = h.uv.new_pipe()
logger.handle = assert(vim.uv.fs_open(logger.path, "w", 0x1a4)) -- 0644
pipe = vim.uv.new_pipe() --[[@as uv.uv_pipe_t]]
pipe:open(logger.handle)
return pipe
@ -94,7 +92,7 @@ function logger:cleanup(pipe, handle)
pipe = nil
end
if handle then
h.uv.fs_close(logger.handle)
vim.uv.fs_close(logger.handle)
handle = nil
end
end

View file

@ -1,4 +1,3 @@
local h = require('dep.helpers')
local logger = require('dep.log')
local module = require("dep.modules.module")
@ -30,9 +29,9 @@ function modules:setup(speclist, overrides, config_path)
"lua", (speclist.modules.prefix:gsub("%.", "/"))
)
local handle = h.uv.fs_scandir(path)
local handle = vim.uv.fs_scandir(path)
while handle do
local name = h.uv.fs_scandir_next(handle)
local name = vim.uv.fs_scandir_next(handle)
if name then
-- skip non-lua files
if name:sub(#name - 3) ~= ".lua" then

View file

@ -105,7 +105,7 @@ function package:new(spec, overrides)
o.name = spec.as or o.name or spec_man.get_name(spec)
o.url = spec.url or o.url or ("https://github.com/"..id..".git")
o.path = spec.path and vim.fs.normalize(spec.path) or spec.path
o.path = spec.path and "file://"..vim.fs.normalize(spec.path) or spec.path
o.branch = spec.branch or o.branch
o.dir = base_dir.."/"..o.name
o.commit = spec.commit

View file

@ -1,129 +0,0 @@
local proc = {}
--- execute a process
---@param process string the program
---@param args string[] the args
---@param cwd string? the pwd
---@param env table env
---@param cb function callback
function proc.exec(process, args, cwd, env, cb)
local buffer = {}
local function cb_output(_, data, _)
table.insert(buffer, table.concat(data))
end
local function cb_exit(_, exit_code, _)
local output = table.concat(buffer)
cb(exit_code ~= 0, output)
end
table.insert(args, 1, process)
vim.fn.jobstart(args, {
cwd = cwd,
env = env,
stdin = nil,
on_exit = cb_exit,
on_stdout = cb_output,
on_stderr = cb_output,
})
end
local git_env = { GIT_TERMINAL_PROMPT = 0 }
function proc.git_rev_parse(dir, arg, cb)
local args = { "rev-parse", "--short", arg }
proc.exec("git", args, dir, git_env, cb)
end
function proc.git_clone(dir, url, branch, cb)
local args = { "clone", "--depth=1", "--recurse-submodules", "--shallow-submodules", url, dir }
if branch then
args[#args + 1] = "--branch="..branch
end
proc.exec("git", args, nil, git_env, cb)
end
function proc.git_fetch(dir, remote, refspec, cb)
local args = { "fetch", "--depth=1", "--recurse-submodules", remote, refspec }
proc.exec("git", args, dir, git_env, cb)
end
function proc.git_reset(dir, treeish, cb)
local args = { "reset", "--hard", "--recurse-submodules", treeish, "--" }
proc.exec("git", args, dir, git_env, cb)
end
function proc.git_checkout(dir, branch, commit, cb)
local args = { "fetch", "--depth=2147483647", "origin", branch }
proc.exec("git", args, dir, git_env, function(err, message)
cb(err, message)
args = { "checkout", commit }
proc.exec("git", args, dir, git_env, cb)
end)
end
function proc.git_resolve_branch(url, branch, cb)
-- if the branch doesn't contain a * then return the branch
if not string.match(branch, "*") then
cb(false, branch)
return
end
local buffer = {}
local function cb_output(_, data, _)
if data[1] ~= "" then
buffer = data
end
end
vim.fn.jobstart({ "git", "ls-remote", "--tags", "--sort", "v:refname", url },
{
cwd = nil,
env = git_env,
stdin = nil,
on_stdout = cb_output,
on_stderr = cb_output,
on_exit = function(_, exit_code, _)
if exit_code ~= 0 then
return
end
-- get a list of all versions
local versions = {}
for _, v in pairs(buffer) do
local s, e = string.find(v, "refs/tags/.+")
if not s or not e then
goto continue
end
local tag = string.sub(v, s, e)
tag = tag:gsub("refs/tags/", ""):gsub("%^{}", "")
table.insert(versions, tag)
::continue::
end
-- match the chosen version against all versions
for i = #versions, 1, -1 do
if branch == "*" then
cb(false, versions[i])
return
else
local r = string.match(versions[i], branch)
if r then
cb(false, r)
return
end
end
end
end
}
)
end
return proc

View file

@ -1,59 +0,0 @@
local logger = require("dep.log")
local format = {}
--- format a boolean to a chunk with highlights
---@param b boolean
---@return chunk chunk
function format.bool(b)
return { vim.inspect(b), b and "DiffAdd" or "DiffDelete" }
end
--- format a number to a chunk with highlights
---@param n number
---@return chunk chunk
function format.number(n)
return { vim.inspect(n), "Number" }
end
--- format a log line with highlights
---@param log_line string log line
---@return chunk[] chunks
function format.log_line(log_line)
-- make sure we don't do operations on nil values
if not log_line or log_line == "" then
return {}
end
-- error on any nil values, this should prevent us from parsing an incorrectly
-- formatted log line
local log_time, colon, log_path, log_path_ln, level, rest
local ok = pcall(function()
log_time = string.sub(log_line, string.find(log_line, "%[") + 1,
string.find(log_line, "%]") - 1)
colon = string.find(log_line, ":", 11)
log_path = string.sub(log_line, string.find(log_line, "%]") + 2,
colon - 1)
log_path_ln = string.sub(log_line, colon + 1,
string.find(log_line, ":", colon + 1) - 1)
level = string.sub(log_line, string.find(log_line, "%(") + 1,
string.find(log_line, "%)") - 1)
rest = string.sub(log_line, string.find(log_line, "%)") + 2)
end)
if not ok then
return {}
end
return {
{ "[", "" },
{ log_time, "Boolean" },
{ "] ", "" },
{ log_path, "String" },
{ ":", "" },
{ log_path_ln, "Number" },
{ ": ", "" },
{ rest, logger.stage_colors[level] or "" }
}
end
return format

View file

@ -1,207 +0,0 @@
local h = require("dep.helpers")
local page = require("dep.ui.page")
local logger = require("dep.log")
local format = require("dep.ui.format")
---@class ui
---@field bufnr number
---@field winnr number
---@field header_bufnr number
---@field header_winnr number
---@field pages page[]
local ui = {}
-- the active page being displayed
local active_page
-- all the pages
local pages = {}
-- the header ext mark
local header_ext_id
local function page_packages(packager)
local p = page:new("Packages", "P")
for _, pkg in pairs(packager.get_packages()) do
p:new_line({
{ pkg.id, "@conditional" },
{ " loaded: ", "" },
format.bool(pkg.loaded),
{ " lazy: ", "" },
format.bool(pkg.lazy)
})
end
return p
end
local function page_log()
local p = page:new("Log", "L")
local f = io.open(logger.path, "r")
if not f then
return
end
-- put the cursor at the bottom of the page after drawing
p.post_draw = function()
if ui.winnr then
vim.api.nvim_win_set_cursor(ui.winnr, { #p.content, 0 })
end
end
-- read in the contents of the file, and keep watching for updates
local function update_contents()
repeat
local line = f:read("*l")
-- if the line isn't empty we shouldn't draw it
if line then
p:new_line(format.log_line(line))
end
until not line
end
update_contents()
local fullpath = vim.api.nvim_call_function(
"fnamemodify", { logger.path, ":p" })
h.uv.new_fs_event():start(fullpath, {}, vim.schedule_wrap(function()
update_contents()
-- if the log is currently being displayed then make sure to draw it when
-- it updates
if active_page == p then
p:draw(ui.bufnr)
end
end))
return p
end
--- set the current page
---@param p string|page page to set
function ui.set_page(p)
if type(p) == "string" then
for _, v in ipairs(pages) do
if p == v.kb then
v:draw(ui.bufnr)
active_page = v
break
end
end
elseif type(p) == "table" then
p:draw(ui.bufnr)
active_page = p
end
-- color the header text
local txt = vim.api.nvim_buf_get_text(ui.header_bufnr, 0, 0, -1, -1, {})[1]
local start_range = (string.find(txt, active_page.name)) - 2
local end_range = #active_page.name + start_range + 2
if header_ext_id then
vim.api.nvim_buf_del_extmark(ui.header_bufnr, active_page.hlns, header_ext_id)
end
header_ext_id = vim.api.nvim_buf_set_extmark(ui.header_bufnr,
active_page.hlns, 0, start_range, {
hl_mode = "replace",
hl_group = "CurSearch",
end_col = end_range,
})
end
local setup
--- setup all the pages
---@param packager package the packager
local function setup_pages(packager)
if setup then
return
end
local header_text = ""
table.insert(pages, page_packages(packager))
table.insert(pages, page_log())
for _, v in ipairs(pages) do
header_text = header_text.." "..v.name.." "
vim.keymap.set("n", v.kb, function()
ui.set_page(v)
end, { buffer = ui.bufnr })
end
-- set the header text
vim.api.nvim_buf_set_lines(ui.header_bufnr, 0, -1, false, { header_text })
-- add keymaps
vim.keymap.set("n", "q", function()
vim.api.nvim_win_close(ui.winnr, false)
ui.winnr = nil
end, { buffer = ui.bufnr })
setup = true
end
--- setup the ui
---@param packager package
function ui.open(packager)
if not ui.bufnr then
ui.bufnr = vim.api.nvim_create_buf(false, true)
end
if not ui.header_bufnr then
ui.header_bufnr = vim.api.nvim_create_buf(false, true)
end
local header_height = 1
local width = math.floor(vim.o.columns * 0.8)
local height = math.floor(vim.o.lines * 0.8) - header_height
if not ui.winnr then
ui.winnr = vim.api.nvim_open_win(ui.bufnr, true, {
relative = "editor",
row = (vim.o.lines - height) / 2,
col = (vim.o.columns - width) / 2,
width = width,
height = height,
border = "solid",
zindex = 998,
})
end
if not ui.header_winnr then
ui.header_winnr = vim.api.nvim_open_win(ui.header_bufnr, false, {
relative = "editor",
row = ((vim.o.lines - height) / 2) - (header_height * 2),
col = (vim.o.columns - width) / 2,
width = width,
height = header_height,
border = "solid",
zindex = 999,
focusable = false
})
end
vim.api.nvim_win_set_buf(ui.winnr, ui.bufnr)
vim.api.nvim_win_set_buf(ui.header_winnr, ui.header_bufnr)
-- make sure the header closes when the body does and vice versa
local function cb()
vim.api.nvim_win_close(ui.header_winnr, false)
ui.header_winnr = nil
vim.api.nvim_win_close(ui.winnr, false)
ui.winnr = nil
end
vim.api.nvim_create_autocmd("WinClosed", {
pattern = ui.winnr.."",
callback = cb
})
vim.api.nvim_create_autocmd("WinClosed", {
pattern = ui.header_winnr.."",
callback = cb
})
setup_pages(packager)
end
return ui

View file

@ -1,84 +0,0 @@
---@class chunk: table
---@field [1] string text to be displayed
---@field [2] string neovim highlight group to use
---@class page
---@field name string name of the ui page
---@field kb string keybind of the page
---@field content chunk[]|chunk[][] all the chunks
---@field hlns number highlight namespace
---@field pre_draw function things to do prior to drawing to the buffer
---@field post_draw function things to do post drawing to the buffer
local page = {}
--- create a new page
---@param name string the name of the page
---@param kb string keybind to change to the page
---@return page page
function page:new(name, kb)
local o = {}
self.__index = self
setmetatable(o, self)
o.hlns = vim.api.nvim_create_namespace("DepUi")
o.name = name
o.kb = kb
o.content = {}
return o
end
--- add a new line to the page
---@param line chunk|chunk[] new line
function page:new_line(line)
table.insert(self.content, line)
end
--- draw the page to the given buffer
---@param bufnr number buffer number
function page:draw(bufnr)
-- try to run pre_draw steps
if self.pre_draw then
self.pre_draw()
end
-- ready all information for rendering
for i, chunk in ipairs(self.content) do
local linenr = i - 1
local text = ""
local hls = {}
if type(chunk[1]) == "table" then
local j = 0
for _, ch in ipairs(chunk) do
text = text..ch[1]
table.insert(hls, { ch[2], j, j + #ch[1] })
j = j + #ch[1]
end
elseif type(chunk[1]) == "string" then
text = chunk[1]
table.insert(hls, { chunk[2], 0, #text })
end
-- draw the text to the buffer
vim.api.nvim_buf_set_lines(bufnr, linenr, -1, false, { text })
-- highlight the buffer
for _, hl in ipairs(hls) do
vim.api.nvim_buf_set_extmark(bufnr, self.hlns, linenr, hl[2], {
hl_mode = "replace",
hl_group = hl[1],
end_col = hl[3],
end_row = linenr
})
end
end
-- try to run post_draw steps
if self.post_draw then
self.post_draw()
end
end
return page