local logger = require('dep.log') local git = require('dep.git') local fs = require('dep.fs') local packager = require('dep.package') local h = require('dep.helpers') -- all functions for convenience local M = {} -- TODO: actually use this (ideally make a view that shows startuptime and -- which plugins are currently loaded) -- performance logging local perf = {} -- 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) --- get execution time of a function ---@param name string name of performance output ---@param code function function to run ---@vararg any arguments for code function M.benchmark(name, code, ...) local start = os.clock() code(...) perf[name] = os.clock() - start end --- recurse over all packages and register them ---@param speclist spec[] table of specs ---@param overrides spec? a package spec that is used to override options function M.registertree(speclist, overrides) overrides = overrides or {} -- recurse the packages local over = overrides for _, spec in pairs(speclist) do -- make sure the overrides override and take into account the packages spec ---@diagnostic disable-next-line: missing-fields over = { pin = over.pin or spec.pin, disable = over.disable or spec.disable } -- While a package can fail to load we just don't care, it will work itself -- out. The goal is to make sure every plugin that can load does load, not -- keep working plugins from loading because an unrelated one doesn't load. packager:new(spec, overrides) end end --- clean out old packages function M.clean() h.uv.fs_scandir( packager.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 then queue[name] = packager.get_base_dir().."/"..name else break end end -- keep packages that still exist for _, package in pairs(packager.get_packages()) do queue[package.name] = nil end 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 --- reload all packages in package table spec ---@param force boolean? force all packages to load function M.reload(force) local reloaded = packager.get_root():loadtree(force) if reloaded then local ok, err M.benchmark("reload", function() ok, err = pcall(vim.cmd, [[ silent! helptags ALL silent! UpdateRemotePlugins ]]) end) if ok then logger:log("vim", "reloaded helptags and remote plugins") else logger:log("error", "failed to reload helptags and remote plugins; reason: %s", err) end end end --- sync a tree of plugins ---@param tree package[] tree of plugins ---@param cb function? callback function M.synctree(tree, cb) local progress = 0 local has_errors = false local function done(err) progress = progress + 1 has_errors = has_errors or err if progress == #tree then if has_errors then logger:log("error", "there were errors during sync; see :messages or :DepLog for more information") else logger:log("update", "synchronized %s %s", #tree, #tree == 1 and "package" or "packages") end M.clean() M.reload() if cb then cb() end 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) end end -- basically the main function of our program return function(opts) logger.pipe = logger:setup() --- make comparison for table.sort ---@param a package package spec a ---@param b package package spec b ---@return boolean local function comp(a, b) -- NOTE: this doesn't have to be in any real order, it just has to be -- consistant, thus we can just check if the unicode value of one package -- id is less than the other return a.id < b.id end local initialized, err = pcall(function() packager.set_base_dir(opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/") M.benchmark("load", function() -- register all packages local root = packager:new({ "squibid/dep", url = "https://git.squi.bid/dep", pin = true }) if not root then logger:log("error", "couldn't register root package") return end M.registertree(opts) -- sort package dependencies for _, package in pairs(packager.get_packages()) do table.sort(package.requirements, comp) table.sort(package.dependents, comp) end -- make sure there arent any circular dependencies local ok = packager.findcycle(packager.get_packages()) if type(ok) == "table" then logger:log("error", "found a cycle in the package spec here: %s", vim.inspect(ok)) end end) -- load packages M.reload() --- check if a package should be synced ---@param package table package table spec ---@return boolean sync local function shouldsync(package) if opts.sync == "new" or opts.sync == nil then return not package.exists else return opts.sync == "always" end end -- get all package that need syncing local targets = {} for _, package in pairs(packager.get_packages()) do if shouldsync(package) then table.insert(targets, package) end end -- install all targets M.synctree(targets) end) if not initialized then logger:log("error", err) end -- add some user commands vim.api.nvim_create_user_command("DepLog", function() vim.cmd('vsp '..logger.path) vim.opt_local.readonly = true -- make the log auto update while it's open local w = h.uv.new_fs_event() local function watch_file(fname) local fullpath = vim.api.nvim_call_function( 'fnamemodify', { fname, ':p' }) w:start(fullpath, {}, vim.schedule_wrap(function(...) vim.cmd('checktime') w:stop() watch_file(fname) end)) end watch_file(logger.path) end, {}) vim.api.nvim_create_user_command("DepSync", function() M.synctree(packager.get_packages()) end, {}) vim.api.nvim_create_user_command("DepReload", function() M.reload() end, {}) vim.api.nvim_create_user_command("DepClean", function() -- clean AND reload to make sure that all old packages are gone M.clean() M.reload() end, {}) logger:cleanup() end