From 1d0b486e088275b12b70232c9a061a6ffcab9c83 Mon Sep 17 00:00:00 2001 From: Squibid Date: Mon, 21 Apr 2025 15:32:41 -0500 Subject: [PATCH 01/74] Dep now supports lazy loading --- README.md | 10 +- lua/dep.lua | 1014 ++++++++++++++++++++++--------------------- lua/dep/log.lua | 165 ++++--- lua/dep/package.lua | 273 ------------ lua/dep/proc.lua | 78 +++- lua/dep2.lua | 12 - lua/lazy/utils.lua | 97 +++++ 7 files changed, 768 insertions(+), 881 deletions(-) delete mode 100644 lua/dep/package.lua delete mode 100644 lua/dep2.lua create mode 100644 lua/lazy/utils.lua diff --git a/README.md b/README.md index 808821e..27937bc 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ A package must be declared in the following format. "user/package", -- [function] Code to run after the package is loaded into neovim. - function() + load = function() require "package".setup(...) end, @@ -83,6 +83,10 @@ A package must be declared in the following format. os.execute(...) end, + -- [function] Code used to determine when the package should be loaded. + lazy = function(load) + end, + -- [string] Overrides the short name of the package. -- Defaults to a substring of the full name after '/'. as = "custom_package", @@ -95,6 +99,10 @@ A package must be declared in the following format. -- Defaults to whatever the remote configured as their HEAD, which is usually "master". branch = "develop", + -- [string] Overrides the commit ref to target + -- Defaults to the latest commit on the current branch + commit = "e76cb03", + -- [boolean] Prevents the package from being loaded. disable = true, diff --git a/lua/dep.lua b/lua/dep.lua index dccd17b..58dcc90 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -1,34 +1,57 @@ --- --- Copyright (c) 2022 chiya.dev --- --- Use of this source code is governed by the MIT License --- which can be found in the LICENSE file and at: --- --- https://chiya.dev/licenses/mit.txt --- +local logger = require('dep.log') +local proc = require('dep.proc') -local logger = require("dep.log").global -local proc = require("dep.proc") +---all functions for convenience +---@type table +local M = {} -local initialized, perf, config_path, base_dir -local packages, root +---@type boolean +local initialized -local function bench(name, code, ...) +---root package +---@type table +local root + +---performance logging +---@type table +local perf = {} + +---table of every package where their id is the index +---@type table +local packages = {} + +---path to root of where plugins are downloaded +---@type string +local base_dir + +--- 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 -local function get_name(id) +--- get name of package +---@param id string id of the package +---@return string id +---@nodiscard +function M.getpkgname(id) local name = id:match("^[%w-_.]+/([%w-_.]+)$") if name then return name else - error(string.format('invalid name "%s"; must be in the format "user/package"', id)) + error(string.format( + 'invalid name "%s"; must be in the format "user/package"', id)) end end -local function link_dependency(parent, child) +--- tell the parent it has a child and the child it has a parent +---@param parent table parent package +---@param child table child package +function M.link_dependency(parent, child) if not parent.dependents[child.id] then parent.dependents[child.id] = child parent.dependents[#parent.dependents + 1] = child @@ -40,7 +63,12 @@ local function link_dependency(parent, child) end end -local function register(spec, overrides) +--- register a new package according to the spec +---@param spec table|string the package spec to register +---@param overrides table|nil a package spec that is used to override options +---@return table package a table containing the package spec +---@nodiscard +function M.registerpkg(spec, overrides) overrides = overrides or {} if type(spec) ~= "table" then @@ -50,133 +78,290 @@ local function register(spec, overrides) local id = spec[1] local package = packages[id] + -- if package hasn't been registered already, get the inital spec regisitered if not package then package = { - id = id, - enabled = true, - exists = false, - added = false, - configured = false, - loaded = false, - subtree_configured = false, + id = id, -- id of the package + enabled = true, -- whether it's going to be used + exists = false, -- if the package exists on the filesystem + lazy = false, -- if the package is lazy loaded in any way + added = false, -- if the package has been added in vim + configured = false, -- if the package has been configured + loaded = false, -- if a package has been loaded subtree_loaded = false, - on_setup = {}, - on_config = {}, - on_load = {}, - dependencies = {}, -- inward edges - dependents = {}, -- outward edges - perf = {}, + on_config = {}, -- table of functions to run on config + on_setup = {}, -- table of function to run on setup + on_load = {}, -- table of functions to run on load + lazy_load = {}, -- table of functions to run which will tell the package + -- when to load + dependencies = {}, -- this package's requirements + dependents = {}, -- packages that require this package + perf = {} } packages[id] = package - packages[#packages + 1] = package end - local prev_dir = package.dir -- optimization - - package.name = spec.as or package.name or get_name(id) - package.url = spec.url or package.url or ("https://github.com/" .. id .. ".git") + -- register the rest of the package spec + package.name = spec.as or package.name or M.getpkgname(id) + package.url = spec.url or package.url or ("https://github.com/"..id..".git") package.branch = spec.branch or package.branch - package.dir = base_dir .. package.name + package.dir = base_dir..package.name + package.commit = spec.commit package.pin = overrides.pin or spec.pin or package.pin package.enabled = not overrides.disable and not spec.disable and package.enabled + package.lazy = spec.lazy or package.lazy - if prev_dir ~= package.dir then - package.exists = vim.fn.isdirectory(package.dir) ~= 0 - package.configured = package.exists - end + -- make sure that the package exists + package.exists = vim.fn.isdirectory(package.dir) ~= 0 + package.configured = package.exists - package.on_setup[#package.on_setup + 1] = spec.setup - package.on_config[#package.on_config + 1] = spec.config - package.on_load[#package.on_load + 1] = spec[2] + -- register all the callback functions + table.insert(package.on_config, spec.config) + table.insert(package.on_load, spec.load) + table.insert(package.lazy_load, spec.lazy) - -- every package is implicitly dependent on us, the package manager + -- if the current package isn't the root package then it depends on the root + -- package if root and package ~= root then - link_dependency(root, package) + M.link_dependency(root, package) end - if type(spec.requires) == "table" then - for i = 1, #spec.requires do - link_dependency(register(spec.requires[i]), package) + -- link the dependencies + if spec.requires then + if type(spec.requires) == "string" then + spec.requires = { spec.requires } + end + for _, v in pairs(spec.requires) do + M.link_dependency(M.registerpkg(v), package) end - elseif spec.requires then - link_dependency(register(spec.requires), package) end - if type(spec.deps) == "table" then - for i = 1, #spec.deps do - link_dependency(package, register(spec.deps[i])) + -- and link the dependents + if spec.deps then + if type(spec.deps) == "string" then + spec.deps = { spec.deps } + end + for _, v in pairs(spec.deps) do + local p = M.registerpkg(v) + M.link_dependency(package, p) + + -- if the child package is lazy loaded make sure the child package + -- is only loaded when the parent package has finished loading + if package.lazy then + table.insert(package.on_load, function() + M.loadtree(package, true) + end) + table.insert(p.lazy_load, function(_) end) + end end - elseif spec.deps then - link_dependency(package, register(spec.deps)) end return package end -local function register_recursive(list, overrides) +--- recurse over all packages and register them +---@param speclist table table of specs +---@param overrides table|nil a package spec that is used to override options +function M.registertree(speclist, overrides) overrides = overrides or {} + + -- make sure the overrides override and take into account the packages spec overrides = { - pin = overrides.pin or list.pin, - disable = overrides.disable or list.disable, + pin = overrides.pin or speclist.pin, + disable = overrides.disable or speclist.disable } - for i = 1, #list do - local ok, err = pcall(register, list[i], overrides) + -- recurse the packages + for i = 1, #speclist do + local ok, err = pcall(M.registerpkg, speclist[i], overrides) + + -- if erroring print out the spec and the error if not ok then - error(string.format("%s (spec=%s)", err, vim.inspect(list[i]))) - end - end - - if list.modules then - for i = 1, #list.modules do - local name, module = "", list.modules[i] - - if type(module) == "string" then - if list.modules.prefix then - module = list.modules.prefix .. module - end - - name, module = module, require(module) - end - - name = module.name or name - - local ok, err = pcall(register_recursive, module, overrides) - if not ok then - error(string.format("%s <- %s", err, name)) - end + error(string.format("%s (spec=%s)", err, vim.inspect(speclist[i]))) end end end -local function sort_dependencies() - -- we don't do topological sort, packages are loaded by traversing the graph recursively - -- any sorting is fine as long as the order is consistent and predictable - local function compare(a, b) - local a_deps, b_deps = #a.dependencies, #b.dependencies - if a_deps == b_deps then - return a.id < b.id +--- run specified hooks on specified package +---@param package table package spec +---@param type string which hook to run +---@return boolean, string|nil +function M.runhooks(package, type) + local hooks = package[type] + if #hooks == 0 then + return true + end + + local start = os.clock() + + -- chdir into the package directory to make running external commands + -- from hooks easier. + local last_cwd = vim.fn.getcwd() + vim.fn.chdir(package.dir) + + for i = 1, #hooks do + local ok, err = pcall(hooks[i]) + if not ok then + vim.fn.chdir(last_cwd) + + package.error = true + return false, err + end + end + + vim.fn.chdir(last_cwd) + package.perf[type] = os.clock() - start + + logger:log("hook", "triggered %d %s %s for %s", #hooks, type, + #hooks == 1 and "hook" or "hooks", package.id) + + return true +end + +--- make sure a package has been loaded +---@param package table package +---@param force boolean? force lazy packages to load +---@return boolean|table return true or false if loaded or package spec if lazy loaded +function M.ensureadded(package, force) + -- print("adding ~ "..package.id) + local function loadpkg(pkg) + -- make sure to load the dependencies first + for _, p in pairs(pkg.dependencies) do + if not p.loaded then + M.ensureadded(p, true) + end + end + + -- now start loading our plugin + local start = os.clock() + + -- trigger the packadd for the plugin + local ok, err = pcall(vim.cmd, "packadd " .. pkg.name) + if not ok then + pkg.error = true + return false, err + end + + pkg.added = true + pkg.perf.pack = os.clock() - start + logger:log("vim", "packadd completed for %s", pkg.id) + + -- set the package to loaded + package.loaded = true + logger:log("load", "loaded %s", package.id) + + -- trigger the on_load hooks + ok, err = M.runhooks(package, "on_load") + if not ok then + logger:log("error", "failed to load %s; reason: %s", package.id, err) + return + end + end + + if not package.added and not package.lazy or force then + loadpkg(package) + elseif not package.added and package.lazy then + logger:log("lazy", "registering %d lazy hooks for %s", #package.lazy_load, + package.id) + for _, load_cond in pairs(package.lazy_load) do + -- configure the lazy loader for the user + local l = require('lazy.utils'):new() + if l == true then + logger:log("lazy", "failed to get lazy utils") + return false + end + l:set_load(function() + logger:log("lazy", "triggered %d lazy hooks for %s", #package.lazy_load, + package.id) + loadpkg(package) + end) + + -- run it + load_cond(l) + end + return package + end + + return true +end + +--- load all packages in package tree +---@param package table package spec table +---@param force boolean? force lazy packages to load +---@return boolean boolean if tree was successfully loaded +function M.loadtree(package, force) + if not package.exists or not package.enabled or package.error then + return false + end + + if package.subtree_loaded then + return true + end + + if not package.lazy then + for i = 1, #package.dependencies do + if not package.dependencies[i].loaded then + return false + end + end + end + + if not package.loaded then + local ok, err = M.ensureadded(package, force) + if not ok then + logger:log("error", "failed to load %s; reason: %s", package.id, err) + return false + end + end + + package.subtree_loaded = true + + for i = 1, #package.dependents do + package.subtree_loaded = M.loadtree(package.dependents[i], force) and package.subtree_loaded + end + + return package.subtree_loaded +end + +--- reload all packages in package table spec +---@param force boolean|nil force all packages to load +function M.reload(force) + -- cleanup any previous errors in the package table + for i in pairs(packages) do + packages[i].error = false + end + + local reloaded = M.loadtree(root, 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 - return a_deps < b_deps + logger:log("error", + "failed to reload helptags and remote plugins; reason: %s", err) end end - - table.sort(packages, compare) - - for i = 1, #packages do - table.sort(packages[i].dependencies, compare) - table.sort(packages[i].dependents, compare) - end end -local function find_cycle() +--- check if there's a circular dependency in the package tree +function M.findcycle() local index = 0 local indexes = {} local lowlink = {} local stack = {} - -- use tarjan algorithm to find circular dependencies (strongly connected components) + -- use tarjan algorithm to find circular dependencies (strongly connected + -- components) local function connect(package) indexes[package.id], lowlink[package.id] = index, index stack[#stack + 1], stack[package.id] = package, true @@ -207,8 +392,8 @@ local function find_cycle() cycle[#cycle + 1] = node until node == package - -- a node is by definition strongly connected to itself - -- ignore single-node components unless it explicitly specified itself as a dependency + -- a node is by definition strongly connected to itself ignore single-node + -- components unless it explicitly specified itself as a dependency if #cycle > 2 or package.dependents[package.id] then return cycle end @@ -227,320 +412,138 @@ local function find_cycle() end end -local function ensure_acyclic() - local cycle = find_cycle() - - if cycle then - local names = {} - for i = 1, #cycle do - names[i] = cycle[i].id - end - error("circular dependency detected in package dependency graph: " .. table.concat(names, " -> ")) - end -end - -local function run_hooks(package, type) - local hooks = package[type] - if #hooks == 0 then - return true - end - - local start = os.clock() - - -- chdir into the package directory to make running external commands - -- from hooks easier. - local last_cwd = vim.fn.getcwd() - vim.fn.chdir(package.dir) - - for i = 1, #hooks do - local ok, err = pcall(hooks[i]) - if not ok then - vim.fn.chdir(last_cwd) - - package.error = true - return false, err - end - end - - vim.fn.chdir(last_cwd) - package.perf[type] = os.clock() - start - - logger:log( - "hook", - string.format("triggered %d %s %s for %s", #hooks, type, #hooks == 1 and "hook" or "hooks", package.id) - ) - - return true -end - -local function ensure_added(package) - if not package.added then - local ok, err = run_hooks(package, "on_setup") - if not ok then - package.error = true - return false, err - end - - local start = os.clock() - - ok, err = pcall(vim.cmd, "packadd " .. package.name) - if not ok then - package.error = true - return false, err - end - - package.added = true - package.perf.pack = os.clock() - start - - logger:log("vim", string.format("packadd completed for %s", package.id)) - end - - return true -end - -local function configure_recursive(package) - if not package.exists or not package.enabled or package.error then - return - end - - if package.subtree_configured then - return true - end - +--- unconfigure a packages tree +---@param package table package to unconfigure +function M.unconfiguretree(package) + -- unconfigure dependencies for i = 1, #package.dependencies do - if not package.dependencies[i].configured then - return - end + package.dependencies[i].subtree_loaded = false end - if not package.configured then - local ok, err = ensure_added(package) - if not ok then - logger:log("error", string.format("failed to configure %s; reason: %s", package.id, err)) - return - end - - ok, err = run_hooks(package, "on_config") - if not ok then - logger:log("error", string.format("failed to configure %s; reason: %s", package.id, err)) - return - end - - package.configured = true - logger:log("config", string.format("configured %s", package.id)) - end - - package.subtree_configured = true - + -- unconfigure dependents for i = 1, #package.dependents do - package.subtree_configured = configure_recursive(package.dependents[i]) and package.subtree_configured - end + package.dependents[i].loaded = false + package.dependents[i].added = false + package.dependents[i].configured = false - return package.subtree_configured -end - -local function load_recursive(package) - if not package.exists or not package.enabled or package.error then - return - end - - if package.subtree_loaded then - return true - end - - for i = 1, #package.dependencies do - if not package.dependencies[i].loaded then - return - end - end - - if not package.loaded then - local ok, err = ensure_added(package) - if not ok then - logger:log("error", string.format("failed to configure %s; reason: %s", package.id, err)) - return - end - - ok, err = run_hooks(package, "on_load") - if not ok then - logger:log("error", string.format("failed to load %s; reason: %s", package.id, err)) - return - end - - package.loaded = true - logger:log("load", string.format("loaded %s", package.id)) - end - - package.subtree_loaded = true - - for i = 1, #package.dependents do - package.subtree_loaded = load_recursive(package.dependents[i]) and package.subtree_loaded - end - - return package.subtree_loaded -end - -local function reload_meta() - local ok, err - bench("meta", 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", string.format("failed to reload helptags and remote plugins; reason: %s", err)) + package.dependents[i].subtree_loaded = false end end -local function reload() - -- clear errors to retry - for i = 1, #packages do - packages[i].error = false - end - - local reloaded - reloaded = configure_recursive(root) or reloaded - reloaded = load_recursive(root) or reloaded - - if reloaded then - reload_meta() - end - - return reloaded -end - -local function reload_all() - for i = 1, #packages do - local package = packages[i] - package.loaded, package.subtree_loaded = false, false - end - - reload() -end - -local function clean() - vim.loop.fs_scandir( - base_dir, - vim.schedule_wrap(function(err, handle) - if err then - logger:log("error", string.format("failed to clean; reason: %s", err)) - else - local queue = {} - - while handle do - local name = vim.loop.fs_scandir_next(handle) - if name then - queue[name] = base_dir .. name - else - break - end - end - - for i = 1, #packages do - queue[packages[i].name] = nil - end - - for name, dir in pairs(queue) do - -- todo: make this async - local ok = vim.fn.delete(dir, "rf") - if ok then - logger:log("clean", string.format("deleted %s", name)) - else - logger:log("error", string.format("failed to delete %s", name)) - end - end - end - end) - ) -end - -local function mark_reconfigure(package) - local function mark_dependencies(node) - node.subtree_configured, node.subtree_loaded = false, false - - for i = 1, #node.dependencies do - mark_dependencies(node.dependencies[i]) - end - end - - local function mark_dependents(node) - node.configured, node.loaded, node.added = false, false, false - node.subtree_configured, node.subtree_loaded = false, false - - for i = 1, #node.dependents do - mark_dependents(node.dependents[i]) - end - end - - mark_dependencies(package) - mark_dependents(package) -end - -local function sync(package, cb) +--- update/download a package +---@param package table package spec +---@param cb function callback +function M.sync(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 + + --- configure a package + ---@param pkg table package spec + local function configurepkg(pkg) + M.runhooks(package, "on_config") + + logger:log("config", "package: %s configured", pkg.id) + package.configured = true + end + if package.exists then if package.pin then cb() return end - local function log_err(err) - logger:log("error", string.format("failed to update %s; reason: %s", package.id, err)) + local function logerr(err) + logger:log("error", "failed to update %s; reason: %s", package.id, err) end proc.git_rev_parse(package.dir, "HEAD", function(err, before) if err then - log_err(before) + logerr(before) cb(err) 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", function(err, after) - if err then - log_err(after) - cb(err) - elseif before == after then - logger:log("skip", string.format("skipped %s", package.id)) - cb(err) - else - proc.git_reset(package.dir, after, function(err, message) - if err then - log_err(message) - else - mark_reconfigure(package) - logger:log("update", string.format("updated %s; %s -> %s", package.id, before, after)) - end - + 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) - end) - end - end) - end - end) + elseif before == after then + logger:log("skip", "skipped %s", package.id) + cb(err) + else + M.unconfiguretree(package) + configurepkg(package) + logger:log("update", "updated %s; %s -> %s", package.id, before, after) + 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", 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 + M.unconfiguretree(package) + configurepkg(package) + logger:log("update", "updated %s; %s -> %s", package.id, before, after) + end + + cb(err) + end) + end + end) + end + end) + end end end) else + logger:log("error", "%s: doesn't exist", package.id) proc.git_clone(package.dir, package.url, package.branch, function(err, message) if err then - logger:log("error", string.format("failed to install %s; reason: %s", package.id, message)) + logger:log("error", "failed to install %s; reason: %s", + package.id, message) else - package.exists = true - mark_reconfigure(package) - logger:log("install", string.format("installed %s", package.id)) + 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 + M.unconfiguretree(package) + configurepkg(package) + logger:log("install", "installed %s", package.id) + end + end) + else + package.exists = true + M.unconfiguretree(package) + configurepkg(package) + logger:log("install", "installed %s", package.id) + end end cb(err) @@ -548,7 +551,10 @@ local function sync(package, cb) end end -local function sync_list(list, on_complete) +--- sync a tree of plugins +---@param tree table tree of plugins +---@param cb function? callback +function M.synctree(tree, cb) local progress = 0 local has_errors = false @@ -556,51 +562,53 @@ local function sync_list(list, on_complete) progress = progress + 1 has_errors = has_errors or err - if progress == #list then - clean() - reload() + if progress == #tree then + -- TODO: implement clean + -- clean() + M.reload() if has_errors then logger:log("error", "there were errors during sync; see :messages or :DepLog for more information") else - logger:log("update", string.format("synchronized %s %s", #list, #list == 1 and "package" or "packages")) + logger:log("update", "synchronized %s %s", #tree, #tree == 1 and "package" or "packages") end - if on_complete then - on_complete() + if cb then + cb() end end end - for i = 1, #list do - sync(list[i], done) + for i in pairs(tree) do + M.sync(tree[i], done) end end -local function get_commits(cb) - local results = {} - local done = 0 - for i = 1, #packages do - local package = packages[i] - - if package.exists then - proc.git_rev_parse(package.dir, "HEAD", function(err, commit) - if not err then - results[package.id] = commit - end - - done = done + 1 - if done == #packages then - cb(results) - end - end) - else - done = done + 1 - end - end -end local function print_list(cb) + local function get_commits(cb) + local results = {} + local done = 0 + for i = 1, #packages do + local package = packages[i] + + if package.exists then + proc.git_rev_parse(package.dir, "HEAD", function(err, commit) + if not err then + results[package.id] = commit + end + + done = done + 1 + if done == #packages then + cb(results) + end + end) + else + done = done + 1 + end + end + end + get_commits(function(commits) local buffer = vim.api.nvim_create_buf(true, true) local line, indent = 0, 0 @@ -777,18 +785,18 @@ local function print_list(cb) walk_graph(root) - print() - print("Debug information:") + -- print() + -- print("Debug information:") - local debug = {} - for l in vim.inspect(packages):gmatch("[^\n]+") do - debug[#debug + 1] = l - end + -- local debug = {} + -- for l in vim.inspect(packages):gmatch("[^\n]+") do + -- debug[#debug + 1] = l + -- end - vim.api.nvim_buf_set_lines(buffer, line, -1, false, debug) - vim.api.nvim_buf_set_name(buffer, "packages.dep") - vim.api.nvim_buf_set_option(buffer, "bufhidden", "wipe") - vim.api.nvim_buf_set_option(buffer, "modifiable", false) + -- vim.api.nvim_buf_set_lines(buffer, line, -1, false, debug) + -- vim.api.nvim_buf_set_name(buffer, "packages.dep") + -- vim.api.nvim_buf_set_option(buffer, "bufhidden", "wipe") + -- vim.api.nvim_buf_set_option(buffer, "modifiable", false) vim.cmd("vsp") vim.api.nvim_win_set_buf(0, buffer) @@ -799,86 +807,100 @@ local function print_list(cb) end) end -vim.cmd([[ - command! DepSync lua require("dep").sync() - command! DepReload lua require("dep").reload() - command! DepClean lua require("dep").clean() - command! DepList lua require("dep").list() - command! DepLog lua require("dep").open_log() - command! DepConfig lua require("dep").open_config() -]]) +-- basically the main function of our program +return function(opts) + logger.pipe = logger:setup() -local function wrap_api(name, fn) - return function(...) - if initialized then - local ok, err = pcall(fn, ...) - if not ok then - logger:log("error", err) - end - else - logger:log("error", string.format("cannot call %s; dep is not initialized", name)) - end + --- make comparison for table.sort + ---@param a table package spec a + ---@param b table 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 -end ---todo: prevent multiple execution of async routines -return setmetatable({ - sync = wrap_api("dep.sync", function(on_complete) - sync_list(packages, on_complete) - end), + initialized, err = pcall(function() + base_dir = opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/" + M.benchmark("load", function() + -- register all packages + root = M.registerpkg("squibid/dep") + M.registertree(opts) - reload = wrap_api("dep.reload", reload_all), - clean = wrap_api("dep.clean", clean), - list = wrap_api("dep.list", print_list), - - open_log = wrap_api("dep.open_log", function() - vim.cmd("vsp " .. logger.path) - end), - - open_config = wrap_api("dep.open_config", function() - vim.cmd("vsp " .. config_path) - end), -}, { - __call = function(_, config) - local err - perf = {} - config_path = debug.getinfo(2, "S").source:sub(2) - - initialized, err = pcall(function() - base_dir = config.base_dir or (vim.fn.stdpath("data") .. "/site/pack/deps/opt/") - packages = {} - - bench("load", function() - root = register("squibid/dep") - register_recursive(config) - sort_dependencies() - ensure_acyclic() - end) - - reload() - - local should_sync = function(package) - if config.sync == "new" or config.sync == nil then - return not package.exists - else - return config.sync == "always" - end - end - - local targets = {} + -- sort packages + table.sort(packages, comp) + -- sort package dependencies for i = 1, #packages do - local package = packages[i] - if should_sync(package) then - targets[#targets + 1] = package - end + table.sort(packages[i].dependencies, comp) + table.sort(packages[i].dependents, comp) end - sync_list(targets) + -- make sure there arent any circular dependencies + M.findcycle() end) - if not initialized then - logger:log("error", err) + -- 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 - end, -}) + + -- get all package that need syncing + local targets = {} + for i in pairs(packages) do + if shouldsync(packages[i]) then + targets[i] = packages[i] + end + end + + M.synctree(targets) + end) + + if not initialized then + logger:log("error", err) + end + + vim.api.nvim_create_user_command("DepLog", function() + vim.cmd('vsp '..logger.path) + vim.opt_local.readonly = true + + local w = vim.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.api.nvim_command('checktime') + -- Debounce: stop/start. + w:stop() + watch_file(fname) + end)) + end + + watch_file(logger.path) + end, {}) + + vim.api.nvim_create_user_command("DepSync", function() + M.synctree(packages) + end, {}) + + vim.api.nvim_create_user_command("DepReload", function() + M.reload() + end, {}) + + vim.api.nvim_create_user_command("DepList", function() + print_list() + end, {}) + + logger:cleanup() +end diff --git a/lua/dep/log.lua b/lua/dep/log.lua index 86994b6..3993856 100644 --- a/lua/dep/log.lua +++ b/lua/dep/log.lua @@ -1,23 +1,28 @@ --- --- Copyright (c) 2022 chiya.dev --- --- Use of this source code is governed by the MIT License --- which can be found in the LICENSE file and at: --- --- https://chiya.dev/licenses/mit.txt --- -local vim, setmetatable, pcall, debug, string, os, assert = vim, setmetatable, pcall, debug, string, os, assert +local logger = {} +logger.stage_colors = { + skip = "Comment", + clean = "Boolean", + install = "MoreMsg", + update = "WarningMsg", + delete = "Directory", + error = "ErrorMsg", +} + +--- create the default logging path +---@return string path to the logfile local function default_log_path() - -- ensure cache directory exists (#5) + -- create cache directory and chmod it if it doesn't already exist local path = vim.fn.stdpath("cache") if not vim.loop.fs_stat(path) then vim.loop.fs_mkdir(path, 0x1ff) -- 0777 end - return path .. "/dep.log" + return vim.fs.joinpath(path, "/dep.log") end +--- attempt to format a string +---@vararg string formating args local function try_format(...) local ok, s = pcall(string.format, ...) if ok then @@ -25,85 +30,69 @@ local function try_format(...) end end ---- Writes logs to a file and prints pretty status messages. -local Logger = setmetatable({ - __metatable = "Logger", - __index = { - --- Prints a message associated with a stage. - log = function(self, stage, message, ...) - -- calling function - local source = debug.getinfo(2, "Sl").short_src +--- setup all logging stuff +---@param path string|nil optional alternative path for the log file +---@return table +function logger:setup(path) + logger.path = path or default_log_path() + local pipe - -- format or stringify message - if type(message) == "string" then - message = try_format(message, ...) or message - else - message = vim.inspect(message) + logger.handle = assert(vim.loop.fs_open(logger.path, "w", 0x1a4)) -- 0644 + pipe = vim.loop.new_pipe() + pipe:open(logger.handle) + + return pipe +end + +--- log a message +---@param level string error level +---@param message any string message to send +---@vararg any options to go into the message +function logger:log(level, message, ...) + -- make sure the message string is actually a string, and formatted + -- appropriately + if type(message) == "string" then + message = try_format(message, ...) or message + else + message = vim.inspect(message) + end + + -- get debug info about the current function + local source = debug.getinfo(2, "Sl") + + -- schedule a log message to be sent to vim, and the log file + vim.schedule(function() + if not logger.silent then + if level == "error" then + vim.api.nvim_echo({ { string.format("[dep] %s", message) } }, true, { err = true }) + elseif logger.stage_colors[level] then + vim.api.nvim_echo({ + { "[dep]", "Identifier" }, + { " " }, + { message, logger.stage_colors[level] }, + }, true, {}) end + end - -- print and write must be done on the main event loop - vim.schedule(function() - if not self.silent then - if stage == "error" then - vim.api.nvim_err_writeln(string.format("[dep] %s", message)) - elseif self.stage_colors[stage] then - vim.api.nvim_echo({ - { "[dep]", "Identifier" }, - { " " }, - { message, self.stage_colors[stage] }, - }, true, {}) - end - end + -- write to the pipe if it's open + if logger.pipe then + logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%Y/%m/%d"), source.short_src:gsub('.*%/', ''), source.currentline, message)) + end + end) +end - if self.pipe then - self.pipe:write(string.format("[%s] %s: %s\n", os.date(), source, message)) - end - end) - end, +--- cleanup all logging stuff +---@param pipe table? pipe +---@param handle table? handle +function logger:cleanup(pipe, handle) + if pipe then + pipe:close() + pipe = nil + end + if handle then + vim.loop.fs_close(logger.handle) + handle = nil + end +end - --- Closes the log file handle. - close = function(self) - if self.pipe then - self.pipe:close() - self.pipe = nil - end - - if self.handle then - vim.loop.fs_close(self.handle) - self.handle = nil - end - end, - }, -}, { - --- Constructs a new `Logger`. - __call = function(mt, path) - path = path or default_log_path() - - -- clear and open log file - local handle = assert(vim.loop.fs_open(path, "w", 0x1a4)) -- 0644 - local pipe = vim.loop.new_pipe() - pipe:open(handle) - - return setmetatable({ - path = path, - handle = handle, - pipe = pipe, - silent = false, - - -- TODO: This looks good for me ;) but it should have proper vim color mapping for other people. - stage_colors = { - skip = "Comment", - clean = "Boolean", - install = "MoreMsg", - update = "WarningMsg", - delete = "Directory", - error = "ErrorMsg", - }, - }, mt) - end, -}) - -return { - Logger = Logger, - global = Logger(), -} +return logger diff --git a/lua/dep/package.lua b/lua/dep/package.lua deleted file mode 100644 index 513df8d..0000000 --- a/lua/dep/package.lua +++ /dev/null @@ -1,273 +0,0 @@ --- --- Copyright (c) 2022 chiya.dev --- --- Use of this source code is governed by the MIT License --- which can be found in the LICENSE file and at: --- --- https://chiya.dev/licenses/mit.txt --- -local require, type, setmetatable, error, table, assert, math, os, debug = - require, type, setmetatable, error, table, assert, math, os, debug -local logger = require("dep.log").global - -local function parse_name_from_id(id) - local name = id:match("^[%w-_.]+/([%w-_.]+)$") - if name then - return name - else - error(string.format('invalid package name "%s"; must be in the format "user/package"', id)) - end -end - -local function is_nonempty_str(s) - return type(s) == "string" and #s ~= 0 -end - ---- Package information. -local Package = setmetatable({ - __metatable = "Package", - __index = { - --- Runs all registered hooks of the given type. - run_hooks = function(self, hook) - local hooks = self["on_" .. hook] - if not hooks or #hooks == 0 then - return true - end - - local start = os.clock() - for i = 1, #hooks do - local ok, err = xpcall(hooks[i], debug.traceback) - if not ok then - return false, err - end - end - - local elapsed = os.clock() - start - self.perf.hooks[hook] = elapsed - - logger:log( - "hook", - "triggered %d %s %s for %s in %dms", - #hooks, - hook, - #hooks == 1 and "hook" or "hooks", - self.id, - elapsed - ) - - return true - end, - }, -}, { - --- Constructs a new `Package` with the given identifier. - __call = function(mt, id) - local name = parse_name_from_id(id) - return setmetatable({ - id = id, - name = name, - url = "https://github.com/" .. id .. ".git", - enabled = true, - exists = false, - added = false, - configured = false, - loaded = false, - dependencies = {}, - dependents = {}, - subtree_configured = false, - subtree_loaded = false, - on_setup = {}, - on_config = {}, - on_load = {}, - perf = { hooks = {} }, - }, mt) - end, -}) - ---- Manages a set of packages. -local PackageStore = setmetatable({ - __metatable = "PackageStore", - __index = { - --- Links the given packages such that the parent must load before the child. - link_dependency = function(self, parent, child) - if not parent.dependents[child.id] then - parent.dependents[child.id] = child - parent.dependents[#parent.dependents + 1] = child - end - - if not child.dependencies[parent.id] then - child.dependencies[parent.id] = parent - child.dependencies[#child.dependencies + 1] = parent - end - end, - - --- Ensures the given package spec table is valid. - validate_spec = function(self, spec) - assert(spec[1] ~= nil, "package id missing from spec") - assert(type(spec[1]) == "string", "package id must be a string") - parse_name_from_id(spec[1]) - - assert(spec.as == nil or is_nonempty_str(spec.as), "package name must be a string") - assert(spec.url == nil or type(spec.url) == "string", "package url must be a string") -- TODO: validate url or path - assert(spec.branch == nil or is_nonempty_str(spec.branch), "package branch must be a string") - assert(spec.pin == nil or type(spec.pin) == "boolean", "package pin must be a boolean") - assert(spec.disable == nil or type(spec.disable) == "boolean", "package disable must be a boolean") - - assert( - spec.requires == nil or type(spec.requires) == "table" or type(spec.requires) == "string", - "package requires must be a string or table" - ) - assert( - spec.deps == nil or type(spec.deps) == "table" or type(spec.deps) == "string", - "package deps must be a string or table" - ) - - assert(spec.setup == nil or type(spec.setup) == "function", "package setup must be a function") - assert(spec.config == nil or type(spec.config) == "function", "package config must be a function") - assert(spec[2] == nil or type(spec[2]) == "function", "package loader must be a function") - end, - - --- Creates or updates a package from the given spec table, and returns that package. - add_spec = function(self, spec, scope) - self:validate_spec(spec) - scope = scope or {} - - local id = spec[1] - local pkg = self[id] - - if not pkg then - pkg = Package(id) - self[id], self[#self + 1] = pkg, pkg - end - - -- blend package spec with existing package info - pkg.name = spec.as or pkg.name - pkg.url = spec.url or pkg.url - pkg.branch = spec.branch or pkg.branch - pkg.pin = scope.pin or spec.pin or pkg.pin - pkg.enabled = not scope.disable and not spec.disable and pkg.enabled - - pkg.on_setup[#pkg.on_setup + 1] = spec.setup - pkg.on_config[#pkg.on_config + 1] = spec.config - pkg.on_load[#pkg.on_load + 1] = spec[2] - - local requires = type(spec.requires) == "table" and spec.requires or { spec.requires } - local deps = type(spec.deps) == "table" and spec.deps or { spec.deps } - - -- recursively add specs for dependencies and dependents - for i = 1, #requires do - self:link_dependency(self:add_spec(requires[i], scope), pkg) - end - - for i = 1, #deps do - self:link_dependency(pkg, self:add_spec(deps[i], scope)) - end - end, - - --- Adds the given list of specs. - add_specs = function(self, specs, scope) - assert(type(specs) == "table", "package list must be a table") - assert(specs.pin == nil or type(specs.pin) == "boolean", "package list pin must be a boolean") - assert(specs.disable == nil or type(specs.disable) == "boolean", "package list disable must be a boolean") - assert(specs.modules == nil or type(specs.modules) == "table", "package list module list must be a table") - - scope = scope or {} - scope = { - -- outer scope takes precedence over inner list's overrides - pin = scope.pin or specs.pin, - disable = scope.disable or specs.disable, - } - - -- add specs in spec list - for i = 1, #specs do - self:add_spec(specs[i], scope) - end - - -- recursively add referenced spec list modules - if specs.modules then - local prefix = specs.modules.prefix or "" - for i = 1, #specs.modules do - local name = specs.modules[i] - assert(type(name) == "string", "package list inner module name must be a string") - name = prefix .. name - - local module = require(name) - assert(type(module) == "table", "package list inner module did not return a spec list table") - self:add_specs(module, scope) - end - end - end, - - --- Ensures there are no circular dependencies in this package store. - ensure_acyclic = function(self) - -- tarjan's strongly connected components algorithm - local idx, indices, lowlink, stack = 0, {}, {}, {} - - local function connect(pkg) - indices[pkg.id], lowlink[pkg.id] = idx, idx - stack[#stack + 1], stack[pkg.id] = pkg, true - idx = idx + 1 - - for i = 1, #pkg.dependents do - local dependent = pkg.dependents[i] - - if not indices[dependent.id] then - local cycle = connect(dependent) - if cycle then - return cycle - else - lowlink[pkg.id] = math.min(lowlink[pkg.id], lowlink[dependent.id]) - end - elseif stack[dependent.id] then - lowlink[pkg.id] = math.min(lowlink[pkg.id], indices[dependent.id]) - end - end - - if lowlink[pkg.id] == indices[pkg.id] then - local cycle = { pkg } - local node - - repeat - node = stack[#stack] - stack[#stack], stack[node.id] = nil, nil - cycle[#cycle + 1] = node - until node == pkg - - -- a node is by definition strongly connected to itself - -- ignore single-node components unless the package explicitly specified itself as a dependency (i.e. the user is being weird) - if #cycle > 2 or pkg.dependents[pkg.id] then - return cycle - end - end - end - - for i = 1, #self do - local pkg = self[i] - - if not indices[pkg.id] then - local cycle = connect(pkg) - if cycle then - -- found dependency cycle - local names = {} - for j = 1, #cycle do - names[j] = cycle[j].id - end - error("circular dependency detected in package dependency graph: " .. table.concat(names, " -> ")) - end - end - end - end, - }, -}, { - --- Constructs a new `PackageStore`. - __call = function(mt) - -- hash part of store maps package ids to packages - -- array part of store is a list of packages - -- all packages in a store are unique based on their id - return setmetatable({}, mt) - end, -}) - -return { - Package = Package, - PackageStore = PackageStore, -} diff --git a/lua/dep/proc.lua b/lua/dep/proc.lua index 4b95baa..85c1858 100644 --- a/lua/dep/proc.lua +++ b/lua/dep/proc.lua @@ -1,4 +1,3 @@ -local logger = require("dep.log").global local proc = {} function proc.exec(process, args, cwd, env, cb) @@ -9,15 +8,6 @@ function proc.exec(process, args, cwd, env, cb) end local function cb_exit(job_id, exit_code, _) local output = table.concat(buffer) - logger:log( - process, - string.format( - 'Job %s ["%s"] finished with exitcode %s\n%s', - job_id, - table.concat(args, '", "'), - exit_code, - output) - ) cb(exit_code ~= 0, output) end table.insert(args, 1, process) @@ -43,7 +33,7 @@ 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 + args[#args + 1] = "--branch="..branch end proc.exec("git", args, nil, git_env, cb) @@ -61,4 +51,70 @@ function proc.git_reset(dir, treeish, cb) 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 string.match(branch or "", "*") ~= "*" 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_TERMINAL_PROMPT = 0 }, + stdin = nil, + on_stdout = cb_output, + on_stderr = cb_output, + on_exit = function(_, exit_code, _) + if exit_code == 0 then + -- 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 = string.gsub(tag, "refs/tags/", "") + tag = string.gsub(tag, "%^{}", "") + + 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 + }) +end + return proc diff --git a/lua/dep2.lua b/lua/dep2.lua deleted file mode 100644 index a4d4953..0000000 --- a/lua/dep2.lua +++ /dev/null @@ -1,12 +0,0 @@ --- --- Copyright (c) 2022 chiya.dev --- --- Use of this source code is governed by the MIT License --- which can be found in the LICENSE file and at: --- --- https://chiya.dev/licenses/mit.txt --- -local logger = require("dep.log").global -local store = require("dep.package").PackageStore() - --- placeholder for refactoring diff --git a/lua/lazy/utils.lua b/lua/lazy/utils.lua new file mode 100644 index 0000000..4487914 --- /dev/null +++ b/lua/lazy/utils.lua @@ -0,0 +1,97 @@ +---@class lazy +---@field load function +---@field command_ids table +---@field auto_ids table +---@field keybind_ids table +local lazy = {} + +--- create a new instance of lazy +---@return lazy +function lazy:new() + local o = {} + + setmetatable(o, self) + + o.command_ids = {} + o.auto_ids = {} + o.keybind_ids = {} + + self.__index = self + + return o +end + +--- set the loading function +---@param load function the loading function +function lazy:set_load(load) + self.load = load +end + +--- get the configured load function +---@return function load function +function lazy:get_load() + return self.load +end + +--- create a usercommand which will trigger the plugin to load +---@param name string the name of the command +---@param opts vim.api.keyset.user_command? options +function lazy:cmd(name, opts) + opts = opts or {} + vim.api.nvim_create_user_command(name, function(o) + self:cleanup() + end, opts) + + table.insert(self.command_ids, name) +end + +--- user an auto command which will trigger the plugin to load +---@param event string the event to trigger on +---@param opts vim.api.keyset.create_autocmd? options +function lazy:auto(event, opts) + opts = opts or {} + opts['once'] = true + + opts['callback'] = function() + self:cleanup() + end + + table.insert(self.auto_ids, vim.api.nvim_create_autocmd(event, opts)) +end + +--- create a keybind which will trigger the plugin to load +---@param mode string the mode to trigger in +---@param bind string the binding to use +---@param opts vim.keymap.set.Opts? options +function lazy:keymap(mode, bind, opts) + opts = opts or {} + vim.keymap.set(mode, bind, function() + self:cleanup() + + -- register keymap unload + local keys = vim.api.nvim_replace_termcodes(bind, true, false, true) + vim.api.nvim_feedkeys(keys, mode, false) + end, opts) + + table.insert(self.keybind_ids, { ['mode'] = mode, ['bind'] = bind }) +end + +--- cleanup all the callbacks, and load the plugin +function lazy:cleanup() + -- cleanup user commands + for _, v in pairs(self.command_ids) do + vim.api.nvim_del_user_command(v) + end + -- cleanup auto commands + for _, v in pairs(self.auto_ids) do + vim.api.nvim_del_autocmd(v) + end + -- cleanup keymaps + for _, v in pairs(self.keybind_ids) do + vim.keymap.del(v['mode'], v['bind'], {}) + end + -- load the plugin + self:load() +end + +return lazy From 8bcc8bc0b1925d7819d2a8fbf98f8a84ea5e4fe7 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 22 Apr 2025 17:31:35 -0500 Subject: [PATCH 02/74] try to clean it up (not working yet) --- lua/dep.lua | 718 +++----------------------------------------- lua/dep/git.lua | 155 ++++++++++ lua/dep/package.lua | 489 ++++++++++++++++++++++++++++++ lua/lazy/utils.lua | 1 - 4 files changed, 681 insertions(+), 682 deletions(-) create mode 100644 lua/dep/git.lua create mode 100644 lua/dep/package.lua diff --git a/lua/dep.lua b/lua/dep.lua index 58dcc90..adfde51 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -1,5 +1,6 @@ local logger = require('dep.log') -local proc = require('dep.proc') +local git = require('dep.git') +local packager = require('dep.package') ---all functions for convenience ---@type table @@ -8,22 +9,10 @@ local M = {} ---@type boolean local initialized ----root package ----@type table -local root - ---performance logging ---@type table local perf = {} ----table of every package where their id is the index ----@type table -local packages = {} - ----path to root of where plugins are downloaded ----@type string -local base_dir - --- get execution time of a function ---@param name string name of performance output ---@param code function function to run @@ -34,305 +23,35 @@ function M.benchmark(name, code, ...) perf[name] = os.clock() - start end ---- get name of package ----@param id string id of the package ----@return string id ----@nodiscard -function M.getpkgname(id) - local name = id:match("^[%w-_.]+/([%w-_.]+)$") - if name then - return name - else - error(string.format( - 'invalid name "%s"; must be in the format "user/package"', id)) - end -end - ---- tell the parent it has a child and the child it has a parent ----@param parent table parent package ----@param child table child package -function M.link_dependency(parent, child) - if not parent.dependents[child.id] then - parent.dependents[child.id] = child - parent.dependents[#parent.dependents + 1] = child - end - - if not child.dependencies[parent.id] then - child.dependencies[parent.id] = parent - child.dependencies[#child.dependencies + 1] = parent - end -end - ---- register a new package according to the spec ----@param spec table|string the package spec to register ----@param overrides table|nil a package spec that is used to override options ----@return table package a table containing the package spec ----@nodiscard -function M.registerpkg(spec, overrides) - overrides = overrides or {} - - if type(spec) ~= "table" then - spec = { spec } - end - - local id = spec[1] - local package = packages[id] - - -- if package hasn't been registered already, get the inital spec regisitered - if not package then - package = { - id = id, -- id of the package - enabled = true, -- whether it's going to be used - exists = false, -- if the package exists on the filesystem - lazy = false, -- if the package is lazy loaded in any way - added = false, -- if the package has been added in vim - configured = false, -- if the package has been configured - loaded = false, -- if a package has been loaded - subtree_loaded = false, - on_config = {}, -- table of functions to run on config - on_setup = {}, -- table of function to run on setup - on_load = {}, -- table of functions to run on load - lazy_load = {}, -- table of functions to run which will tell the package - -- when to load - dependencies = {}, -- this package's requirements - dependents = {}, -- packages that require this package - perf = {} - } - - packages[id] = package - end - - -- register the rest of the package spec - package.name = spec.as or package.name or M.getpkgname(id) - package.url = spec.url or package.url or ("https://github.com/"..id..".git") - package.branch = spec.branch or package.branch - package.dir = base_dir..package.name - package.commit = spec.commit - package.pin = overrides.pin or spec.pin or package.pin - package.enabled = not overrides.disable and not spec.disable and package.enabled - package.lazy = spec.lazy or package.lazy - - -- make sure that the package exists - package.exists = vim.fn.isdirectory(package.dir) ~= 0 - package.configured = package.exists - - -- register all the callback functions - table.insert(package.on_config, spec.config) - table.insert(package.on_load, spec.load) - table.insert(package.lazy_load, spec.lazy) - - -- if the current package isn't the root package then it depends on the root - -- package - if root and package ~= root then - M.link_dependency(root, package) - end - - -- link the dependencies - if spec.requires then - if type(spec.requires) == "string" then - spec.requires = { spec.requires } - end - for _, v in pairs(spec.requires) do - M.link_dependency(M.registerpkg(v), package) - end - end - - -- and link the dependents - if spec.deps then - if type(spec.deps) == "string" then - spec.deps = { spec.deps } - end - for _, v in pairs(spec.deps) do - local p = M.registerpkg(v) - M.link_dependency(package, p) - - -- if the child package is lazy loaded make sure the child package - -- is only loaded when the parent package has finished loading - if package.lazy then - table.insert(package.on_load, function() - M.loadtree(package, true) - end) - table.insert(p.lazy_load, function(_) end) - end - end - end - - return package -end - --- recurse over all packages and register them ----@param speclist table table of specs ----@param overrides table|nil a package spec that is used to override options +---@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 {} - -- make sure the overrides override and take into account the packages spec - overrides = { - pin = overrides.pin or speclist.pin, - disable = overrides.disable or speclist.disable - } - -- recurse the packages - for i = 1, #speclist do - local ok, err = pcall(M.registerpkg, speclist[i], 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 + overrides = { + pin = overrides.pin or spec.pin, + disable = overrides.disable or spec.disable + } + + local ok = packager:new(spec, overrides) -- if erroring print out the spec and the error if not ok then - error(string.format("%s (spec=%s)", err, vim.inspect(speclist[i]))) + error(string.format("%s (spec=%s)", err, vim.inspect(spec))) end end end ---- run specified hooks on specified package ----@param package table package spec ----@param type string which hook to run ----@return boolean, string|nil -function M.runhooks(package, type) - local hooks = package[type] - if #hooks == 0 then - return true - end - - local start = os.clock() - - -- chdir into the package directory to make running external commands - -- from hooks easier. - local last_cwd = vim.fn.getcwd() - vim.fn.chdir(package.dir) - - for i = 1, #hooks do - local ok, err = pcall(hooks[i]) - if not ok then - vim.fn.chdir(last_cwd) - - package.error = true - return false, err - end - end - - vim.fn.chdir(last_cwd) - package.perf[type] = os.clock() - start - - logger:log("hook", "triggered %d %s %s for %s", #hooks, type, - #hooks == 1 and "hook" or "hooks", package.id) - - return true -end - ---- make sure a package has been loaded ----@param package table package ----@param force boolean? force lazy packages to load ----@return boolean|table return true or false if loaded or package spec if lazy loaded -function M.ensureadded(package, force) - -- print("adding ~ "..package.id) - local function loadpkg(pkg) - -- make sure to load the dependencies first - for _, p in pairs(pkg.dependencies) do - if not p.loaded then - M.ensureadded(p, true) - end - end - - -- now start loading our plugin - local start = os.clock() - - -- trigger the packadd for the plugin - local ok, err = pcall(vim.cmd, "packadd " .. pkg.name) - if not ok then - pkg.error = true - return false, err - end - - pkg.added = true - pkg.perf.pack = os.clock() - start - logger:log("vim", "packadd completed for %s", pkg.id) - - -- set the package to loaded - package.loaded = true - logger:log("load", "loaded %s", package.id) - - -- trigger the on_load hooks - ok, err = M.runhooks(package, "on_load") - if not ok then - logger:log("error", "failed to load %s; reason: %s", package.id, err) - return - end - end - - if not package.added and not package.lazy or force then - loadpkg(package) - elseif not package.added and package.lazy then - logger:log("lazy", "registering %d lazy hooks for %s", #package.lazy_load, - package.id) - for _, load_cond in pairs(package.lazy_load) do - -- configure the lazy loader for the user - local l = require('lazy.utils'):new() - if l == true then - logger:log("lazy", "failed to get lazy utils") - return false - end - l:set_load(function() - logger:log("lazy", "triggered %d lazy hooks for %s", #package.lazy_load, - package.id) - loadpkg(package) - end) - - -- run it - load_cond(l) - end - return package - end - - return true -end - ---- load all packages in package tree ----@param package table package spec table ----@param force boolean? force lazy packages to load ----@return boolean boolean if tree was successfully loaded -function M.loadtree(package, force) - if not package.exists or not package.enabled or package.error then - return false - end - - if package.subtree_loaded then - return true - end - - if not package.lazy then - for i = 1, #package.dependencies do - if not package.dependencies[i].loaded then - return false - end - end - end - - if not package.loaded then - local ok, err = M.ensureadded(package, force) - if not ok then - logger:log("error", "failed to load %s; reason: %s", package.id, err) - return false - end - end - - package.subtree_loaded = true - - for i = 1, #package.dependents do - package.subtree_loaded = M.loadtree(package.dependents[i], force) and package.subtree_loaded - end - - return package.subtree_loaded -end - --- reload all packages in package table spec ---@param force boolean|nil force all packages to load function M.reload(force) - -- cleanup any previous errors in the package table - for i in pairs(packages) do - packages[i].error = false - end - - local reloaded = M.loadtree(root, force) + local reloaded = packager.get_root():loadtree(force) if reloaded then local ok, err @@ -400,9 +119,7 @@ function M.findcycle() end end - for i = 1, #packages do - local package = packages[i] - + for _, package in pairs(packager.get_packages()) do if not indexes[package.id] then local cycle = connect(package) if cycle then @@ -412,147 +129,8 @@ function M.findcycle() end end ---- unconfigure a packages tree ----@param package table package to unconfigure -function M.unconfiguretree(package) - -- unconfigure dependencies - for i = 1, #package.dependencies do - package.dependencies[i].subtree_loaded = false - end - - -- unconfigure dependents - for i = 1, #package.dependents do - package.dependents[i].loaded = false - package.dependents[i].added = false - package.dependents[i].configured = false - - package.dependents[i].subtree_loaded = false - end -end - ---- update/download a package ----@param package table package spec ----@param cb function callback -function M.sync(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 - - --- configure a package - ---@param pkg table package spec - local function configurepkg(pkg) - M.runhooks(package, "on_config") - - logger:log("config", "package: %s configured", pkg.id) - package.configured = true - end - - if package.exists then - if package.pin then - cb() - return - end - - local function logerr(err) - logger:log("error", "failed to update %s; reason: %s", package.id, err) - end - - proc.git_rev_parse(package.dir, "HEAD", function(err, before) - if err then - logerr(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 - M.unconfiguretree(package) - configurepkg(package) - logger:log("update", "updated %s; %s -> %s", package.id, before, after) - 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", 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 - M.unconfiguretree(package) - configurepkg(package) - logger:log("update", "updated %s; %s -> %s", package.id, before, after) - end - - cb(err) - end) - end - end) - end - end) - end - end - end) - else - logger:log("error", "%s: doesn't exist", package.id) - 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 - M.unconfiguretree(package) - configurepkg(package) - logger:log("install", "installed %s", package.id) - end - end) - else - package.exists = true - M.unconfiguretree(package) - configurepkg(package) - logger:log("install", "installed %s", package.id) - end - end - - cb(err) - end) - end -end - --- sync a tree of plugins ----@param tree table tree of plugins +---@param tree package[] tree of plugins ---@param cb function? callback function M.synctree(tree, cb) local progress = 0 @@ -579,234 +157,11 @@ function M.synctree(tree, cb) end end - for i in pairs(tree) do - M.sync(tree[i], done) + for _, package in pairs(tree) do + git.sync(package, done) end end - -local function print_list(cb) - local function get_commits(cb) - local results = {} - local done = 0 - for i = 1, #packages do - local package = packages[i] - - if package.exists then - proc.git_rev_parse(package.dir, "HEAD", function(err, commit) - if not err then - results[package.id] = commit - end - - done = done + 1 - if done == #packages then - cb(results) - end - end) - else - done = done + 1 - end - end - end - - get_commits(function(commits) - local buffer = vim.api.nvim_create_buf(true, true) - local line, indent = 0, 0 - - local function print(chunks) - local concat = {} - local column = 0 - - for _ = 1, indent do - concat[#concat + 1] = " " - column = column + 2 - end - - if not chunks then - chunks = {} - elseif type(chunks) == "string" then - chunks = { { chunks } } - end - - for i = 1, #chunks do - local chunk = chunks[i] - concat[#concat + 1] = chunk[1] - chunk.offset, column = column, column + #chunk[1] - end - - vim.api.nvim_buf_set_lines(buffer, line, -1, false, { table.concat(concat) }) - - for i = 1, #chunks do - local chunk = chunks[i] - if chunk[2] then - vim.api.nvim_buf_add_highlight(buffer, -1, chunk[2], line, chunk.offset, chunk.offset + #chunk[1]) - end - end - - line = line + 1 - end - - print(string.format("Installed packages (%s):", #packages)) - indent = 1 - - local loaded = {} - - local function dry_load(package) - if loaded[package.id] then - return - end - - for i = 1, #package.dependencies do - if not loaded[package.dependencies[i].id] then - return - end - end - - loaded[package.id], loaded[#loaded + 1] = true, package - - local chunk = { - { string.format("[%s] ", commits[package.id] or " "), "Comment" }, - { package.id, "Underlined" }, - } - - if not package.exists then - chunk[#chunk + 1] = { " *not installed", "Comment" } - end - - if not package.loaded then - chunk[#chunk + 1] = { " *not loaded", "Comment" } - end - - if not package.enabled then - chunk[#chunk + 1] = { " *disabled", "Comment" } - end - - if package.pin then - chunk[#chunk + 1] = { " *pinned", "Comment" } - end - - print(chunk) - - for i = 1, #package.dependents do - dry_load(package.dependents[i]) - end - end - - dry_load(root) - indent = 0 - - print() - print("Load time (μs):") - indent = 1 - local profiles = {} - - for i = 1, #packages do - local package = packages[i] - local profile = { - package = package, - total = 0, - setup = package.perf.on_setup or 0, - load = package.perf.on_load or 0, - pack = package.perf.pack or 0, - - "total", - "setup", - "pack", - "load", - } - - if package == root then - for k, v in pairs(perf) do - if profile[k] then - profile[k] = profile[k] + v - end - end - end - - for j = 1, #profile do - profile.total = profile.total + profile[profile[j]] - end - - profiles[#profiles + 1] = profile - end - - table.sort(profiles, function(a, b) - return a.total > b.total - end) - - for i = 1, #profiles do - local profile = profiles[i] - local chunk = { - { "- ", "Comment" }, - { profile.package.id, "Underlined" }, - { string.rep(" ", 40 - #profile.package.id) }, - } - - for j = 1, #profile do - local key, value = profile[j], profile[profile[j]] - chunk[#chunk + 1] = { string.format(" %5s ", key), "Comment" } - chunk[#chunk + 1] = { string.format("%4d", value * 1000000) } - end - - print(chunk) - end - - indent = 0 - print() - print("Dependency graph:") - - local function walk_graph(package) - local chunk = { - { "| ", "Comment" }, - { package.id, "Underlined" }, - } - - local function add_edges(p) - for i = 1, #p.dependencies do - local dependency = p.dependencies[i] - - if dependency ~= root and not chunk[dependency.id] then -- don't convolute the list - chunk[#chunk + 1] = { " " .. dependency.id, "Comment" } - chunk[dependency.id] = true - add_edges(dependency) - end - end - end - - add_edges(package) - print(chunk) - - for i = 1, #package.dependents do - indent = indent + 1 - walk_graph(package.dependents[i]) - indent = indent - 1 - end - end - - walk_graph(root) - - -- print() - -- print("Debug information:") - - -- local debug = {} - -- for l in vim.inspect(packages):gmatch("[^\n]+") do - -- debug[#debug + 1] = l - -- end - - -- vim.api.nvim_buf_set_lines(buffer, line, -1, false, debug) - -- vim.api.nvim_buf_set_name(buffer, "packages.dep") - -- vim.api.nvim_buf_set_option(buffer, "bufhidden", "wipe") - -- vim.api.nvim_buf_set_option(buffer, "modifiable", false) - - vim.cmd("vsp") - vim.api.nvim_win_set_buf(0, buffer) - - if cb then - cb() - end - end) -end - -- basically the main function of our program return function(opts) logger.pipe = logger:setup() @@ -823,19 +178,24 @@ return function(opts) end initialized, err = pcall(function() - base_dir = opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/" + packager.set_base_dir(opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/") M.benchmark("load", function() -- register all packages - root = M.registerpkg("squibid/dep") + 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 packages - table.sort(packages, comp) - -- sort package dependencies - for i = 1, #packages do - table.sort(packages[i].dependencies, comp) - table.sort(packages[i].dependents, comp) + for _, package in pairs(root.get_packages()) do + table.sort(package.requirements, comp) + table.sort(package.dependents, comp) end -- make sure there arent any circular dependencies @@ -858,9 +218,9 @@ return function(opts) -- get all package that need syncing local targets = {} - for i in pairs(packages) do - if shouldsync(packages[i]) then - targets[i] = packages[i] + for i, package in pairs(packager.get_packages()) do + if shouldsync(package) then + targets[i] = package end end @@ -891,16 +251,12 @@ return function(opts) end, {}) vim.api.nvim_create_user_command("DepSync", function() - M.synctree(packages) + M.synctree(packager.get_packages()) end, {}) vim.api.nvim_create_user_command("DepReload", function() M.reload() end, {}) - vim.api.nvim_create_user_command("DepList", function() - print_list() - end, {}) - logger:cleanup() end diff --git a/lua/dep/git.lua b/lua/dep/git.lua new file mode 100644 index 0000000..fc9736f --- /dev/null +++ b/lua/dep/git.lua @@ -0,0 +1,155 @@ +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) + if package.exists then + git.update(package, cb) + else + git.install(package, cb) + end +end + +--- install a given package +---@param package package package to install +---@param cb function callback +function git.install(package, cb) + local function configurepkg() + package:runhooks("on_config") + + logger:log("config", "package: %s configured", package.id) + package.configured = true + end + + + if not package.enabled then + cb() + return + end + + logger:log("error", "%s: doesn't exist", package.id) + 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() + configurepkg() + logger:log("install", "installed %s", package.id) + end + end) + else + package.exists = true + package:unconfiguretree() + configurepkg() + logger:log("install", "installed %s", package.id) + 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 + + --- configure a package + ---@param pkg table package spec + local function configurepkg(pkg) + package:runhooks("on_config") + + logger:log("config", "package: %s configured", pkg.id) + package.configured = true + end + + if package.pin then + cb() + return + end + + local function logerr(err) + logger:log("error", "failed to update %s; reason: %s", package.id, err) + end + + proc.git_rev_parse(package.dir, "HEAD", function(err, before) + if err then + logerr(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() + configurepkg(package) + logger:log("update", "updated %s; %s -> %s", package.id, before, after) + 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", 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() + configurepkg(package) + logger:log("update", "updated %s; %s -> %s", package.id, before, after) + end + + cb(err) + end) + end + end) + end + end) + end + end + end) +end + +return git diff --git a/lua/dep/package.lua b/lua/dep/package.lua new file mode 100644 index 0000000..ff587d1 --- /dev/null +++ b/lua/dep/package.lua @@ -0,0 +1,489 @@ +local logger = require('dep.log') + +---@class spec +---@field [1] string id +---@field setup function? code to run before the package is loaded +---@field load function? code to run after the package is loaded +---@field config function? code to run after the package is installed/updated +---@field lazy function? code to run which determines when the package is loaded +---@field as string? overrides the short name of the package which is usually set +--- to a substring of all the chars after '/' in spec[1] +---@field url string? the url to the git repository hosting the package +---@field branch string? the branch which the version of the package resides +---@field commit string? the commit which the version of the package resides +---@field disable boolean? if true disables the package from being loaded +---@field pin boolean? if true disables the package from being installed/updated +---@field reqs spec|spec[]|string? packages that this package requires +---@field deps spec|spec[]|string? packages that depend on this package + +---@class package +---@field id string id of the package +---@field enabled boolean whether it's going to be used +---@field exists boolean if the package exists on the filesystem +---@field lazy boolean if the package is lazy loaded in any way +---@field added boolean if the package has been added in vim +---@field configured boolean if the package has been configured +---@field loaded boolean if a package has been loaded +---@field subtree_loaded boolean is the subtree has been loaded +---@field on_config function[] table of functions to run on config +---@field on_setup function[] table of function to run on setup +---@field on_load function[] table of functions to run on load +---@field lazy_load function[] table of functions to run which will tell the +--- package when to load +---@field requirements package[] this package's requirements +---@field dependents package[] packages that require this package +---@field perf table performance metrics for the package +---@field name string the name of the package +---@field url string the url of the package +---@field branch string the branch of the package +---@field dir string the directory of the package +---@field commit string the commit of the package +---@field pin boolean whether to pin the package or not +local package = {} + +--- the base directory for the packages +---@type string +local base_dir + +--- the root package +---@type package +local root + +--- list of all package in dep +---@type package[] +local packages = {} + +--- check a spec to see if it's correct +---@param spec spec|string the specification to check +---@return spec|false spec if the spec is ok or false +local function check_spec(spec) + -- make sure spec is a table + if type(spec) == "string" then + spec = { spec } + end + + -- make sure all the data is correct + do -- spec[1] + if type(spec[1]) ~= "string" then + logger:log("spec", "spec[1] must be a string") + return false + end + + local name = spec[1]:match("^[%w-_.]+/([%w-_.]+)$") + if not name then + logger:log("spec",'invalid name "%s"; must be in the format "user/package"', spec[1]) + return false + end + end + + if spec.setup ~= nil then -- spec.setup + if type(spec.setup) ~= "function" then + logger:log("spec", "spec.setup must be a function in %s", spec[1]) + return false + end + end + + if spec.load ~= nil then -- spec.load + if type(spec.load) ~= "function" then + logger:log("spec", "spec.load must be a function in %s", spec[1]) + return false + end + end + + if spec.config ~= nil then -- spec.config + if type(spec.config) ~= "function" then + logger:log("spec", "spec.config must be a function in %s", spec[1]) + return false + end + end + + if spec.lazy ~= nil then -- spec.lazy + if type(spec.lazy) ~= "function" then + logger:log("spec", "spec.lazy must be a function in %s", spec[1]) + return false + end + end + + if spec.as ~= nil then -- spec.as + if type(spec.as) ~= "string" then + logger:log("spec", "spec.as must be a string in %s", spec[1]) + return false + end + end + + if spec.url ~= nil then -- spec.url + if type(spec.url) ~= "string" then + logger:log("spec", "spec.url must be a string in %s", spec[1]) + return false + end + end + + if spec.branch ~= nil then -- spec.branch + if type(spec.branch) ~= "string" then + logger:log("spec", "spec.branch must be a string in %s", spec[1]) + return false + end + end + + if spec.commit ~= nil then -- spec.commit + if type(spec.commit) ~= "string" then + logger:log("spec", "spec.commit must be a string in %s", spec[1]) + return false + end + end + + if spec.disable ~= nil then -- spec.disable + if type(spec.disable) ~= "boolean" then + logger:log("spec", "spec.disable must be a boolean in %s", spec[1]) + return false + end + end + + if spec.pin ~= nil then -- spec.pin + if type(spec.pin) ~= "boolean" then + logger:log("spec", "spec.pin must be a boolean in %s", spec[1]) + return false + end + end + + if spec.reqs ~= nil then -- spec.reqs + local is = type(spec.reqs) + if is ~= "table" and is ~= "string" then + logger:log("spec", "spec.reqs must be a table or a string in %s", spec[1]) + return false + end + + if (is == "string") then + spec.reqs = { spec.reqs } + end + end + + if spec.deps ~= nil then -- spec.deps + local is = type(spec.deps) + if is ~= "table" and is ~= "string" then + logger:log("spec", "spec.deps must be a table or a string in %s", spec[1]) + return false + end + + if (is == "string") then + spec.deps = { spec.deps } + end + end + + return spec +end + +--- tell the parent it has a child and the child it has a parent +---@param parent package? parent package if nil defaults to self +---@param child package child package +function package:link_dependency(parent, child) + parent = parent or self + + if not parent.dependents[child.id] then + parent.dependents[child.id] = child + table.insert(parent.dependents, child) + end + + if not child.requirements[parent.id] then + child.requirements[parent.id] = parent + table.insert(child.requirements, parent) + end +end + +--- create a new package instance +---@param spec spec|string a package spec to use for the new package +---@param overrides spec? a package spec that is used to overried this package +---@return package|false package an instance of the package or false on failure +---@nodisacard +function package:new(spec, overrides) + overrides = overrides or {} + + -- ensure that the spec is ok + local new_spec = check_spec(spec) + if new_spec == false then + logger:log("spec", vim.inspect(spec)) + logger:log("error", "spec check failed, check DepLog") + return false + else + spec = new_spec + end + + -- start initializing the package + local id = spec[1] + + local o = packages[id] or {} + setmetatable(o, self) + + -- if package hasn't been registered already, get the inital spec regisitered + if not o.id then + o.id = id -- id of the package + o.enabled = true -- whether it's going to be used + o.exists = false -- if the package exists on the filesystem + o.lazy = false -- if the package is lazy loaded in any way + o.added = false -- if the package has been added in vim + o.configured = false -- if the package has been configured + o.loaded = false -- if a package has been loaded + o.subtree_loaded = false + o.on_config = {} -- table of functions to run on config + o.on_setup = {} -- table of function to run on setup + o.on_load = {} -- table of functions to run on load + o.lazy_load = {} -- table of functions to run which will tell the package + -- when to load + o.requirements = {} -- this package's requirements + o.dependents = {} -- packages that require this package + o.perf = {} + + packages[id] = o + end + + o.name = spec.as or o.name or id + o.url = spec.url or o.url or ("https://github.com/"..id..".git") + o.branch = spec.branch or o.branch + o.dir = base_dir..o.name + o.commit = spec.commit + o.pin = overrides.pin or spec.pin or o.pin + o.enabled = not overrides.disable and not spec.disable and o.enabled + o.lazy = spec.lazy ~= nil or o.lazy + + -- make sure that the package exists + o.exists = vim.fn.isdirectory(o.dir) ~= 0 + o.configured = o.exists + + -- register all the callback functions + if spec.config ~= nil then + table.insert(o.on_config, spec.config) + end + if spec.setup ~= nil then + table.insert(o.on_setup, spec.setup) + end + if spec.load ~= nil then + table.insert(o.on_load, spec.load) + end + if spec.lazy ~= nil then + table.insert(o.lazy_load, spec.lazy) + end + + -- if the current package isn't the root package then it depends on the root + -- package + if root and package ~= root then + o:link_dependency(root, o) + elseif not root then + root = o + end + + -- link the dependencies + if spec.reqs then + ---it is the correct type as asserted in check_spec() + ---@diagnostic disable-next-line: param-type-mismatch + for _, req in pairs(spec.reqs) do + local pkg = package:new(req) + if type(pkg) == "table" then + o:link_dependency(pkg, o) + end + end + end + + -- and link the dependents + if spec.deps then + ---it is the correct type as asserted in check_spec() + ---@diagnostic disable-next-line: param-type-mismatch + for _, v in pairs(spec.deps) do + local pkg = package:new(v) + if type(pkg) ~= "table" then + return false + end + o:link_dependency(nil, pkg) + + -- if the child package is lazy loaded make sure the child package + -- is only loaded when the parent package has finished loading + if package.lazy then + table.insert(package.on_load, function() + o:loadtree(true) + end) + table.insert(pkg.lazy_load, function(_) end) + end + end + end + + self.__index = self + + return o +end + +--- set the base directory for packages +---@param _base_dir string base directory +function package.set_base_dir(_base_dir) + base_dir = _base_dir +end + +--- get the root directory +---@return package root +function package.get_root() + return root +end + +--- get the packages in dep +---@return package root +function package.get_packages() + return packages +end + +--- run specified hooks on the current package +---@param type "on_load"|"on_config"|"on_setup" which hook to run +---@return boolean, string|nil +function package:runhooks(type) + local hooks = self[type] + if #hooks == 0 then + return true + end + + local start = os.clock() + + -- chdir into the package directory to make running external commands + -- from hooks easier. + local last_cwd = vim.fn.getcwd() + vim.fn.chdir(self.dir) + + for i = 1, #hooks do + local ok, err = pcall(hooks[i]) + if not ok then + vim.fn.chdir(last_cwd) + + return false, err + end + end + + vim.fn.chdir(last_cwd) + self.perf[type] = os.clock() - start + + logger:log("hook", "triggered %d %s %s for %s", #hooks, type, + #hooks == 1 and "hook" or "hooks", self.id) + + return true +end + +--- make sure a package has been loaded +---@param force boolean? force lazy packages to load +---@return boolean|table return true or false if loaded or package spec if lazy loaded +function package:ensureadded(force) + --- load a package + ---@param pkg package + local function loadpkg(pkg) + -- make sure to load the dependencies first + for _, p in pairs(pkg.requirements) do + if not p.loaded then + p:ensureadded(true) + end + end + + -- now start loading our plugin + local start = os.clock() + + -- run setup hooks + self:runhooks("on_setup") + + -- trigger the packadd for the plugin + local ok, err = pcall(vim.cmd, "packadd "..pkg.name) + if not ok then + return false, err + end + + pkg.added = true + pkg.perf.pack = os.clock() - start + logger:log("vim", "packadd completed for %s", pkg.id) + + -- set the package to loaded + self.loaded = true + logger:log("load", "loaded %s", self.id) + + -- trigger the on_load hooks + ok, err = self:runhooks("on_load") + if not ok then + logger:log("error", "failed to load %s; reason: %s", self.id, err) + return + end + end + + -- make sure the package is lazy loaded if need be + if not self.added and not self.lazy or force then + loadpkg(self) + elseif not self.added and self.lazy then + logger:log("lazy", "registering %d lazy hooks for %s", #self.lazy_load, + self.id) + for _, load_cond in pairs(self.lazy_load) do + -- configure the lazy loader for the user + local l = require('lazy.utils'):new() + if l == true then + logger:log("lazy", "failed to get lazy utils") + return false + end + l:set_load(function() + logger:log("lazy", "triggered %d lazy hooks for %s", #self.lazy_load, + self.id) + loadpkg(self) + end) + + -- run it + load_cond(l) + end + return self + end + + return true +end + +--- load all packages in package tree +---@param force boolean? force lazy packages to load +---@return boolean boolean if tree was successfully loaded +function package:loadtree(force) + if not self.exists or not self.enabled then + return false + end + + if self.subtree_loaded then + return true + end + + if not self.lazy then + for _, requirement in pairs(self.requirements) do + if not requirement.loaded then + return false + end + end + end + + if not self.loaded then + local ok, err = self:ensureadded(force) + if not ok then + logger:log("error", "failed to load %s; reason: %s", self.id, err) + return false + end + end + + package.subtree_loaded = true + + for _, dependant in pairs(self.dependents) do + self.subtree_loaded = dependant:loadtree(force) and self.subtree_loaded + end + + return self.subtree_loaded +end + +--- unconfigure a packages tree +function package:unconfiguretree() + -- unconfigure requirements + for _, requirement in pairs(self.requirements) do + requirement.subtree_loaded = false + end + + -- unconfigure dependents + for _, dependant in pairs(self.dependents) do + dependant.loaded = false + dependant.added = false + dependant.configured = false + + dependant.subtree_loaded = false + end +end + +return package diff --git a/lua/lazy/utils.lua b/lua/lazy/utils.lua index 4487914..4c4841b 100644 --- a/lua/lazy/utils.lua +++ b/lua/lazy/utils.lua @@ -11,7 +11,6 @@ function lazy:new() local o = {} setmetatable(o, self) - o.command_ids = {} o.auto_ids = {} o.keybind_ids = {} From 254436c24d657fd7569ed0cf44583b35c7be2401 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 22 Apr 2025 17:49:40 -0500 Subject: [PATCH 03/74] it's working now, but some of the logging is very redundant --- lua/dep.lua | 2 +- lua/dep/package.lua | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index adfde51..46cb1fb 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -193,7 +193,7 @@ return function(opts) M.registertree(opts) -- sort package dependencies - for _, package in pairs(root.get_packages()) do + for _, package in pairs(packager.get_packages()) do table.sort(package.requirements, comp) table.sort(package.dependents, comp) end diff --git a/lua/dep/package.lua b/lua/dep/package.lua index ff587d1..4ba5ea4 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -265,7 +265,7 @@ function package:new(spec, overrides) -- if the current package isn't the root package then it depends on the root -- package - if root and package ~= root then + if root and o ~= root then o:link_dependency(root, o) elseif not root then root = o @@ -383,6 +383,7 @@ function package:ensureadded(force) self:runhooks("on_setup") -- trigger the packadd for the plugin + ---@diagnostic disable-next-line: param-type-mismatch local ok, err = pcall(vim.cmd, "packadd "..pkg.name) if not ok then return false, err @@ -437,16 +438,19 @@ end ---@return boolean boolean if tree was successfully loaded function package:loadtree(force) if not self.exists or not self.enabled then + logger:log("load", "package %s doesn't exist or is not enabled", self.id) return false end if self.subtree_loaded then + logger:log("load", "package %s's subtree is already loaded", self.id) return true end if not self.lazy then for _, requirement in pairs(self.requirements) do if not requirement.loaded then + logger:log("load", "package %s requires %s to be loaded first", self.id, requirement.id) return false end end From c29395004d0140cbf8247ea4bc87dddbb05e8bd0 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 23 Apr 2025 00:15:13 -0500 Subject: [PATCH 04/74] more fixes --- lua/dep.lua | 153 +++++++++++++++++++-------------------- lua/dep/git.lua | 63 +++++++++------- lua/dep/log.lua | 2 +- lua/dep/package.lua | 172 +++++++++++++++++++++++++++++++++----------- lua/dep/proc.lua | 66 +++++++++-------- lua/lazy/utils.lua | 9 ++- 6 files changed, 282 insertions(+), 183 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index 46cb1fb..1b63ed5 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -2,15 +2,10 @@ local logger = require('dep.log') local git = require('dep.git') local packager = require('dep.package') ----all functions for convenience ----@type table +-- all functions for convenience local M = {} ----@type boolean -local initialized - ----performance logging ----@type table +-- performance logging local perf = {} --- get execution time of a function @@ -30,13 +25,13 @@ 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 - overrides = { - pin = overrides.pin or spec.pin, - disable = overrides.disable or spec.disable + over = { + pin = over.pin or spec.pin, + disable = over.disable or spec.disable } local ok = packager:new(spec, overrides) @@ -48,8 +43,48 @@ function M.registertree(speclist, overrides) end end +--- clean out old packages +function M.clean() + vim.loop.fs_scandir( + packager.get_base_dir(), + vim.schedule_wrap(function(err, handle) + if err then + logger:log("error", string.format("failed to clean; reason: %s", err)) + else + local queue = {} + + while handle do + local name = vim.loop.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", string.format("deleted %s", name)) + else + logger:log("error", string.format("failed to delete %s", name)) + end + end) + coroutine.resume(co) + end + end + end) + ) +end + --- reload all packages in package table spec ----@param force boolean|nil force all packages to load +---@param force boolean? force all packages to load function M.reload(force) local reloaded = packager.get_root():loadtree(force) @@ -72,63 +107,6 @@ function M.reload(force) end end ---- check if there's a circular dependency in the package tree -function M.findcycle() - local index = 0 - local indexes = {} - local lowlink = {} - local stack = {} - - -- use tarjan algorithm to find circular dependencies (strongly connected - -- components) - local function connect(package) - indexes[package.id], lowlink[package.id] = index, index - stack[#stack + 1], stack[package.id] = package, true - index = index + 1 - - for i = 1, #package.dependents do - local dependent = package.dependents[i] - - if not indexes[dependent.id] then - local cycle = connect(dependent) - if cycle then - return cycle - else - lowlink[package.id] = math.min(lowlink[package.id], lowlink[dependent.id]) - end - elseif stack[dependent.id] then - lowlink[package.id] = math.min(lowlink[package.id], indexes[dependent.id]) - end - end - - if lowlink[package.id] == indexes[package.id] then - local cycle = { package } - local node - - repeat - node = stack[#stack] - stack[#stack], stack[node.id] = nil, nil - cycle[#cycle + 1] = node - until node == package - - -- a node is by definition strongly connected to itself ignore single-node - -- components unless it explicitly specified itself as a dependency - if #cycle > 2 or package.dependents[package.id] then - return cycle - end - end - end - - for _, package in pairs(packager.get_packages()) do - if not indexes[package.id] then - local cycle = connect(package) - if cycle then - return cycle - end - end - end -end - --- sync a tree of plugins ---@param tree package[] tree of plugins ---@param cb function? callback @@ -141,16 +119,15 @@ function M.synctree(tree, cb) has_errors = has_errors or err if progress == #tree then - -- TODO: implement clean - -- clean() - M.reload() - 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 @@ -158,7 +135,10 @@ function M.synctree(tree, cb) end for _, package in pairs(tree) do - git.sync(package, done) + local co = coroutine.create(function() + git.sync(package, done) + end) + coroutine.resume(co) end end @@ -167,8 +147,8 @@ return function(opts) logger.pipe = logger:setup() --- make comparison for table.sort - ---@param a table package spec a - ---@param b table package spec b + ---@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 @@ -177,7 +157,7 @@ return function(opts) return a.id < b.id end - initialized, err = pcall(function() + 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 @@ -199,7 +179,10 @@ return function(opts) end -- make sure there arent any circular dependencies - M.findcycle() + 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 @@ -218,12 +201,13 @@ return function(opts) -- get all package that need syncing local targets = {} - for i, package in pairs(packager.get_packages()) do + for _, package in pairs(packager.get_packages()) do if shouldsync(package) then - targets[i] = package + table.insert(targets, package) end end + -- install all targets M.synctree(targets) end) @@ -231,17 +215,18 @@ return function(opts) 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 = vim.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.api.nvim_command('checktime') - -- Debounce: stop/start. w:stop() watch_file(fname) end)) @@ -258,5 +243,11 @@ return function(opts) 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 diff --git a/lua/dep/git.lua b/lua/dep/git.lua index fc9736f..23dde0f 100644 --- a/lua/dep/git.lua +++ b/lua/dep/git.lua @@ -1,3 +1,7 @@ +-- 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') @@ -7,31 +11,47 @@ local git = {} ---@param package package package to update/install ---@param cb function callback function git.sync(package, cb) - if package.exists then - git.update(package, cb) - else - git.install(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) - local function configurepkg() - package:runhooks("on_config") - - logger:log("config", "package: %s configured", package.id) - package.configured = true - end - if not package.enabled then cb() return end - logger:log("error", "%s: doesn't exist", package.id) proc.git_clone(package.dir, package.url, package.branch, function(err, message) if err then logger:log("error", "failed to install %s; reason: %s", @@ -44,15 +64,15 @@ function git.install(package, cb) else package.exists = true package:unconfiguretree() - configurepkg() logger:log("install", "installed %s", package.id) + configurepkg(package) end end) else package.exists = true package:unconfiguretree() - configurepkg() logger:log("install", "installed %s", package.id) + configurepkg(package) end end @@ -74,15 +94,6 @@ function git.update(package, cb) logger:log("error", "failed to update %s; reason: %s", package.id, err) end - --- configure a package - ---@param pkg table package spec - local function configurepkg(pkg) - package:runhooks("on_config") - - logger:log("config", "package: %s configured", pkg.id) - package.configured = true - end - if package.pin then cb() return @@ -112,8 +123,8 @@ function git.update(package, cb) cb(err) else package:unconfiguretree() - configurepkg(package) logger:log("update", "updated %s; %s -> %s", package.id, before, after) + configurepkg(package) end end) end @@ -124,7 +135,7 @@ function git.update(package, cb) log_err(message) cb(err) else - proc.git_rev_parse(package.dir, "FETCH_HEAD", function(err, after) + proc.git_rev_parse(package.dir, "FETCH_HEAD^{commit}", function(err, after) if err then log_err(after) cb(err) @@ -137,8 +148,8 @@ function git.update(package, cb) log_err(message) else package:unconfiguretree() - configurepkg(package) logger:log("update", "updated %s; %s -> %s", package.id, before, after) + configurepkg(package) end cb(err) diff --git a/lua/dep/log.lua b/lua/dep/log.lua index 3993856..a7a49ab 100644 --- a/lua/dep/log.lua +++ b/lua/dep/log.lua @@ -76,7 +76,7 @@ function logger:log(level, message, ...) -- write to the pipe if it's open if logger.pipe then - logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%Y/%m/%d"), source.short_src:gsub('.*%/', ''), source.currentline, message)) + logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%T"), source.short_src:gsub('.*%/', ''), source.currentline, message)) end end) end diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 4ba5ea4..fe206fe 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -23,13 +23,13 @@ local logger = require('dep.log') ---@field lazy boolean if the package is lazy loaded in any way ---@field added boolean if the package has been added in vim ---@field configured boolean if the package has been configured +---@field lazied boolean if the packages lazy loading has been set ---@field loaded boolean if a package has been loaded ---@field subtree_loaded boolean is the subtree has been loaded ---@field on_config function[] table of functions to run on config ---@field on_setup function[] table of function to run on setup ---@field on_load function[] table of functions to run on load ----@field lazy_load function[] table of functions to run which will tell the ---- package when to load +---@field lazy_load function[] table of functions to run which will tell the package when to load ---@field requirements package[] this package's requirements ---@field dependents package[] packages that require this package ---@field perf table performance metrics for the package @@ -71,7 +71,7 @@ local function check_spec(spec) local name = spec[1]:match("^[%w-_.]+/([%w-_.]+)$") if not name then - logger:log("spec",'invalid name "%s"; must be in the format "user/package"', spec[1]) + logger:log("spec", 'invalid name "%s"; must be in the format "user/package"', spec[1]) return false end end @@ -153,6 +153,7 @@ local function check_spec(spec) return false end + -- turn an id into a spec if (is == "string") then spec.reqs = { spec.reqs } end @@ -165,6 +166,7 @@ local function check_spec(spec) return false end + -- turn an id into a spec if (is == "string") then spec.deps = { spec.deps } end @@ -212,31 +214,33 @@ function package:new(spec, overrides) local id = spec[1] local o = packages[id] or {} + self.__index = self setmetatable(o, self) -- if package hasn't been registered already, get the inital spec regisitered if not o.id then - o.id = id -- id of the package - o.enabled = true -- whether it's going to be used - o.exists = false -- if the package exists on the filesystem - o.lazy = false -- if the package is lazy loaded in any way - o.added = false -- if the package has been added in vim - o.configured = false -- if the package has been configured - o.loaded = false -- if a package has been loaded + o.id = id + o.enabled = true + o.exists = false + o.lazy = false + o.added = false + o.lazied = false + o.configured = false + o.loaded = false o.subtree_loaded = false - o.on_config = {} -- table of functions to run on config - o.on_setup = {} -- table of function to run on setup - o.on_load = {} -- table of functions to run on load - o.lazy_load = {} -- table of functions to run which will tell the package - -- when to load - o.requirements = {} -- this package's requirements - o.dependents = {} -- packages that require this package + o.on_config = {} + o.on_setup = {} + o.on_load = {} + o.lazy_load = {} + + o.requirements = {} + o.dependents = {} o.perf = {} packages[id] = o end - o.name = spec.as or o.name or id + o.name = spec.as or o.name or id:match("^[%w-_.]+/([%w-_.]+)$") o.url = spec.url or o.url or ("https://github.com/"..id..".git") o.branch = spec.branch or o.branch o.dir = base_dir..o.name @@ -287,26 +291,32 @@ function package:new(spec, overrides) if spec.deps then ---it is the correct type as asserted in check_spec() ---@diagnostic disable-next-line: param-type-mismatch - for _, v in pairs(spec.deps) do - local pkg = package:new(v) - if type(pkg) ~= "table" then + for _, dep in pairs(spec.deps) do + local pkg = package:new(dep) + if not pkg then return false end - o:link_dependency(nil, pkg) + o:link_dependency(o, pkg) -- if the child package is lazy loaded make sure the child package -- is only loaded when the parent package has finished loading - if package.lazy then - table.insert(package.on_load, function() - o:loadtree(true) + if o.lazy then + table.insert(o.on_load, function() + local ok = o:loadtree(true) + if not ok then + logger:log("lazy", + "failed to run loadtree for %s, some packages may not be loaded", + o.id) + end end) + + -- tell the dep that it's gonna be lazy + pkg.lazy = true table.insert(pkg.lazy_load, function(_) end) end end end - self.__index = self - return o end @@ -316,14 +326,23 @@ function package.set_base_dir(_base_dir) base_dir = _base_dir end +--- get the base directory for packages +---@return string base_dir +---@nodiscard +function package.get_base_dir() + return base_dir +end + --- get the root directory ---@return package root +---@nodiscard function package.get_root() return root end --- get the packages in dep ---@return package root +---@nodiscard function package.get_packages() return packages end @@ -376,12 +395,12 @@ function package:ensureadded(force) end end + -- run setup hooks + pkg:runhooks("on_setup") + -- now start loading our plugin local start = os.clock() - -- run setup hooks - self:runhooks("on_setup") - -- trigger the packadd for the plugin ---@diagnostic disable-next-line: param-type-mismatch local ok, err = pcall(vim.cmd, "packadd "..pkg.name) @@ -394,23 +413,24 @@ function package:ensureadded(force) logger:log("vim", "packadd completed for %s", pkg.id) -- set the package to loaded - self.loaded = true - logger:log("load", "loaded %s", self.id) + pkg.loaded = true + logger:log("load", "loaded %s", pkg.id) -- trigger the on_load hooks - ok, err = self:runhooks("on_load") + ok, err = pkg:runhooks("on_load") if not ok then - logger:log("error", "failed to load %s; reason: %s", self.id, err) + logger:log("error", "failed to load %s; reason: %s", pkg.id, err) return end end -- make sure the package is lazy loaded if need be - if not self.added and not self.lazy or force then + if not self.added and not self.loaded and not self.lazy or force then loadpkg(self) elseif not self.added and self.lazy then logger:log("lazy", "registering %d lazy hooks for %s", #self.lazy_load, self.id) + self.lazied = true for _, load_cond in pairs(self.lazy_load) do -- configure the lazy loader for the user local l = require('lazy.utils'):new() @@ -436,26 +456,31 @@ end --- load all packages in package tree ---@param force boolean? force lazy packages to load ---@return boolean boolean if tree was successfully loaded +---@nodiscard function package:loadtree(force) + -- if the package doesn't exist or isn't enabled then don't load it if not self.exists or not self.enabled then - logger:log("load", "package %s doesn't exist or is not enabled", self.id) return false end - if self.subtree_loaded then - logger:log("load", "package %s's subtree is already loaded", self.id) + -- if the subtree is loaded then it's already loaded unless it needs forcing + if not force and self.subtree_loaded then return true end + -- if the package isn't lazy check that it's requirements are loaded if not self.lazy then for _, requirement in pairs(self.requirements) do - if not requirement.loaded then - logger:log("load", "package %s requires %s to be loaded first", self.id, requirement.id) + if not requirement.loaded and not requirement.lazy then + logger:log("error", "failed to load %s; requirement: %s isn't loaded", + self.id, requirement.id) return false end end end + -- if the package isn't loaded and isn't lazy then it should probably be + -- loaded if not self.loaded then local ok, err = self:ensureadded(force) if not ok then @@ -464,8 +489,9 @@ function package:loadtree(force) end end - package.subtree_loaded = true + self.subtree_loaded = true + -- make sure the dependants are loaded for _, dependant in pairs(self.dependents) do self.subtree_loaded = dependant:loadtree(force) and self.subtree_loaded end @@ -490,4 +516,68 @@ function package:unconfiguretree() end end +--- check a list of packages for any cycles +---@param pkgs package[] list of packages +---@return package[]|false cycle the cycle that was found or false if not found +---@nodisacard +function package.findcycle(pkgs) + local index = 0 + local indexes = {} + local lowlink = {} + local stack = {} + + --- use tarjan algorithm to find circular dependencies (strongly connected + --- components) + ---@param pkg package + local function connect(pkg) + indexes[pkg.id], lowlink[pkg.id] = index, index + stack[#stack + 1], stack[pkg.id] = pkg, true + index = index + 1 + + for i = 1, #pkg.dependents do + local dependent = pkg.dependents[i] + + if not indexes[dependent.id] then + local cycle = connect(dependent) + if cycle then + return cycle + else + lowlink[pkg.id] = math.min(lowlink[pkg.id], lowlink[dependent.id]) + end + elseif stack[dependent.id] then + lowlink[pkg.id] = math.min(lowlink[pkg.id], indexes[dependent.id]) + end + end + + if lowlink[pkg.id] == indexes[pkg.id] then + local cycle = { pkg } + local node + + repeat + node = stack[#stack] + stack[#stack], stack[node.id] = nil, nil + cycle[#cycle + 1] = node + until node == pkg + + -- a node is by definition strongly connected to itself ignore single-node + -- components unless it explicitly specified itself as a dependency + if #cycle > 2 or pkg.dependents[pkg.id] then + return cycle + end + end + end + + -- actually check the cycle + for _, pkg in pairs(pkgs) do + if not indexes[package.id] then + local cycle = connect(pkg) + if cycle then + return cycle + end + end + end + + return false +end + return package diff --git a/lua/dep/proc.lua b/lua/dep/proc.lua index 85c1858..a23f4cd 100644 --- a/lua/dep/proc.lua +++ b/lua/dep/proc.lua @@ -1,12 +1,18 @@ 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(job_id, exit_code, _) + local function cb_exit(_, exit_code, _) local output = table.concat(buffer) cb(exit_code ~= 0, output) end @@ -62,54 +68,56 @@ function proc.git_checkout(dir, branch, commit, cb) end function proc.git_resolve_branch(url, branch, cb) - if string.match(branch or "", "*") ~= "*" then + -- 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 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, }, + vim.fn.jobstart({ "git", "ls-remote", "--tags", "--sort", "v:refname", url }, { cwd = nil, - env = { GIT_TERMINAL_PROMPT = 0 }, + env = git_env, stdin = nil, on_stdout = cb_output, on_stderr = cb_output, on_exit = function(_, exit_code, _) - if exit_code == 0 then - -- 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 + if exit_code ~= 0 then + return + end - local tag = string.sub(v, s, e) - tag = string.gsub(tag, "refs/tags/", "") - tag = string.gsub(tag, "%^{}", "") - - table.insert(versions, tag) - ::continue:: + -- 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 - -- match the chosen version against all versions - for i = #versions, 1, -1 do - if branch == "*" then - cb(false, versions[i]) + 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 - else - local r = string.match(versions[i], branch) - if r then - cb(false, r) - return - end end end end diff --git a/lua/lazy/utils.lua b/lua/lazy/utils.lua index 4c4841b..8dc960e 100644 --- a/lua/lazy/utils.lua +++ b/lua/lazy/utils.lua @@ -37,7 +37,7 @@ end ---@param opts vim.api.keyset.user_command? options function lazy:cmd(name, opts) opts = opts or {} - vim.api.nvim_create_user_command(name, function(o) + vim.api.nvim_create_user_command(name, opts['callback'] or function() self:cleanup() end, opts) @@ -49,9 +49,8 @@ end ---@param opts vim.api.keyset.create_autocmd? options function lazy:auto(event, opts) opts = opts or {} - opts['once'] = true - - opts['callback'] = function() + opts['once'] = opts['once'] or true + opts['callback'] = opts['callback'] or function() self:cleanup() end @@ -64,7 +63,7 @@ end ---@param opts vim.keymap.set.Opts? options function lazy:keymap(mode, bind, opts) opts = opts or {} - vim.keymap.set(mode, bind, function() + vim.keymap.set(mode, bind, opts['callback'] or function() self:cleanup() -- register keymap unload From 2267d17d252531fa4c3383b2fe4f8010b9984253 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 23 Apr 2025 15:12:48 -0500 Subject: [PATCH 05/74] Update readme and add myself to the licence --- LICENSE | 3 ++- README.md | 70 +++++++++++++++++++++++++++---------------------------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/LICENSE b/LICENSE index 3a42b2f..5f28328 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2021 chiya.dev +Copyright (c) 2023 squibid +Copyright (c) 2021-2023 chiya.dev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 27937bc..3c73386 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,26 @@ # dep -[![License](https://img.shields.io/github/license/chiyadev/dep)](LICENSE) -[![Maintainer](https://img.shields.io/badge/maintainer-luaneko-pink)][4] -[![Issues](https://img.shields.io/github/issues/chiyadev/dep.svg)][8] -[![Contributors](https://img.shields.io/github/contributors/chiyadev/dep.svg)][9] - > This readme is a work in progress. A versatile, declarative and correct [neovim][2] package manager in [Lua][3]. -Originally written for personal use by [luaneko][4]. +Originally written for personal use by [luaneko][4]. Adapted by [squibid][5] for +general use. What does that mean? -1. `versatile` - packages can be declared in any Lua file in any order of your liking. +1. `versatile` - packages can be declared in any Lua file in any order of your +liking. 2. `declarative` - packages are declared using simple Lua tables. 3. `correct` - packages are always loaded in a correct and consistent order. -See also luaneko's [neovim-configs][10] for an example of how dep can be used in practice. +See also squibid's [neovim-configs][11] for an example of how dep can be used in +practice. ## Requirements - [Neovim][2] 0.6+ -- [Git][5] +> Needs checking not sure which version this requires +- [Git][6] ## Setup @@ -54,9 +53,7 @@ require "dep" { cleans removed packages and reloads packages as necessary. - `:DepClean` - cleans removed packages. - `:DepReload` - reloads all packages. -- `:DepList` - prints the package list, performance metrics and dependency graphs. - `:DepLog` - opens the log file. -- `:DepConfig` - opens the file that called dep, for convenience. ## Package specification @@ -109,9 +106,9 @@ A package must be declared in the following format. -- [boolean] Prevents the package from being updated. pin = true, - -- [string|array] Specifies dependencies that must be loaded before the package. + -- [string|array] Specifies requirements that must be loaded before the package. -- If given a string, it is wrapped into an array. - requires = {...}, + reqs = {...}, -- [string|array] Specifies dependents that must be loaded after the package. -- If given a string, it is wrapped into an array. @@ -137,7 +134,7 @@ combined into one. This is useful when declaring dependencies, which is explored require "dep" { { "user/package", - requires = "user/dependency", + reqs = "user/dependency", disabled = true, config = function() print "my config hook" @@ -158,7 +155,7 @@ require "dep" { require "dep" { { "user/package", - requires = { "user/dependency", "user/another_dependency" }, + reqs = { "user/dependency", "user/another_dependency" }, deps = "user/dependent", disabled = true, config = function() @@ -179,10 +176,10 @@ they are combined into one just like normal package specifications. require "dep" { { "user/package", - requires = { + reqs = { { "user/dependency1", - requires = "user/dependency2" + reqs = "user/dependency2" } } } @@ -205,7 +202,7 @@ require "dep" { require "dep" { { "user/dependency1", - requires = "user/dependency2", + reqs = "user/dependency2", deps = "user/package" } } @@ -214,11 +211,11 @@ require "dep" { require "dep" { { "user/dependency1", - requires = "user/dependency2" + reqs = "user/dependency2" }, { "user/package", - requires = "user/dependency1" + reqs = "user/dependency1" } } @@ -245,11 +242,11 @@ instead of hanging or crashing. require "dep" { { "user/package1", - requires = "user/package2" + reqs = "user/package2" }, { "user/package2", - requires = "user/package1" + reqs = "user/package1" } } ``` @@ -265,12 +262,12 @@ require "dep" { { "user/package1", disabled = true, -- implied - requires = "user/dependency" + reqs = "user/dependency" }, { "user/package2", disabled = true, -- implied - requires = "user/dependency" + reqs = "user/dependency" } } ``` @@ -281,14 +278,14 @@ If a dependency fails to load for some reason, all of its dependents are guarant require "dep" { { "user/problematic", - function() + load = function() error("bad hook") end }, { "user/dependent", requires = "user/problematic", - function() + load = function() print "unreachable" end } @@ -298,14 +295,14 @@ require "dep" { ## Separating code into modules Suppose you split your `init.lua` into two files `packages/search.lua` and -`packages/vcs.lua`, which declare the packages [telescope.nvim][6] and [vim-fugitive][7] respectively. +`packages/vcs.lua`, which declare the packages [telescope.nvim][7] and [vim-fugitive][8] respectively. ```lua -- ~/.config/nvim/lua/packages/search.lua: return { { "nvim-telescope/telescope.nvim", - requires = "nvim-lua/plenary.nvim" + reqs = "nvim-lua/plenary.nvim" } } ``` @@ -353,7 +350,7 @@ require("dep")(packages) require "dep" { { "nvim-telescope/telescope.nvim", - requires = "nvim-lua/plenary.nvim" + reqs = "nvim-lua/plenary.nvim" }, "tpope/vim-fugitive" } @@ -370,7 +367,7 @@ return { { "user/package", disabled = true, -- implied by module - requires = { + reqs = { { "user/dependency", -- disabled = true -- not implied @@ -419,9 +416,10 @@ dep is licensed under the [MIT License](LICENSE). [2]: https://neovim.io/ [3]: https://www.lua.org/ [4]: https://github.com/luaneko -[5]: https://git-scm.com/ -[6]: https://github.com/nvim-telescope/telescope.nvim -[7]: https://github.com/tpope/vim-fugitive -[8]: https://GitHub.com/chiyadev/dep/issues -[9]: https://github.com/chiyadev/dep/graphs/contributors -[10]: https://github.com/luaneko/neovim-config +[5]: https://squi.bid +[6]: https://git-scm.com/ +[7]: https://github.com/nvim-telescope/telescope.nvim +[8]: https://github.com/tpope/vim-fugitive +[9]: https://GitHub.com/chiyadev/dep/issues +[10]: https://github.com/chiyadev/dep/graphs/contributors +[11]: https://git.squi.bid/nvim From 296dc11c9391e94f18f30c0daf300e2ce9f1fe2b Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 23 Apr 2025 15:16:15 -0500 Subject: [PATCH 06/74] remove unnecessary log, and add a TODO for local plugins --- lua/dep/package.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lua/dep/package.lua b/lua/dep/package.lua index fe206fe..918d5c1 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -1,5 +1,13 @@ local logger = require('dep.log') +-- TODO: allow specificying a path to use instead of cloning +-- like so: @field path string? path to override other url +-- I might have to deal with removing dead links and what not +-- +-- I think it would be best to link the desired directory to the +-- neovim package directory, but idk if that will be easy enough +-- we'll see + ---@class spec ---@field [1] string id ---@field setup function? code to run before the package is loaded @@ -472,8 +480,6 @@ function package:loadtree(force) if not self.lazy then for _, requirement in pairs(self.requirements) do if not requirement.loaded and not requirement.lazy then - logger:log("error", "failed to load %s; requirement: %s isn't loaded", - self.id, requirement.id) return false end end From d217ffa0b6f37d64996ba9fb66cd196108e71169 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 24 Apr 2025 14:38:25 -0500 Subject: [PATCH 07/74] make dep only require nvim 0.8, and make the lazy utils better --- lua/dep.lua | 4 ++-- lua/dep/log.lua | 2 +- lua/dep/package.lua | 2 +- lua/lazy/utils.lua | 28 +++++++++++++++++++--------- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index 1b63ed5..098eff4 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -38,7 +38,7 @@ function M.registertree(speclist, overrides) -- if erroring print out the spec and the error if not ok then - error(string.format("%s (spec=%s)", err, vim.inspect(spec))) + error(string.format("(spec=%s)", vim.inspect(spec))) end end end @@ -226,7 +226,7 @@ return function(opts) local fullpath = vim.api.nvim_call_function( 'fnamemodify', { fname, ':p' }) w:start(fullpath, {}, vim.schedule_wrap(function(...) - vim.api.nvim_command('checktime') + vim.cmd('checktime') w:stop() watch_file(fname) end)) diff --git a/lua/dep/log.lua b/lua/dep/log.lua index a7a49ab..a127d77 100644 --- a/lua/dep/log.lua +++ b/lua/dep/log.lua @@ -18,7 +18,7 @@ local function default_log_path() vim.loop.fs_mkdir(path, 0x1ff) -- 0777 end - return vim.fs.joinpath(path, "/dep.log") + return vim.fs.normalize(path).."/dep.log" end --- attempt to format a string diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 918d5c1..17f87b6 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -331,7 +331,7 @@ end --- set the base directory for packages ---@param _base_dir string base directory function package.set_base_dir(_base_dir) - base_dir = _base_dir + base_dir = vim.fs.normalize(_base_dir) end --- get the base directory for packages diff --git a/lua/lazy/utils.lua b/lua/lazy/utils.lua index 8dc960e..2551926 100644 --- a/lua/lazy/utils.lua +++ b/lua/lazy/utils.lua @@ -54,6 +54,7 @@ function lazy:auto(event, opts) self:cleanup() end + -- create the auto command and save it table.insert(self.auto_ids, vim.api.nvim_create_autocmd(event, opts)) end @@ -63,12 +64,21 @@ end ---@param opts vim.keymap.set.Opts? options function lazy:keymap(mode, bind, opts) opts = opts or {} + + -- move the rerun arg to a seperate variable because keymap.set doesn't like + -- options it doesn't know of + local rerun = opts['rerun'] or true + opts['rerun'] = nil + vim.keymap.set(mode, bind, opts['callback'] or function() + -- register keymap unload self:cleanup() - -- register keymap unload - local keys = vim.api.nvim_replace_termcodes(bind, true, false, true) - vim.api.nvim_feedkeys(keys, mode, false) + -- call the keymap after the user has mapped it + if rerun then + local keys = vim.api.nvim_replace_termcodes(bind, true, false, true) + vim.api.nvim_input(keys) + end end, opts) table.insert(self.keybind_ids, { ['mode'] = mode, ['bind'] = bind }) @@ -77,16 +87,16 @@ end --- cleanup all the callbacks, and load the plugin function lazy:cleanup() -- cleanup user commands - for _, v in pairs(self.command_ids) do - vim.api.nvim_del_user_command(v) + for _, command_id in pairs(self.command_ids) do + vim.api.nvim_del_user_command(command_id) end -- cleanup auto commands - for _, v in pairs(self.auto_ids) do - vim.api.nvim_del_autocmd(v) + for _, auto_id in pairs(self.auto_ids) do + vim.api.nvim_del_autocmd(auto_id) end -- cleanup keymaps - for _, v in pairs(self.keybind_ids) do - vim.keymap.del(v['mode'], v['bind'], {}) + for _, keybind_id in pairs(self.keybind_ids) do + vim.keymap.del(keybind_id.mode, keybind_id.bind, {}) end -- load the plugin self:load() From 452414cafb926d6fcd7fe1a41fdbffbf6e2ce99b Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 24 Apr 2025 14:40:13 -0500 Subject: [PATCH 08/74] update the README with more relevant information --- README.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 3c73386..f7865f9 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,13 @@ liking. 2. `declarative` - packages are declared using simple Lua tables. 3. `correct` - packages are always loaded in a correct and consistent order. -See also squibid's [neovim-configs][11] for an example of how dep can be used in +See also squibid's [neovim-configs][10] for an example of how dep can be used in practice. ## Requirements -- [Neovim][2] 0.6+ -> Needs checking not sure which version this requires -- [Git][6] +- [Neovim][2] 0.8+ +- [Git][6] 2.13+ ## Setup @@ -28,11 +27,11 @@ practice. ```lua -- ~/.config/nvim/lua/bootstrap.lua: --- automatically install `chiyadev/dep` on startup +-- automatically install `squibid/dep` on startup local path = vim.fn.stdpath("data") .. "/site/pack/deps/opt/dep" if vim.fn.empty(vim.fn.glob(path)) > 0 then - vim.fn.system({ "git", "clone", "--depth=1", "https://github.com/chiyadev/dep", path }) + vim.fn.system({ "git", "clone", "--depth=1", "https://git.squi.bid/dep", path }) end vim.cmd("packadd dep") @@ -65,16 +64,16 @@ A package must be declared in the following format. -- This is the only required field; all other fields are optional. "user/package", - -- [function] Code to run after the package is loaded into neovim. - load = function() - require "package".setup(...) - end, - -- [function] Code to run before the package is loaded into neovim. setup = function() vim.g.package_config = ... end, + -- [function] Code to run after the package is loaded into neovim. + load = function() + require "package".setup(...) + end, + -- [function] Code to run after the package is installed or updated. config = function() os.execute(...) @@ -421,5 +420,4 @@ dep is licensed under the [MIT License](LICENSE). [7]: https://github.com/nvim-telescope/telescope.nvim [8]: https://github.com/tpope/vim-fugitive [9]: https://GitHub.com/chiyadev/dep/issues -[10]: https://github.com/chiyadev/dep/graphs/contributors -[11]: https://git.squi.bid/nvim +[10]: https://git.squi.bid/nvim From 3b7963ab0ab06d0a5bd21226f545af2f5f97749c Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 16:14:28 -0500 Subject: [PATCH 09/74] add helper file to deal with aliases --- lua/dep.lua | 7 ++++--- lua/dep/helpers.lua | 4 ++++ lua/dep/log.lua | 12 +++++++----- 3 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 lua/dep/helpers.lua diff --git a/lua/dep.lua b/lua/dep.lua index 098eff4..e876401 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -1,6 +1,7 @@ local logger = require('dep.log') local git = require('dep.git') local packager = require('dep.package') +local h = require('dep.helpers') -- all functions for convenience local M = {} @@ -45,7 +46,7 @@ end --- clean out old packages function M.clean() - vim.loop.fs_scandir( + h.uv.fs_scandir( packager.get_base_dir(), vim.schedule_wrap(function(err, handle) if err then @@ -54,7 +55,7 @@ function M.clean() local queue = {} while handle do - local name = vim.loop.fs_scandir_next(handle) + local name = h.uv.fs_scandir_next(handle) if name then queue[name] = packager.get_base_dir()..name else @@ -221,7 +222,7 @@ return function(opts) vim.opt_local.readonly = true -- make the log auto update while it's open - local w = vim.uv.new_fs_event() + local w = h.uv.new_fs_event() local function watch_file(fname) local fullpath = vim.api.nvim_call_function( 'fnamemodify', { fname, ':p' }) diff --git a/lua/dep/helpers.lua b/lua/dep/helpers.lua new file mode 100644 index 0000000..7bd9273 --- /dev/null +++ b/lua/dep/helpers.lua @@ -0,0 +1,4 @@ +return { + -- vim.loop was depricated in nvim 0.10 + uv = vim.uv or vim.loop +} diff --git a/lua/dep/log.lua b/lua/dep/log.lua index a127d77..f045250 100644 --- a/lua/dep/log.lua +++ b/lua/dep/log.lua @@ -1,3 +1,5 @@ +local h = require('dep.helpers') + local logger = {} logger.stage_colors = { @@ -14,8 +16,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 vim.loop.fs_stat(path) then - vim.loop.fs_mkdir(path, 0x1ff) -- 0777 + if not h.uv.fs_stat(path) then + h.uv.fs_mkdir(path, 0x1ff) -- 0777 end return vim.fs.normalize(path).."/dep.log" @@ -37,8 +39,8 @@ function logger:setup(path) logger.path = path or default_log_path() local pipe - logger.handle = assert(vim.loop.fs_open(logger.path, "w", 0x1a4)) -- 0644 - pipe = vim.loop.new_pipe() + logger.handle = assert(h.uv.fs_open(logger.path, "w", 0x1a4)) -- 0644 + pipe = h.uv.new_pipe() pipe:open(logger.handle) return pipe @@ -90,7 +92,7 @@ function logger:cleanup(pipe, handle) pipe = nil end if handle then - vim.loop.fs_close(logger.handle) + h.uv.fs_close(logger.handle) handle = nil end end From 125d83ccf9c9d390ecde624d5718821f91e4b0e3 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 16:20:23 -0500 Subject: [PATCH 10/74] add some logging to the lazy utils --- lua/lazy/utils.lua | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lua/lazy/utils.lua b/lua/lazy/utils.lua index 2551926..4f3b5d2 100644 --- a/lua/lazy/utils.lua +++ b/lua/lazy/utils.lua @@ -1,8 +1,10 @@ +local logger = require('dep.log') + ---@class lazy ----@field load function ----@field command_ids table ----@field auto_ids table ----@field keybind_ids table +---@field load function the function to load the plugin +---@field command_ids table the commands that have been registered +---@field auto_ids table the auto commands that have been registered +---@field keybind_ids table the keybinds that have been registered local lazy = {} --- create a new instance of lazy @@ -88,15 +90,24 @@ end function lazy:cleanup() -- cleanup user commands for _, command_id in pairs(self.command_ids) do - vim.api.nvim_del_user_command(command_id) + local ok, err = pcall(vim.api.nvim_del_user_command, command_id) + if not ok then + logger:log("lazy", err or "failed to delete user command") + end end -- cleanup auto commands for _, auto_id in pairs(self.auto_ids) do - vim.api.nvim_del_autocmd(auto_id) + local ok, err = pcall(vim.api.nvim_del_autocmd, auto_id) + if not ok then + logger:log("lazy", err or "failed to delete auto command") + end end -- cleanup keymaps for _, keybind_id in pairs(self.keybind_ids) do - vim.keymap.del(keybind_id.mode, keybind_id.bind, {}) + local ok, err = pcall(vim.keymap.del, keybind_id.mode, keybind_id.bind, {}) + if not ok then + logger:log("lazy", err or "failed to delete keymap") + end end -- load the plugin self:load() From 6dd68240ac8c33f6142ff5008730fca3299159cd Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 16:20:45 -0500 Subject: [PATCH 11/74] update README --- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f7865f9..24e5728 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ liking. 2. `declarative` - packages are declared using simple Lua tables. 3. `correct` - packages are always loaded in a correct and consistent order. +In addition to the above dep has been built to be completely in control of you, +the user. With the help of lazy loading you can choose when your plugin loads +down to the finest detail (examples may be found below). + See also squibid's [neovim-configs][10] for an example of how dep can be used in practice. @@ -291,6 +295,60 @@ require "dep" { } ``` +## Lazy loading + +Imagine you're using [telescope.nvim][7] and you need to pull it up with a keybind, +but you don't want to have it load before that moment. With lazy loading you may +choose to only load it when needed using the built in lazy utils which are made +available to you as soon as you start using the lazy option. + +```lua +require "dep" { + { "nvim-telescope/telescope.nvim", + lazy = function(load) + load:keymap("n", "f") + end, + load = function() + require("telescope").setup {} + vim.keymap.set("n", "f", require("telescope.builtin").find_files, {}) + end + } +} +``` + +Say you wanted to use [gitsigns.nvim][9], but only wanted to load it when +in a git directory OR when you call the Gitsigns command. With the power of lazy +loading this can be accomplished by simply defining an auto command like so: + +```lua +require "dep" { + { + "lewis6991/gitsigns.nvim", + lazy = function(load) + -- load gitsigns if we're in a git repository + load:auto({ "BufEnter", "BufNew" }, { + callback = function() + local paths = vim.fs.find({ ".git", }, { upward = true }) + if #paths > 0 then + load:cleanup() + end + end + }) + + -- load gitsigns if the user trys to run the command + load:cmd("Gitsigns") + end, + load = function() + require("gitsigns").setup {} + end + } +} + +``` + +If you're in the need of a deeper understanding of how the utils work go check +out `lua/lazy/utils.lua` for the source code. + ## Separating code into modules Suppose you split your `init.lua` into two files `packages/search.lua` and @@ -419,5 +477,5 @@ dep is licensed under the [MIT License](LICENSE). [6]: https://git-scm.com/ [7]: https://github.com/nvim-telescope/telescope.nvim [8]: https://github.com/tpope/vim-fugitive -[9]: https://GitHub.com/chiyadev/dep/issues +[9]: https://github.com/lewis6991/gitsigns.nvim [10]: https://git.squi.bid/nvim From a6bf2a2637337bdc2422a31dbca103ebf0b1293b Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 16:21:16 -0500 Subject: [PATCH 12/74] remove once property from auto commands (they get deleted anyways) --- lua/lazy/utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/lazy/utils.lua b/lua/lazy/utils.lua index 4f3b5d2..ede428d 100644 --- a/lua/lazy/utils.lua +++ b/lua/lazy/utils.lua @@ -51,7 +51,7 @@ end ---@param opts vim.api.keyset.create_autocmd? options function lazy:auto(event, opts) opts = opts or {} - opts['once'] = opts['once'] or true + opts['callback'] = opts['callback'] or function() self:cleanup() end From 2b9498c5fdc11f35a19614da8c63e7f150a66c4a Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 16:22:03 -0500 Subject: [PATCH 13/74] remove stylua.toml --- stylua.toml | 2 -- 1 file changed, 2 deletions(-) delete mode 100755 stylua.toml diff --git a/stylua.toml b/stylua.toml deleted file mode 100755 index 0435f67..0000000 --- a/stylua.toml +++ /dev/null @@ -1,2 +0,0 @@ -indent_type = "Spaces" -indent_width = 2 From 85a0755af77808eac1660738f2a813d398e4dca7 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 16:22:35 -0500 Subject: [PATCH 14/74] add more TODOs to dep.lua --- lua/dep.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lua/dep.lua b/lua/dep.lua index e876401..c187044 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -6,9 +6,15 @@ 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 From 381f473a15c3e60e2c60a7ba49184d0eb39192f7 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 23:14:00 -0500 Subject: [PATCH 15/74] fix directory concatination --- lua/dep.lua | 2 +- lua/dep/package.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index c187044..e47b3a6 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -63,7 +63,7 @@ function M.clean() while handle do local name = h.uv.fs_scandir_next(handle) if name then - queue[name] = packager.get_base_dir()..name + queue[name] = packager.get_base_dir().."/"..name else break end diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 17f87b6..6ce6299 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -251,7 +251,7 @@ function package:new(spec, overrides) o.name = spec.as or o.name or id:match("^[%w-_.]+/([%w-_.]+)$") o.url = spec.url or o.url or ("https://github.com/"..id..".git") o.branch = spec.branch or o.branch - o.dir = base_dir..o.name + o.dir = base_dir.."/"..o.name o.commit = spec.commit o.pin = overrides.pin or spec.pin or o.pin o.enabled = not overrides.disable and not spec.disable and o.enabled From d030a5c39bef5bdd3398308b1ac8662eb22f742b Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 23:15:57 -0500 Subject: [PATCH 16/74] enable the ability to add local filesystem plugins --- README.md | 4 ++++ lua/dep.lua | 9 ++++++++- lua/dep/fs.lua | 22 ++++++++++++++++++++++ lua/dep/package.lua | 10 ++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 lua/dep/fs.lua diff --git a/README.md b/README.md index 24e5728..bc36712 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,10 @@ A package must be declared in the following format. -- Defaults to "https://github.com/{full_name}.git". url = "https://git.chiya.dev/user/package.git", + -- [string] Overrides the source in which the package is gotten + -- from. This is not set by default. + path = "~/my-local-package/", + -- [string] Overrides the name of the branch to clone. -- Defaults to whatever the remote configured as their HEAD, which is usually "master". branch = "develop", diff --git a/lua/dep.lua b/lua/dep.lua index e47b3a6..4f05655 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -1,5 +1,6 @@ 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') @@ -143,7 +144,13 @@ function M.synctree(tree, cb) for _, package in pairs(tree) do local co = coroutine.create(function() - git.sync(package, done) + -- 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 diff --git a/lua/dep/fs.lua b/lua/dep/fs.lua new file mode 100644 index 0000000..9eac8ec --- /dev/null +++ b/lua/dep/fs.lua @@ -0,0 +1,22 @@ +local h = require('dep.helpers') +local logger = require('dep.log') + +local fs = {} + +function fs:sync(package, cb) + if not package.exists then + fs:link(package, cb) + end +end + +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 + +return fs diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 6ce6299..7ee2245 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -17,6 +17,7 @@ local logger = require('dep.log') ---@field as string? overrides the short name of the package which is usually set --- to a substring of all the chars after '/' in spec[1] ---@field url string? the url to the git repository hosting the package +---@field path string? path to local version of plugin, overrides the url ---@field branch string? the branch which the version of the package resides ---@field commit string? the commit which the version of the package resides ---@field disable boolean? if true disables the package from being loaded @@ -43,6 +44,7 @@ local logger = require('dep.log') ---@field perf table performance metrics for the package ---@field name string the name of the package ---@field url string the url of the package +---@field path string? the path of the package which overrides the url ---@field branch string the branch of the package ---@field dir string the directory of the package ---@field commit string the commit of the package @@ -126,6 +128,13 @@ local function check_spec(spec) end end + if spec.path ~= nil then -- spec.path + if type(spec.path) ~= "string" then + logger:log("spec", "spec.path must be a string in %s", spec[1]) + return false + end + end + if spec.branch ~= nil then -- spec.branch if type(spec.branch) ~= "string" then logger:log("spec", "spec.branch must be a string in %s", spec[1]) @@ -250,6 +259,7 @@ function package:new(spec, overrides) o.name = spec.as or o.name or id:match("^[%w-_.]+/([%w-_.]+)$") 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.branch = spec.branch or o.branch o.dir = base_dir.."/"..o.name o.commit = spec.commit From a28fd8f2e600872bd5a6db7ed881fc8e5af9eeef Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 23:18:49 -0500 Subject: [PATCH 17/74] remove useless string.format(s) --- lua/dep.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index 4f05655..d5c1d1b 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -57,7 +57,7 @@ function M.clean() packager.get_base_dir(), vim.schedule_wrap(function(err, handle) if err then - logger:log("error", string.format("failed to clean; reason: %s", err)) + logger:log("error", "failed to clean; reason: %s", err) else local queue = {} @@ -79,9 +79,9 @@ function M.clean() local co = coroutine.create(function() local ok = vim.fn.delete(dir, "rf") if ok then - logger:log("clean", string.format("deleted %s", name)) + logger:log("clean", "deleted %s", name) else - logger:log("error", string.format("failed to delete %s", name)) + logger:log("error", "failed to delete %s", name) end end) coroutine.resume(co) From dfb1820a8e1f67d46e2a577d3304a73693a3ae83 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 23:19:37 -0500 Subject: [PATCH 18/74] remove todo for loading local plugins --- lua/dep/package.lua | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 7ee2245..1ee712c 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -1,13 +1,5 @@ local logger = require('dep.log') --- TODO: allow specificying a path to use instead of cloning --- like so: @field path string? path to override other url --- I might have to deal with removing dead links and what not --- --- I think it would be best to link the desired directory to the --- neovim package directory, but idk if that will be easy enough --- we'll see - ---@class spec ---@field [1] string id ---@field setup function? code to run before the package is loaded From 2ccbb7ea74dfdda828adf27ccbc13769b884f226 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 25 Apr 2025 23:58:33 -0500 Subject: [PATCH 19/74] add stricter checking on urls and paths, and make sure that if one package fails to load other may continue to load --- lua/dep.lua | 10 ++++------ lua/dep/package.lua | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index d5c1d1b..2a90ad1 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -42,12 +42,10 @@ function M.registertree(speclist, overrides) disable = over.disable or spec.disable } - local ok = packager:new(spec, overrides) - - -- if erroring print out the spec and the error - if not ok then - error(string.format("(spec=%s)", vim.inspect(spec))) - end + -- 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 diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 1ee712c..9112cbc 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -55,6 +55,17 @@ local root ---@type package[] local packages = {} +--- check if a string seems to be a url +---@param url string the "url" to check +---@return boolean is_url +local function is_url(url) + if url:sub(1, 8) == "https://" or + url:sub(1, 7) == "http://" then + return true + end + return false +end + --- check a spec to see if it's correct ---@param spec spec|string the specification to check ---@return spec|false spec if the spec is ok or false @@ -117,6 +128,10 @@ local function check_spec(spec) if type(spec.url) ~= "string" then logger:log("spec", "spec.url must be a string in %s", spec[1]) return false + elseif not is_url(spec.url) then -- more strict checking on urls + logger:log("spec", "spec.url must be a properly formatted url in %s", + spec[1]) + return false end end @@ -124,6 +139,9 @@ local function check_spec(spec) if type(spec.path) ~= "string" then logger:log("spec", "spec.path must be a string in %s", spec[1]) return false + elseif not vim.fn.isdirectory(spec.path) then + logger:log("spec", "spec.path must be a valid directory in %s", spec[1]) + return false end end From a3a36522945b6a56b4a81808856d38060977b99a Mon Sep 17 00:00:00 2001 From: Squibid Date: Sat, 26 Apr 2025 00:05:16 -0500 Subject: [PATCH 20/74] move clean() to the fs file --- lua/dep.lua | 44 ++------------------------------------------ lua/dep/fs.lua | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index 2a90ad1..41ef4ce 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -49,46 +49,6 @@ function M.registertree(speclist, 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) @@ -131,7 +91,7 @@ function M.synctree(tree, cb) logger:log("update", "synchronized %s %s", #tree, #tree == 1 and "package" or "packages") end - M.clean() + fs:clean(packager) M.reload() if cb then @@ -257,7 +217,7 @@ return function(opts) vim.api.nvim_create_user_command("DepClean", function() -- clean AND reload to make sure that all old packages are gone - M.clean() + fs:clean(packager) M.reload() end, {}) diff --git a/lua/dep/fs.lua b/lua/dep/fs.lua index 9eac8ec..ac05f74 100644 --- a/lua/dep/fs.lua +++ b/lua/dep/fs.lua @@ -19,4 +19,45 @@ function fs:link(package, cb) 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 then + queue[name] = package.get_base_dir().."/"..name + else + break + end + end + + -- keep packages that still exist + for _, pkg in pairs(package.get_packages()) do + queue[pkg.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 + return fs From a0bfaefe7c2abb636fe3862a27468097670827aa Mon Sep 17 00:00:00 2001 From: Squibid Date: Mon, 28 Apr 2025 14:34:51 -0500 Subject: [PATCH 21/74] add support for modules again --- lua/dep.lua | 29 +++++++++++++++++++++++++++-- lua/dep/package.lua | 8 ++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index 41ef4ce..8f635dd 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -27,14 +27,14 @@ function M.benchmark(name, code, ...) end --- recurse over all packages and register them ----@param speclist spec[] table of specs +---@param speclist speclist 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 + for _, spec in ipairs(speclist) do -- make sure the overrides override and take into account the packages spec ---@diagnostic disable-next-line: missing-fields over = { @@ -47,6 +47,31 @@ function M.registertree(speclist, overrides) -- keep working plugins from loading because an unrelated one doesn't load. packager:new(spec, overrides) end + + if speclist.modules then + for _, module in ipairs(speclist.modules) do + local name = "" + + if type(module) == "string" then + if speclist.modules.prefix then + if speclist.modules.prefix:sub(#speclist.modules.prefix) ~= "." and + module:sub(1, 2) ~= "." then + module = "."..module + end + module = speclist.modules.prefix..module + end + + name, module = module, require(module) + end + + name = module.name or name + + local ok, err = pcall(M.registertree, module, overrides) + if not ok then + error(string.format("%s <- %s", err, name)) + end + end + end end --- reload all packages in package table spec diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 9112cbc..e84fdf2 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -1,5 +1,13 @@ local logger = require('dep.log') +---@class modules +---@field prefix string prefix to prepend to the modules +---@field [integer] string list of all modules to load + +---@class speclist +---@field modules modules a list of modules +---@field [integer] spec a spec + ---@class spec ---@field [1] string id ---@field setup function? code to run before the package is loaded From 3013d714e040797eb757f970ea17707a3e347da3 Mon Sep 17 00:00:00 2001 From: Squibid Date: Mon, 28 Apr 2025 14:46:05 -0500 Subject: [PATCH 22/74] replace pairs with ipairs where necessary --- lua/dep.lua | 6 +++--- lua/dep/fs.lua | 2 +- lua/dep/package.lua | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index 8f635dd..033b14c 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -125,7 +125,7 @@ function M.synctree(tree, cb) end end - for _, package in pairs(tree) do + for _, package in ipairs(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 @@ -170,7 +170,7 @@ return function(opts) M.registertree(opts) -- sort package dependencies - for _, package in pairs(packager.get_packages()) do + for _, package in ipairs(packager.get_packages()) do table.sort(package.requirements, comp) table.sort(package.dependents, comp) end @@ -198,7 +198,7 @@ return function(opts) -- get all package that need syncing local targets = {} - for _, package in pairs(packager.get_packages()) do + for _, package in ipairs(packager.get_packages()) do if shouldsync(package) then table.insert(targets, package) end diff --git a/lua/dep/fs.lua b/lua/dep/fs.lua index ac05f74..c19f858 100644 --- a/lua/dep/fs.lua +++ b/lua/dep/fs.lua @@ -40,7 +40,7 @@ function fs:clean(package) end -- keep packages that still exist - for _, pkg in pairs(package.get_packages()) do + for _, pkg in ipairs(package.get_packages()) do queue[pkg.name] = nil end diff --git a/lua/dep/package.lua b/lua/dep/package.lua index e84fdf2..83b8768 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -315,7 +315,7 @@ function package:new(spec, overrides) if spec.reqs then ---it is the correct type as asserted in check_spec() ---@diagnostic disable-next-line: param-type-mismatch - for _, req in pairs(spec.reqs) do + for _, req in ipairs(spec.reqs) do local pkg = package:new(req) if type(pkg) == "table" then o:link_dependency(pkg, o) @@ -327,7 +327,7 @@ function package:new(spec, overrides) if spec.deps then ---it is the correct type as asserted in check_spec() ---@diagnostic disable-next-line: param-type-mismatch - for _, dep in pairs(spec.deps) do + for _, dep in ipairs(spec.deps) do local pkg = package:new(dep) if not pkg then return false @@ -425,7 +425,7 @@ function package:ensureadded(force) ---@param pkg package local function loadpkg(pkg) -- make sure to load the dependencies first - for _, p in pairs(pkg.requirements) do + for _, p in ipairs(pkg.requirements) do if not p.loaded then p:ensureadded(true) end @@ -506,7 +506,7 @@ function package:loadtree(force) -- if the package isn't lazy check that it's requirements are loaded if not self.lazy then - for _, requirement in pairs(self.requirements) do + for _, requirement in ipairs(self.requirements) do if not requirement.loaded and not requirement.lazy then return false end @@ -526,7 +526,7 @@ function package:loadtree(force) self.subtree_loaded = true -- make sure the dependants are loaded - for _, dependant in pairs(self.dependents) do + for _, dependant in ipairs(self.dependents) do self.subtree_loaded = dependant:loadtree(force) and self.subtree_loaded end @@ -536,12 +536,12 @@ end --- unconfigure a packages tree function package:unconfiguretree() -- unconfigure requirements - for _, requirement in pairs(self.requirements) do + for _, requirement in ipairs(self.requirements) do requirement.subtree_loaded = false end -- unconfigure dependents - for _, dependant in pairs(self.dependents) do + for _, dependant in ipairs(self.dependents) do dependant.loaded = false dependant.added = false dependant.configured = false @@ -602,7 +602,7 @@ function package.findcycle(pkgs) end -- actually check the cycle - for _, pkg in pairs(pkgs) do + for _, pkg in ipairs(pkgs) do if not indexes[package.id] then local cycle = connect(pkg) if cycle then From d6c37cf3640b64b9db72efd039e70503e225f629 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 6 May 2025 17:12:43 -0500 Subject: [PATCH 23/74] refactor: extract spec --- lua/dep/package.lua | 177 +----------------------------------------- lua/dep/spec.lua | 182 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 174 deletions(-) create mode 100644 lua/dep/spec.lua diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 83b8768..d1a1161 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -1,29 +1,5 @@ local logger = require('dep.log') - ----@class modules ----@field prefix string prefix to prepend to the modules ----@field [integer] string list of all modules to load - ----@class speclist ----@field modules modules a list of modules ----@field [integer] spec a spec - ----@class spec ----@field [1] string id ----@field setup function? code to run before the package is loaded ----@field load function? code to run after the package is loaded ----@field config function? code to run after the package is installed/updated ----@field lazy function? code to run which determines when the package is loaded ----@field as string? overrides the short name of the package which is usually set ---- to a substring of all the chars after '/' in spec[1] ----@field url string? the url to the git repository hosting the package ----@field path string? path to local version of plugin, overrides the url ----@field branch string? the branch which the version of the package resides ----@field commit string? the commit which the version of the package resides ----@field disable boolean? if true disables the package from being loaded ----@field pin boolean? if true disables the package from being installed/updated ----@field reqs spec|spec[]|string? packages that this package requires ----@field deps spec|spec[]|string? packages that depend on this package +local spec_man = require("dep.spec") ---@class package ---@field id string id of the package @@ -63,153 +39,6 @@ local root ---@type package[] local packages = {} ---- check if a string seems to be a url ----@param url string the "url" to check ----@return boolean is_url -local function is_url(url) - if url:sub(1, 8) == "https://" or - url:sub(1, 7) == "http://" then - return true - end - return false -end - ---- check a spec to see if it's correct ----@param spec spec|string the specification to check ----@return spec|false spec if the spec is ok or false -local function check_spec(spec) - -- make sure spec is a table - if type(spec) == "string" then - spec = { spec } - end - - -- make sure all the data is correct - do -- spec[1] - if type(spec[1]) ~= "string" then - logger:log("spec", "spec[1] must be a string") - return false - end - - local name = spec[1]:match("^[%w-_.]+/([%w-_.]+)$") - if not name then - logger:log("spec", 'invalid name "%s"; must be in the format "user/package"', spec[1]) - return false - end - end - - if spec.setup ~= nil then -- spec.setup - if type(spec.setup) ~= "function" then - logger:log("spec", "spec.setup must be a function in %s", spec[1]) - return false - end - end - - if spec.load ~= nil then -- spec.load - if type(spec.load) ~= "function" then - logger:log("spec", "spec.load must be a function in %s", spec[1]) - return false - end - end - - if spec.config ~= nil then -- spec.config - if type(spec.config) ~= "function" then - logger:log("spec", "spec.config must be a function in %s", spec[1]) - return false - end - end - - if spec.lazy ~= nil then -- spec.lazy - if type(spec.lazy) ~= "function" then - logger:log("spec", "spec.lazy must be a function in %s", spec[1]) - return false - end - end - - if spec.as ~= nil then -- spec.as - if type(spec.as) ~= "string" then - logger:log("spec", "spec.as must be a string in %s", spec[1]) - return false - end - end - - if spec.url ~= nil then -- spec.url - if type(spec.url) ~= "string" then - logger:log("spec", "spec.url must be a string in %s", spec[1]) - return false - elseif not is_url(spec.url) then -- more strict checking on urls - logger:log("spec", "spec.url must be a properly formatted url in %s", - spec[1]) - return false - end - end - - if spec.path ~= nil then -- spec.path - if type(spec.path) ~= "string" then - logger:log("spec", "spec.path must be a string in %s", spec[1]) - return false - elseif not vim.fn.isdirectory(spec.path) then - logger:log("spec", "spec.path must be a valid directory in %s", spec[1]) - return false - end - end - - if spec.branch ~= nil then -- spec.branch - if type(spec.branch) ~= "string" then - logger:log("spec", "spec.branch must be a string in %s", spec[1]) - return false - end - end - - if spec.commit ~= nil then -- spec.commit - if type(spec.commit) ~= "string" then - logger:log("spec", "spec.commit must be a string in %s", spec[1]) - return false - end - end - - if spec.disable ~= nil then -- spec.disable - if type(spec.disable) ~= "boolean" then - logger:log("spec", "spec.disable must be a boolean in %s", spec[1]) - return false - end - end - - if spec.pin ~= nil then -- spec.pin - if type(spec.pin) ~= "boolean" then - logger:log("spec", "spec.pin must be a boolean in %s", spec[1]) - return false - end - end - - if spec.reqs ~= nil then -- spec.reqs - local is = type(spec.reqs) - if is ~= "table" and is ~= "string" then - logger:log("spec", "spec.reqs must be a table or a string in %s", spec[1]) - return false - end - - -- turn an id into a spec - if (is == "string") then - spec.reqs = { spec.reqs } - end - end - - if spec.deps ~= nil then -- spec.deps - local is = type(spec.deps) - if is ~= "table" and is ~= "string" then - logger:log("spec", "spec.deps must be a table or a string in %s", spec[1]) - return false - end - - -- turn an id into a spec - if (is == "string") then - spec.deps = { spec.deps } - end - end - - return spec -end - --- tell the parent it has a child and the child it has a parent ---@param parent package? parent package if nil defaults to self ---@param child package child package @@ -236,7 +65,7 @@ function package:new(spec, overrides) overrides = overrides or {} -- ensure that the spec is ok - local new_spec = check_spec(spec) + local new_spec = spec_man.check(spec) if new_spec == false then logger:log("spec", vim.inspect(spec)) logger:log("error", "spec check failed, check DepLog") @@ -275,7 +104,7 @@ function package:new(spec, overrides) packages[id] = o end - o.name = spec.as or o.name or id:match("^[%w-_.]+/([%w-_.]+)$") + 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.branch = spec.branch or o.branch diff --git a/lua/dep/spec.lua b/lua/dep/spec.lua new file mode 100644 index 0000000..1e5b467 --- /dev/null +++ b/lua/dep/spec.lua @@ -0,0 +1,182 @@ +local logger = require("dep.log") + +---@class modules +---@field prefix string prefix to prepend to the modules +---@field [integer] string list of all modules to load + +---@class speclist +---@field modules modules a list of modules +---@field [integer] spec a spec + +---@class spec +---@field [1] string id +---@field setup function? code to run before the package is loaded +---@field load function? code to run after the package is loaded +---@field config function? code to run after the package is installed/updated +---@field lazy function? code to run which determines when the package is loaded +---@field as string? overrides the short name of the package which is usually set +--- to a substring of all the chars after "/" in spec[1] +---@field url string? the url to the git repository hosting the package +---@field path string? path to local version of plugin, overrides the url +---@field branch string? the branch which the version of the package resides +---@field commit string? the commit which the version of the package resides +---@field disable boolean? if true disables the package from being loaded +---@field pin boolean? if true disables the package from being installed/updated +---@field reqs spec|spec[]|string? packages that this package requires +---@field deps spec|spec[]|string? packages that depend on this package +local spec = {} + +--- check if a string seems to be a url +---@param url string the "url" to check +---@return boolean is_url +local function is_url(url) + if url:sub(1, 8) == "https://" or + url:sub(1, 7) == "http://" then + return true + end + return false +end + +--- get the proper name of a spec +---@return string spec.name +function spec:get_name() + return self[1]:match("^[%w-_.]+/([%w-_.]+)$") +end + +--- check a spec to see if it's correct +---@param self table|string spec to check +---@return spec|false spec if the spec is ok or false +function spec:check() + -- make sure spec is a table + if type(self) == "string" then + self = { self } + end + + -- make sure all the data is correct + do -- spec[1] + if type(self[1]) ~= "string" then + logger:log("spec", "spec[1] must be a string") + return false + end + + local name = spec.get_name(self) + if not name then + logger:log("spec", 'invalid name "%s"; must be in the format "user/package"', self[1]) + return false + end + end + + if self.setup ~= nil then -- spec.setup + if type(self.setup) ~= "function" then + logger:log("spec", "spec.setup must be a function in %s", self[1]) + return false + end + end + + if self.load ~= nil then -- spec.load + if type(self.load) ~= "function" then + logger:log("spec", "spec.load must be a function in %s", self[1]) + return false + end + end + + if self.config ~= nil then -- spec.config + if type(self.config) ~= "function" then + logger:log("spec", "spec.config must be a function in %s", self[1]) + return false + end + end + + if self.lazy ~= nil then -- spec.lazy + if type(self.lazy) ~= "function" then + logger:log("spec", "spec.lazy must be a function in %s", self[1]) + return false + end + end + + if self.as ~= nil then -- spec.as + if type(self.as) ~= "string" then + logger:log("spec", "spec.as must be a string in %s", self[1]) + return false + end + end + + if self.url ~= nil then -- spec.url + if type(self.url) ~= "string" then + logger:log("spec", "spec.url must be a string in %s", self[1]) + return false + elseif not is_url(self.url) then -- more strict checking on urls + logger:log("spec", "spec.url must be a properly formatted url in %s", + self[1]) + return false + end + end + + if self.path ~= nil then -- spec.path + if type(self.path) ~= "string" then + logger:log("spec", "spec.path must be a string in %s", self[1]) + return false + elseif not vim.fn.isdirectory(self.path) then + logger:log("spec", "spec.path must be a valid directory in %s", self[1]) + return false + end + end + + if self.branch ~= nil then -- spec.branch + if type(self.branch) ~= "string" then + logger:log("spec", "spec.branch must be a string in %s", self[1]) + return false + end + end + + if self.commit ~= nil then -- spec.commit + if type(self.commit) ~= "string" then + logger:log("spec", "spec.commit must be a string in %s", self[1]) + return false + end + end + + if self.disable ~= nil then -- spec.disable + if type(self.disable) ~= "boolean" then + logger:log("spec", "spec.disable must be a boolean in %s", self[1]) + return false + end + end + + if self.pin ~= nil then -- spec.pin + if type(self.pin) ~= "boolean" then + logger:log("spec", "spec.pin must be a boolean in %s", self[1]) + return false + end + end + + if self.reqs ~= nil then -- spec.reqs + local is = type(self.reqs) + if is ~= "table" and is ~= "string" then + logger:log("spec", "spec.reqs must be a table or a string in %s", self[1]) + return false + end + + -- turn an id into a spec + if (is == "string") then + self.reqs = { self.reqs } + end + end + + if self.deps ~= nil then -- spec.deps + local is = type(self.deps) + if is ~= "table" and is ~= "string" then + logger:log("spec", "spec.deps must be a table or a string in %s", self[1]) + return false + end + + -- turn an id into a spec + if (is == "string") then + self.deps = { self.deps } + end + end + + return self +end + +return spec From 47796d597ead8fe8d7031a4ed3516d6b9cb22667 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 6 May 2025 17:13:12 -0500 Subject: [PATCH 24/74] add comments to fs --- lua/dep/fs.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lua/dep/fs.lua b/lua/dep/fs.lua index c19f858..0c32a6c 100644 --- a/lua/dep/fs.lua +++ b/lua/dep/fs.lua @@ -3,12 +3,18 @@ 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 @@ -44,6 +50,7 @@ function fs:clean(package) 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") From e0814ff507ce86a95dbdc1a3196809237144dfa9 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 6 May 2025 17:13:36 -0500 Subject: [PATCH 25/74] add more logging to fs --- lua/dep/fs.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/dep/fs.lua b/lua/dep/fs.lua index 0c32a6c..fb307e0 100644 --- a/lua/dep/fs.lua +++ b/lua/dep/fs.lua @@ -41,7 +41,9 @@ function fs:clean(package) if name then queue[name] = package.get_base_dir().."/"..name else - break + -- if there's a single error bail out + logger:log("error", "failed to run clean uv.fs_scandir_next failed") + return end end From 30cc9a8d50574f17649bf19554d4b40a5282529a Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 6 May 2025 17:13:58 -0500 Subject: [PATCH 26/74] improve logger --- lua/dep/log.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/dep/log.lua b/lua/dep/log.lua index f045250..36ef6c6 100644 --- a/lua/dep/log.lua +++ b/lua/dep/log.lua @@ -66,7 +66,8 @@ function logger:log(level, message, ...) vim.schedule(function() if not logger.silent then if level == "error" then - vim.api.nvim_echo({ { string.format("[dep] %s", message) } }, true, { err = true }) + vim.api.nvim_echo({ { string.format("[dep] %s", message) } }, true, + { err = true }) elseif logger.stage_colors[level] then vim.api.nvim_echo({ { "[dep]", "Identifier" }, @@ -78,7 +79,8 @@ function logger:log(level, message, ...) -- write to the pipe if it's open if logger.pipe then - logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%T"), source.short_src:gsub('.*%/', ''), source.currentline, message)) + logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%T"), + source.short_src:gsub('.*%/', ''), source.currentline, message)) end end) end From 71b78bfca43f607ca2aaebd27f4bf686c4c9d03b Mon Sep 17 00:00:00 2001 From: Squibid Date: Mon, 23 Jun 2025 00:18:33 -0400 Subject: [PATCH 27/74] the spec no longer fixes itself... modules are more reliable cleanup some typos --- lua/dep.lua | 25 ++++++++++++++++--------- lua/dep/git.lua | 7 +------ lua/dep/package.lua | 20 ++++++++++---------- lua/dep/proc.lua | 3 ++- lua/dep/spec.lua | 35 ++++++++++++++++++++++++++++++----- 5 files changed, 59 insertions(+), 31 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index 033b14c..ad0907a 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -3,6 +3,7 @@ local git = require('dep.git') local fs = require('dep.fs') local packager = require('dep.package') local h = require('dep.helpers') +local spec_man = require("dep.spec") -- all functions for convenience local M = {} @@ -38,14 +39,14 @@ function M.registertree(speclist, overrides) -- 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 + pin = overrides.pin or spec.pin, + disable = overrides.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) + packager:new(spec, over) end if speclist.modules then @@ -63,9 +64,14 @@ function M.registertree(speclist, overrides) name, module = module, require(module) end - name = module.name or name + -- allow a module to be a spec + if spec_man.check(module, true) ~= false then + ---@diagnostic disable-next-line: cast-local-type + module = { module } + end + local ok, err = pcall(M.registertree, module, overrides) if not ok then error(string.format("%s <- %s", err, name)) @@ -125,7 +131,7 @@ function M.synctree(tree, cb) end end - for _, package in ipairs(tree) do + 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 @@ -160,7 +166,8 @@ return function(opts) -- register all packages local root = packager:new({ "squibid/dep", - url = "https://git.squi.bid/dep", + url = "https://git.squi.bid/squibid/dep.git", + branch = "lazy", pin = true }) if not root then @@ -170,7 +177,7 @@ return function(opts) M.registertree(opts) -- sort package dependencies - for _, package in ipairs(packager.get_packages()) do + for _, package in pairs(packager.get_packages()) do table.sort(package.requirements, comp) table.sort(package.dependents, comp) end @@ -183,7 +190,7 @@ return function(opts) end) -- load packages - M.reload() + M.reload(false) --- check if a package should be synced ---@param package table package table spec @@ -198,7 +205,7 @@ return function(opts) -- get all package that need syncing local targets = {} - for _, package in ipairs(packager.get_packages()) do + for _, package in pairs(packager.get_packages()) do if shouldsync(package) then table.insert(targets, package) end diff --git a/lua/dep/git.lua b/lua/dep/git.lua index 23dde0f..beb39b8 100644 --- a/lua/dep/git.lua +++ b/lua/dep/git.lua @@ -46,7 +46,6 @@ end ---@param package package package to install ---@param cb function callback function git.install(package, cb) - if not package.enabled then cb() return @@ -99,13 +98,9 @@ function git.update(package, cb) return end - local function logerr(err) - logger:log("error", "failed to update %s; reason: %s", package.id, err) - end - proc.git_rev_parse(package.dir, "HEAD", function(err, before) if err then - logerr(before) + log_err(before) cb(err) else if package.commit then diff --git a/lua/dep/package.lua b/lua/dep/package.lua index d1a1161..cf0157b 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -65,7 +65,7 @@ function package:new(spec, overrides) overrides = overrides or {} -- ensure that the spec is ok - local new_spec = spec_man.check(spec) + local new_spec = spec_man.check(spec_man.correct_spec(spec)) if new_spec == false then logger:log("spec", vim.inspect(spec)) logger:log("error", "spec check failed, check DepLog") @@ -144,7 +144,7 @@ function package:new(spec, overrides) if spec.reqs then ---it is the correct type as asserted in check_spec() ---@diagnostic disable-next-line: param-type-mismatch - for _, req in ipairs(spec.reqs) do + for _, req in pairs(spec.reqs) do local pkg = package:new(req) if type(pkg) == "table" then o:link_dependency(pkg, o) @@ -156,7 +156,7 @@ function package:new(spec, overrides) if spec.deps then ---it is the correct type as asserted in check_spec() ---@diagnostic disable-next-line: param-type-mismatch - for _, dep in ipairs(spec.deps) do + for _, dep in pairs(spec.deps) do local pkg = package:new(dep) if not pkg then return false @@ -206,7 +206,7 @@ function package.get_root() end --- get the packages in dep ----@return package root +---@return package[] packages ---@nodiscard function package.get_packages() return packages @@ -254,7 +254,7 @@ function package:ensureadded(force) ---@param pkg package local function loadpkg(pkg) -- make sure to load the dependencies first - for _, p in ipairs(pkg.requirements) do + for _, p in pairs(pkg.requirements) do if not p.loaded then p:ensureadded(true) end @@ -335,7 +335,7 @@ function package:loadtree(force) -- if the package isn't lazy check that it's requirements are loaded if not self.lazy then - for _, requirement in ipairs(self.requirements) do + for _, requirement in pairs(self.requirements) do if not requirement.loaded and not requirement.lazy then return false end @@ -355,7 +355,7 @@ function package:loadtree(force) self.subtree_loaded = true -- make sure the dependants are loaded - for _, dependant in ipairs(self.dependents) do + for _, dependant in pairs(self.dependents) do self.subtree_loaded = dependant:loadtree(force) and self.subtree_loaded end @@ -365,12 +365,12 @@ end --- unconfigure a packages tree function package:unconfiguretree() -- unconfigure requirements - for _, requirement in ipairs(self.requirements) do + for _, requirement in pairs(self.requirements) do requirement.subtree_loaded = false end -- unconfigure dependents - for _, dependant in ipairs(self.dependents) do + for _, dependant in pairs(self.dependents) do dependant.loaded = false dependant.added = false dependant.configured = false @@ -431,7 +431,7 @@ function package.findcycle(pkgs) end -- actually check the cycle - for _, pkg in ipairs(pkgs) do + for _, pkg in pairs(pkgs) do if not indexes[package.id] then local cycle = connect(pkg) if cycle then diff --git a/lua/dep/proc.lua b/lua/dep/proc.lua index a23f4cd..14dd9fd 100644 --- a/lua/dep/proc.lua +++ b/lua/dep/proc.lua @@ -122,7 +122,8 @@ function proc.git_resolve_branch(url, branch, cb) end end end - }) + } + ) end return proc diff --git a/lua/dep/spec.lua b/lua/dep/spec.lua index 1e5b467..b5ff240 100644 --- a/lua/dep/spec.lua +++ b/lua/dep/spec.lua @@ -43,13 +43,35 @@ function spec:get_name() return self[1]:match("^[%w-_.]+/([%w-_.]+)$") end ---- check a spec to see if it's correct +--- attempt to correct a spec ---@param self table|string spec to check ----@return spec|false spec if the spec is ok or false -function spec:check() - -- make sure spec is a table +---@return spec spec +function spec:correct_spec() if type(self) == "string" then - self = { self } + return { self } + elseif type(self) == "table" then + repeat + if type(self[1]) ~= "string" then + self = self[1] + end + until type(self[1]) == "string" + end + + return self +end + +-- store the logger temporarily to prevent any logs from being printed when +-- being run in silent mode +local __logger + +--- check a spec to see if it's correct +---@param self table spec to check +---@param silent boolean? should the checker report errors +---@return spec|false spec if the spec is ok or false +function spec:check(silent) + if silent == true then + __logger = logger + logger = { log = function() end } end -- make sure all the data is correct @@ -176,6 +198,9 @@ function spec:check() end end + if silent == true then + logger = __logger + end return self end From eba21a80217e2cdd68e6ca80b3c0a0ec439a8e10 Mon Sep 17 00:00:00 2001 From: Squibid Date: Mon, 23 Jun 2025 05:17:07 -0400 Subject: [PATCH 28/74] do my best to prevent nuking the plugins especially dep --- lua/dep/fs.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lua/dep/fs.lua b/lua/dep/fs.lua index fb307e0..02a799a 100644 --- a/lua/dep/fs.lua +++ b/lua/dep/fs.lua @@ -38,17 +38,23 @@ function fs:clean(package) while handle do local name = h.uv.fs_scandir_next(handle) - if name then + 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 ipairs(package.get_packages()) do + for _, pkg in pairs(package.get_packages()) do queue[pkg.name] = nil end From 6bd6db3c02210c714de6fab8189d26997ac25f2c Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 24 Jun 2025 01:48:30 -0400 Subject: [PATCH 29/74] refine module support and package loading --- lua/dep.lua | 118 ++++++------------------------------- lua/dep/bench.lua | 35 +++++++++++ lua/dep/modules/init.lua | 39 ++++++++++++ lua/dep/modules/module.lua | 55 +++++++++++++++++ lua/dep/package.lua | 48 +++++++++++++++ lua/dep/spec.lua | 6 +- 6 files changed, 200 insertions(+), 101 deletions(-) create mode 100644 lua/dep/bench.lua create mode 100644 lua/dep/modules/init.lua create mode 100644 lua/dep/modules/module.lua diff --git a/lua/dep.lua b/lua/dep.lua index ad0907a..b28cd5c 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -3,107 +3,16 @@ local git = require('dep.git') local fs = require('dep.fs') local packager = require('dep.package') local h = require('dep.helpers') -local spec_man = require("dep.spec") +local modules = require("dep.modules") +local bench = require("dep.bench") -- 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 speclist 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 ipairs(speclist) do - -- make sure the overrides override and take into account the packages spec - ---@diagnostic disable-next-line: missing-fields - over = { - pin = overrides.pin or spec.pin, - disable = overrides.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, over) - end - - if speclist.modules then - for _, module in ipairs(speclist.modules) do - local name = "" - - if type(module) == "string" then - if speclist.modules.prefix then - if speclist.modules.prefix:sub(#speclist.modules.prefix) ~= "." and - module:sub(1, 2) ~= "." then - module = "."..module - end - module = speclist.modules.prefix..module - end - - name, module = module, require(module) - end - name = module.name or name - - -- allow a module to be a spec - if spec_man.check(module, true) ~= false then - ---@diagnostic disable-next-line: cast-local-type - module = { module } - end - - local ok, err = pcall(M.registertree, module, overrides) - if not ok then - error(string.format("%s <- %s", err, name)) - 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 @@ -123,7 +32,9 @@ function M.synctree(tree, cb) end fs:clean(packager) - M.reload() + for _, package in pairs(tree) do + package:reload() + end if cb then cb() @@ -148,6 +59,7 @@ end -- basically the main function of our program return function(opts) logger.pipe = logger:setup() + bench:setup() --- make comparison for table.sort ---@param a package package spec a @@ -162,7 +74,7 @@ return function(opts) 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() + bench.mark("load", function() -- register all packages local root = packager:new({ "squibid/dep", @@ -174,7 +86,12 @@ return function(opts) logger:log("error", "couldn't register root package") return end - M.registertree(opts) + + -- setup all packages and modules + if opts.modules then + modules:setup(opts) + end + packager.register_speclist(opts) -- sort package dependencies for _, package in pairs(packager.get_packages()) do @@ -190,7 +107,9 @@ return function(opts) end) -- load packages - M.reload(false) + for _, package in pairs(packager.get_packages()) do + package:reload() + end --- check if a package should be synced ---@param package table package table spec @@ -244,13 +163,14 @@ return function(opts) end, {}) vim.api.nvim_create_user_command("DepReload", function() - M.reload() + for _, package in pairs(packager.get_packages()) do + 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) - M.reload() end, {}) logger:cleanup() diff --git a/lua/dep/bench.lua b/lua/dep/bench.lua new file mode 100644 index 0000000..86d3aff --- /dev/null +++ b/lua/dep/bench.lua @@ -0,0 +1,35 @@ +-- TODO: actually use this (ideally make a view that shows startuptime and +-- which plugins are currently loaded) +-- performance logging + +---@class bench +---@field perf number[] list of all perfs +local bench = {} +local b + +function bench:setup() + local o = {} + self = {} + self.__index = self + setmetatable(o, self) + + o.perf = {} + o.inited = true + + b = o +end + +--- benchmark a peice of code +---@param name string the name of the benchmark +---@param f function the code to benchmark +---@vararg any args for f +---@return any ret the result of f +function bench.mark(name, f, ...) + local start = os.clock() + local ret = f(...) + b.perf[name] = os.clock() - start + + return ret +end + +return bench diff --git a/lua/dep/modules/init.lua b/lua/dep/modules/init.lua new file mode 100644 index 0000000..d212eb6 --- /dev/null +++ b/lua/dep/modules/init.lua @@ -0,0 +1,39 @@ +local module = require("dep.modules.module") + +---@class modules +---@field modules module[] all modules in dep +local modules = {} + +--- Initialize all the modules +---@param self table? +---@param speclist table +---@param overrides spec? overrides +---@return modules modules manager +---@nodisacard +function modules:setup(speclist, overrides) + overrides = overrides or {} + + local o = {} + self = {} + self.__index = self + setmetatable(o, self) + + -- create a list of modules + o.modules = {} + + -- loop through all modules and initialize them + for _, modpath in ipairs(speclist.modules) do + local mod = module.new( + nil, + modpath, + speclist.modules.prefix, + overrides + ) + + table.insert(o.modules, mod) + end + + return self +end + +return modules diff --git a/lua/dep/modules/module.lua b/lua/dep/modules/module.lua new file mode 100644 index 0000000..f83bc11 --- /dev/null +++ b/lua/dep/modules/module.lua @@ -0,0 +1,55 @@ +local spec_man = require("dep.spec") +local packager = require("dep.package") + +---@class module +---@field name string name of the module +---@field path string path to the module +---@field mod table the module +local module = {} + +--- Initialize a module +---@param self table? +---@param modpath string path to the module +---@param prefix string? the prefix to all modules +---@param overrides spec? a module override +---@return module|false module false on failure to load module +---@nodiscard +function module:new(modpath, prefix, overrides) + local ok, err + local o = {} + self = {} + self.__index = self + setmetatable(o, self) + + o.name = "" + if type(modpath) == "string" then + if prefix ~= nil then + if prefix:sub(#prefix) ~= "." and modpath:sub(1, 2) ~= "." then + modpath = "."..modpath + end + o.path = prefix..modpath + else + o.path = modpath + end + o.name = modpath + ok, o.mod = pcall(require, o.path) + if not ok then + return false + end + end + o.name = o.mod.name or o.name + + -- allow a module to be a spec + if spec_man.check(o.mod, true) ~= false then + o.mod = { o.mod } + end + + ok, err = pcall(packager.register_speclist, o.mod, overrides) + if not ok then + error(string.format("%s <- %s", err, o.name)) + end + + return self +end + +return module diff --git a/lua/dep/package.lua b/lua/dep/package.lua index cf0157b..0ae5753 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -1,5 +1,6 @@ local logger = require('dep.log') local spec_man = require("dep.spec") +local bench = require("dep.bench") ---@class package ---@field id string id of the package @@ -443,4 +444,51 @@ function package.findcycle(pkgs) return false end +--- recurse over all packages and register them +---@param speclist speclist table of specs +---@param overrides spec? a package spec that is used to override options +function package.register_speclist(speclist, overrides) + overrides = overrides or {} + + -- recurse the packages + local over = overrides + for _, spec in ipairs(speclist) do + -- make sure the overrides override and take into account the packages spec + ---@diagnostic disable-next-line: missing-fields + over = { + pin = overrides.pin or spec.pin, + disable = overrides.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. + package:new(spec, over) + end +end + +--- reload the package +---@param self package the package to reload +---@param force boolean? force all packages to load +function package:reload(force) + local reloaded = self:loadtree(force) + + if reloaded then + local ok, err + -- TODO: make a benchmark function + bench.mark("reload", function() + ok, err = pcall(vim.cmd, + [[ + silent! helptags ALL + silent! UpdateRemotePlugins + ]]) + end) + + if not ok then + logger:log("error", + "failed to reload helptags and remote plugins; reason: %s", err) + end + end +end + return package diff --git a/lua/dep/spec.lua b/lua/dep/spec.lua index b5ff240..5934a16 100644 --- a/lua/dep/spec.lua +++ b/lua/dep/spec.lua @@ -1,11 +1,11 @@ local logger = require("dep.log") ----@class modules +---@class specmodules ---@field prefix string prefix to prepend to the modules ---@field [integer] string list of all modules to load ---@class speclist ----@field modules modules a list of modules +---@field modules specmodules a list of modules ---@field [integer] spec a spec ---@class spec @@ -53,6 +53,8 @@ function spec:correct_spec() repeat if type(self[1]) ~= "string" then self = self[1] + elseif self[1] == nil then + break end until type(self[1]) == "string" end From fd1c5d6e79d09c025fe978f423e7730397dc341a Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 24 Jun 2025 03:08:24 -0400 Subject: [PATCH 30/74] make lazy loading better... - add a ft function to make it better when trying to load a plugin on a ft - add shorthands to make it easier to create one load condition --- lua/lazy/short.lua | 43 +++++++++++++++++++++++++++++++++++++++++++ lua/lazy/utils.lua | 10 +++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 lua/lazy/short.lua diff --git a/lua/lazy/short.lua b/lua/lazy/short.lua new file mode 100644 index 0000000..d00fce2 --- /dev/null +++ b/lua/lazy/short.lua @@ -0,0 +1,43 @@ +--- shorthands for when you only need to define one callback +local short = {} + +--- create a usercommand which will trigger the plugin to load +---@param name string the name of the command +---@param opts vim.api.keyset.user_command? options +---@return function callback lazy setup +function short:cmd(name, opts) + return function(load) + load:cmd(name, opts) + end +end + +--- create an auto command which will trigger the plugin to load +---@param event string the event to trigger on +---@param opts vim.api.keyset.create_autocmd? options +---@return function callback lazy setup +function short:auto(event, opts) + return function(load) + load:auto(event, opts) + end +end + +--- create an auto command which will trigger on filetype +---@param filetype string filetype to register the auto on +function short:ft(filetype) + return function(load) + load:ft(filetype) + end +end + +--- create a keybind which will trigger the plugin to load +---@param mode string the mode to trigger in +---@param bind string the binding to use +---@param opts vim.keymap.set.Opts? options +---@return function callback lazy setup +function short:keymap(mode, bind, opts) + return function(load) + load:keymap(mode, bind, opts) + end +end + +return short diff --git a/lua/lazy/utils.lua b/lua/lazy/utils.lua index ede428d..889c9be 100644 --- a/lua/lazy/utils.lua +++ b/lua/lazy/utils.lua @@ -46,7 +46,7 @@ function lazy:cmd(name, opts) table.insert(self.command_ids, name) end ---- user an auto command which will trigger the plugin to load +--- create an auto command which will trigger the plugin to load ---@param event string the event to trigger on ---@param opts vim.api.keyset.create_autocmd? options function lazy:auto(event, opts) @@ -60,6 +60,14 @@ function lazy:auto(event, opts) table.insert(self.auto_ids, vim.api.nvim_create_autocmd(event, opts)) end +--- create an auto command which will trigger on filetype +---@param filetype string filetype to register the auto on +function lazy:ft(filetype) + lazy:auto("FileType", { + pattern = filetype + }) +end + --- create a keybind which will trigger the plugin to load ---@param mode string the mode to trigger in ---@param bind string the binding to use From 561f1d8f8b39105414dd643b9e3b98bcb39f5247 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 24 Jun 2025 17:19:42 -0400 Subject: [PATCH 31/74] add a description field to modules --- lua/dep/modules/module.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/dep/modules/module.lua b/lua/dep/modules/module.lua index f83bc11..930b8b6 100644 --- a/lua/dep/modules/module.lua +++ b/lua/dep/modules/module.lua @@ -3,6 +3,7 @@ local packager = require("dep.package") ---@class module ---@field name string name of the module +---@field desc string description of the module ---@field path string path to the module ---@field mod table the module local module = {} @@ -22,6 +23,7 @@ function module:new(modpath, prefix, overrides) setmetatable(o, self) o.name = "" + o.desc = "" if type(modpath) == "string" then if prefix ~= nil then if prefix:sub(#prefix) ~= "." and modpath:sub(1, 2) ~= "." then @@ -38,6 +40,7 @@ function module:new(modpath, prefix, overrides) end end o.name = o.mod.name or o.name + o.desc = o.mod.desc or o.desc -- allow a module to be a spec if spec_man.check(o.mod, true) ~= false then From 078926063773e3b7214f002b5e00a0d87af7fab1 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 24 Jun 2025 17:20:22 -0400 Subject: [PATCH 32/74] add a known issues section --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index bc36712..8118c7d 100644 --- a/README.md +++ b/README.md @@ -469,6 +469,11 @@ require "dep" { } ``` +## Known Issues + +- Lazy loading nvim-cmp doesn't work as the external sources don't get reconized +by nvim-cmp when it's loaded. + ## License dep is licensed under the [MIT License](LICENSE). From b7218c64c28893e793d8ec19c38fe7e8a3362034 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 24 Jun 2025 17:20:37 -0400 Subject: [PATCH 33/74] make sure the log doesn't get filled with redundant info --- lua/dep/package.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 0ae5753..074f471 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -305,6 +305,11 @@ function package:ensureadded(force) return false end l:set_load(function() + -- ensure that we can't attempt to load a plugin twice via lazy loading + if self.loaded then + return + end + logger:log("lazy", "triggered %d lazy hooks for %s", #self.lazy_load, self.id) loadpkg(self) From 84ac4aef17362f9fbc6bbcdf9333bfebce8da356 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 27 Jun 2025 17:59:15 -0400 Subject: [PATCH 34/74] add better logging to modules --- lua/dep/modules/init.lua | 11 +++++------ lua/dep/modules/module.lua | 5 ++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lua/dep/modules/init.lua b/lua/dep/modules/init.lua index d212eb6..9199898 100644 --- a/lua/dep/modules/init.lua +++ b/lua/dep/modules/init.lua @@ -23,14 +23,13 @@ function modules:setup(speclist, overrides) -- loop through all modules and initialize them for _, modpath in ipairs(speclist.modules) do - local mod = module.new( - nil, - modpath, - speclist.modules.prefix, - overrides - ) + local mod = module.new(nil, modpath, speclist.modules.prefix, overrides) + if not mod then + goto continue + end table.insert(o.modules, mod) + ::continue:: end return self diff --git a/lua/dep/modules/module.lua b/lua/dep/modules/module.lua index 930b8b6..a6cd04d 100644 --- a/lua/dep/modules/module.lua +++ b/lua/dep/modules/module.lua @@ -1,3 +1,4 @@ +local logger = require('dep.log') local spec_man = require("dep.spec") local packager = require("dep.package") @@ -36,6 +37,7 @@ function module:new(modpath, prefix, overrides) o.name = modpath ok, o.mod = pcall(require, o.path) if not ok then + logger:log("error", "failed to load module: %s", vim.inspect(o.mod)) return false end end @@ -49,7 +51,8 @@ function module:new(modpath, prefix, overrides) ok, err = pcall(packager.register_speclist, o.mod, overrides) if not ok then - error(string.format("%s <- %s", err, o.name)) + logger:log("error", "%s <- %s", err, o.name) + return false end return self From 84ee8414f73f3d94573c078098b66011a6940daf Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 27 Jun 2025 18:00:36 -0400 Subject: [PATCH 35/74] add optional parameters to speclist type --- lua/dep/spec.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/dep/spec.lua b/lua/dep/spec.lua index 5934a16..951e654 100644 --- a/lua/dep/spec.lua +++ b/lua/dep/spec.lua @@ -5,7 +5,9 @@ local logger = require("dep.log") ---@field [integer] string list of all modules to load ---@class speclist ----@field modules specmodules a list of modules +---@field modules specmodules? a list of modules +---@field base_dir string? the base directory for all plugins +---@field sync ("new"|"always")? when to sync (defaults to new) ---@field [integer] spec a spec ---@class spec From 92cf3634c9757fc85aea987eb699ee58644f7428 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 27 Jun 2025 18:01:46 -0400 Subject: [PATCH 36/74] move lazy loading helpers inside of the dep folder --- lua/{ => dep}/lazy/short.lua | 0 lua/{ => dep}/lazy/utils.lua | 0 lua/dep/package.lua | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename lua/{ => dep}/lazy/short.lua (100%) rename lua/{ => dep}/lazy/utils.lua (100%) diff --git a/lua/lazy/short.lua b/lua/dep/lazy/short.lua similarity index 100% rename from lua/lazy/short.lua rename to lua/dep/lazy/short.lua diff --git a/lua/lazy/utils.lua b/lua/dep/lazy/utils.lua similarity index 100% rename from lua/lazy/utils.lua rename to lua/dep/lazy/utils.lua diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 074f471..7ef768a 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -299,7 +299,7 @@ function package:ensureadded(force) self.lazied = true for _, load_cond in pairs(self.lazy_load) do -- configure the lazy loader for the user - local l = require('lazy.utils'):new() + local l = require('dep.lazy.utils'):new() if l == true then logger:log("lazy", "failed to get lazy utils") return false From 1623276cb01f4fb465e7bc2a3fc9f896e2796590 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 27 Jun 2025 18:02:50 -0400 Subject: [PATCH 37/74] allow for modules to keep track of the packages they've declared --- lua/dep/modules/module.lua | 4 ++++ lua/dep/package.lua | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lua/dep/modules/module.lua b/lua/dep/modules/module.lua index a6cd04d..7e6d12e 100644 --- a/lua/dep/modules/module.lua +++ b/lua/dep/modules/module.lua @@ -7,6 +7,7 @@ local packager = require("dep.package") ---@field desc string description of the module ---@field path string path to the module ---@field mod table the module +---@field packages package[] all packages registed from the module local module = {} --- Initialize a module @@ -55,6 +56,9 @@ function module:new(modpath, prefix, overrides) return false end + -- ensure that the module contains the packages that it's created + self.packages = err + return self end diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 7ef768a..e9345f8 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -452,8 +452,10 @@ end --- recurse over all packages and register them ---@param speclist speclist table of specs ---@param overrides spec? a package spec that is used to override options +---@return package[] packages function package.register_speclist(speclist, overrides) overrides = overrides or {} + local packages_from_speclist = {} -- recurse the packages local over = overrides @@ -468,8 +470,19 @@ function package.register_speclist(speclist, overrides) -- 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. - package:new(spec, over) + local pkg = package:new(spec, over) + if not pkg then + goto continue + end + + -- we store all the packages in a table so that the caller may keep track of + -- their packages, this is not required and therefore the return value may + -- be discarded + table.insert(packages_from_speclist, pkg) + ::continue:: end + + return packages_from_speclist end --- reload the package From 07df092fc6e746b281e8724911205da6779f5949 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 27 Jun 2025 18:03:16 -0400 Subject: [PATCH 38/74] add type annotation for the main function --- lua/dep.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/dep.lua b/lua/dep.lua index b28cd5c..e439167 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -57,6 +57,7 @@ function M.synctree(tree, cb) end -- basically the main function of our program +---@param opts speclist return function(opts) logger.pipe = logger:setup() bench:setup() From ac14e3d5bb25633245973fc6fd3b28df5e3e1b47 Mon Sep 17 00:00:00 2001 From: Squibid Date: Sat, 28 Jun 2025 15:21:01 -0400 Subject: [PATCH 39/74] dep can now search for modules as long as it's provided with a prefix --- lua/dep.lua | 3 +- lua/dep/modules/init.lua | 64 ++++++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index e439167..162aef0 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -59,6 +59,7 @@ end -- basically the main function of our program ---@param opts speclist return function(opts) + M.config_path = debug.getinfo(2, "S").source:sub(2) logger.pipe = logger:setup() bench:setup() @@ -90,7 +91,7 @@ return function(opts) -- setup all packages and modules if opts.modules then - modules:setup(opts) + modules:setup(opts, nil, M.config_path) end packager.register_speclist(opts) diff --git a/lua/dep/modules/init.lua b/lua/dep/modules/init.lua index 9199898..0902fc5 100644 --- a/lua/dep/modules/init.lua +++ b/lua/dep/modules/init.lua @@ -1,3 +1,5 @@ +local h = require('dep.helpers') +local logger = require('dep.log') local module = require("dep.modules.module") ---@class modules @@ -10,7 +12,7 @@ local modules = {} ---@param overrides spec? overrides ---@return modules modules manager ---@nodisacard -function modules:setup(speclist, overrides) +function modules:setup(speclist, overrides, config_path) overrides = overrides or {} local o = {} @@ -21,15 +23,59 @@ function modules:setup(speclist, overrides) -- create a list of modules o.modules = {} - -- loop through all modules and initialize them - for _, modpath in ipairs(speclist.modules) do - local mod = module.new(nil, modpath, speclist.modules.prefix, overrides) - if not mod then - goto continue - end + if (speclist.modules[1] == "*" or #speclist.modules == 0) + and speclist.modules.prefix then - table.insert(o.modules, mod) - ::continue:: + local path = vim.fs.joinpath(config_path:gsub("[^/]*$", ""), + "lua", (speclist.modules.prefix:gsub("%.", "/")) + ) + + h.uv.fs_scandir(path, function(err, handle) + if err then + logger:log("error", "failed to load modules; reason: %s", err) + else + while handle do + local name = h.uv.fs_scandir_next(handle) + if name then + -- skip non-lua files + if name:sub(#name - 3) ~= ".lua" then + goto continue + end + + -- remove the file extension from the name so that lua doesn't fail + -- when attempting to load it + name = name:sub(0, #name - 4) + + -- attempt to load the module + local mod = module.new(nil, name, speclist.modules.prefix, overrides) + if not mod then + goto continue + end + + table.insert(o.modules, mod) + ::continue:: + elseif name == nil then + -- no more entries + break + else + -- if there's a single error bail out + logger:log("error", "failed to run clean uv.fs_scandir_next failed") + return + end + end + end + end) + else + -- loop through all modules and initialize them + for _, modpath in ipairs(speclist.modules) do + local mod = module.new(nil, modpath, speclist.modules.prefix, overrides) + if not mod then + goto continue + end + + table.insert(o.modules, mod) + ::continue:: + end end return self From b60e6a3934fe60f42ebcf31d8b9f8297677dd197 Mon Sep 17 00:00:00 2001 From: Squibid Date: Sun, 29 Jun 2025 18:12:30 -0400 Subject: [PATCH 40/74] allow lazy loading colorschemes --- lua/dep.lua | 2 + lua/dep/lazy/init.lua | 37 ++++++++++++ lua/dep/lazy/{utils.lua => loader/init.lua} | 41 +++++++------- lua/dep/lazy/loader/short.lua | 62 +++++++++++++++++++++ lua/dep/lazy/short.lua | 43 -------------- lua/dep/package.lua | 14 +++-- lua/dep/spec.lua | 7 ++- 7 files changed, 135 insertions(+), 71 deletions(-) create mode 100644 lua/dep/lazy/init.lua rename lua/dep/lazy/{utils.lua => loader/init.lua} (81%) create mode 100644 lua/dep/lazy/loader/short.lua delete mode 100644 lua/dep/lazy/short.lua diff --git a/lua/dep.lua b/lua/dep.lua index 162aef0..da9174a 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -5,6 +5,7 @@ local packager = require('dep.package') local h = require('dep.helpers') local modules = require("dep.modules") local bench = require("dep.bench") +local lazy = require("dep.lazy") -- all functions for convenience local M = {} @@ -62,6 +63,7 @@ return function(opts) M.config_path = debug.getinfo(2, "S").source:sub(2) logger.pipe = logger:setup() bench:setup() + lazy.setup() --- make comparison for table.sort ---@param a package package spec a diff --git a/lua/dep/lazy/init.lua b/lua/dep/lazy/init.lua new file mode 100644 index 0000000..3ca604e --- /dev/null +++ b/lua/dep/lazy/init.lua @@ -0,0 +1,37 @@ +local h = require("dep.helpers") +local packager = require("dep.package") + +---@class lazy +local lazy = {} + +-- since this is already a ridiculous "optimization" we should really be caching +-- the results of this for when the user keeps on loading the colorscheme that +-- they've lazy loaded, that way we speed up the lazy loading process +local function colorscheme() + -- if a colorscheme doesn't exist attempt load it prior to it being set + vim.api.nvim_create_autocmd("ColorschemePre", { + pattern = vim.fn.getcompletion("", "color"), + callback = function(e) + for _, p in pairs(packager.get_packages()) do + 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 + p:ensureadded(true) + -- break out of here, we've loaded the colorscheme + return + end + end + end + end + end + }) +end + +--- setup all lazy handlers +function lazy.setup() + -- start the colorscheme watcher + colorscheme() +end + +return lazy diff --git a/lua/dep/lazy/utils.lua b/lua/dep/lazy/loader/init.lua similarity index 81% rename from lua/dep/lazy/utils.lua rename to lua/dep/lazy/loader/init.lua index 889c9be..7569b9b 100644 --- a/lua/dep/lazy/utils.lua +++ b/lua/dep/lazy/loader/init.lua @@ -1,15 +1,15 @@ local logger = require('dep.log') ----@class lazy +---@class lazy_loader ---@field load function the function to load the plugin ---@field command_ids table the commands that have been registered ---@field auto_ids table the auto commands that have been registered ---@field keybind_ids table the keybinds that have been registered -local lazy = {} +local lazy_loader = {} --- create a new instance of lazy ---@return lazy -function lazy:new() +function lazy_loader:new() local o = {} setmetatable(o, self) @@ -22,22 +22,16 @@ function lazy:new() return o end ---- set the loading function ----@param load function the loading function -function lazy:set_load(load) +--- set the loading callback +---@param load function the loader function +function lazy_loader:set_load(load) self.load = load end ---- get the configured load function ----@return function load function -function lazy:get_load() - return self.load -end - --- create a usercommand which will trigger the plugin to load ---@param name string the name of the command ---@param opts vim.api.keyset.user_command? options -function lazy:cmd(name, opts) +function lazy_loader:cmd(name, opts) opts = opts or {} vim.api.nvim_create_user_command(name, opts['callback'] or function() self:cleanup() @@ -49,7 +43,7 @@ end --- create an auto command which will trigger the plugin to load ---@param event string the event to trigger on ---@param opts vim.api.keyset.create_autocmd? options -function lazy:auto(event, opts) +function lazy_loader:auto(event, opts) opts = opts or {} opts['callback'] = opts['callback'] or function() @@ -62,17 +56,20 @@ end --- create an auto command which will trigger on filetype ---@param filetype string filetype to register the auto on -function lazy:ft(filetype) - lazy:auto("FileType", { +function lazy_loader:ft(filetype) + lazy_loader:auto("FileType", { pattern = filetype }) end +---@class lazy.Opts: vim.keymap.set.Opts +---@field rerun boolean|function weather to rerun and what to do + --- create a keybind which will trigger the plugin to load ---@param mode string the mode to trigger in ---@param bind string the binding to use ----@param opts vim.keymap.set.Opts? options -function lazy:keymap(mode, bind, opts) +---@param opts lazy.Opts? options +function lazy_loader:keymap(mode, bind, opts) opts = opts or {} -- move the rerun arg to a seperate variable because keymap.set doesn't like @@ -85,7 +82,9 @@ function lazy:keymap(mode, bind, opts) self:cleanup() -- call the keymap after the user has mapped it - if rerun then + if type(rerun) == "function" then + rerun() + elseif rerun then local keys = vim.api.nvim_replace_termcodes(bind, true, false, true) vim.api.nvim_input(keys) end @@ -95,7 +94,7 @@ function lazy:keymap(mode, bind, opts) end --- cleanup all the callbacks, and load the plugin -function lazy:cleanup() +function lazy_loader:cleanup() -- cleanup user commands for _, command_id in pairs(self.command_ids) do local ok, err = pcall(vim.api.nvim_del_user_command, command_id) @@ -121,4 +120,4 @@ function lazy:cleanup() self:load() end -return lazy +return lazy_loader diff --git a/lua/dep/lazy/loader/short.lua b/lua/dep/lazy/loader/short.lua new file mode 100644 index 0000000..d6badde --- /dev/null +++ b/lua/dep/lazy/loader/short.lua @@ -0,0 +1,62 @@ +-- This file contains shorthands which rely on the loader core functions. They +-- are intended to ease lazy loading condition definitions to use them you may +-- do the following: +-- +-- ```lua +-- _G.dep_short = require("dep.lazy.loader.short") +-- ``` +-- +-- Which will allow you to reference it anywhere in your config like so: +-- +-- ```lua +-- require("dep") { +-- { "some/plugin", +-- lazy = dep_short.cmd("TheCommand") +-- } +-- } +-- ``` +-- +-- Happy vimming o/ +local short = {} + +--- create a single command +---@param name string the name of the command +---@param opts vim.api.keyset.user_command? options +---@return function callback +function short.cmd(name, opts) + return function(load) + load:cmd(name, opts) + end +end + +--- create a single auto command +---@param event string the event to trigger on +---@param opts vim.api.keyset.create_autocmd? options +---@return function callback +function short.auto(event, opts) + return function(load) + load:auto(event, opts) + end +end + +--- create a single auto command which will trigger on filetype +---@param filetype string filetype to register the auto on +---@return function callback +function short.ft(filetype) + return function(load) + load:ft(filetype) + end +end + +--- create a single keybind +---@param mode string the mode to trigger in +---@param bind string the binding to use +---@param opts lazy.Opts? options +---@return function callback +function short.keymap(mode, bind, opts) + return function(load) + load:keymap(mode, bind, opts) + end +end + +return short diff --git a/lua/dep/lazy/short.lua b/lua/dep/lazy/short.lua deleted file mode 100644 index d00fce2..0000000 --- a/lua/dep/lazy/short.lua +++ /dev/null @@ -1,43 +0,0 @@ ---- shorthands for when you only need to define one callback -local short = {} - ---- create a usercommand which will trigger the plugin to load ----@param name string the name of the command ----@param opts vim.api.keyset.user_command? options ----@return function callback lazy setup -function short:cmd(name, opts) - return function(load) - load:cmd(name, opts) - end -end - ---- create an auto command which will trigger the plugin to load ----@param event string the event to trigger on ----@param opts vim.api.keyset.create_autocmd? options ----@return function callback lazy setup -function short:auto(event, opts) - return function(load) - load:auto(event, opts) - end -end - ---- create an auto command which will trigger on filetype ----@param filetype string filetype to register the auto on -function short:ft(filetype) - return function(load) - load:ft(filetype) - end -end - ---- create a keybind which will trigger the plugin to load ----@param mode string the mode to trigger in ----@param bind string the binding to use ----@param opts vim.keymap.set.Opts? options ----@return function callback lazy setup -function short:keymap(mode, bind, opts) - return function(load) - load:keymap(mode, bind, opts) - end -end - -return short diff --git a/lua/dep/package.lua b/lua/dep/package.lua index e9345f8..5606dc7 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -15,7 +15,7 @@ local bench = require("dep.bench") ---@field on_config function[] table of functions to run on config ---@field on_setup function[] table of function to run on setup ---@field on_load function[] table of functions to run on load ----@field lazy_load function[] table of functions to run which will tell the package when to load +---@field lazy_load table table of functions and booleans to run which will tell the package when to load ---@field requirements package[] this package's requirements ---@field dependents package[] packages that require this package ---@field perf table performance metrics for the package @@ -299,7 +299,7 @@ function package:ensureadded(force) self.lazied = true for _, load_cond in pairs(self.lazy_load) do -- configure the lazy loader for the user - local l = require('dep.lazy.utils'):new() + local l = require('dep.lazy.loader'):new() if l == true then logger:log("lazy", "failed to get lazy utils") return false @@ -315,8 +315,14 @@ function package:ensureadded(force) loadpkg(self) end) - -- run it - load_cond(l) + -- run it if it's not just a stopper to keep the plugin lazy + if load_cond ~= true then + local ok, err = pcall(load_cond, l) + if not ok then + logger:log("lazy", "failed to register load conditions for '%s': %s", + self.name, err) + end + end end return self end diff --git a/lua/dep/spec.lua b/lua/dep/spec.lua index 951e654..e7b1d9d 100644 --- a/lua/dep/spec.lua +++ b/lua/dep/spec.lua @@ -15,7 +15,7 @@ local logger = require("dep.log") ---@field setup function? code to run before the package is loaded ---@field load function? code to run after the package is loaded ---@field config function? code to run after the package is installed/updated ----@field lazy function? code to run which determines when the package is loaded +---@field lazy function|true? code to run which determines when the package is loaded ---@field as string? overrides the short name of the package which is usually set --- to a substring of all the chars after "/" in spec[1] ---@field url string? the url to the git repository hosting the package @@ -114,8 +114,9 @@ function spec:check(silent) end if self.lazy ~= nil then -- spec.lazy - if type(self.lazy) ~= "function" then - logger:log("spec", "spec.lazy must be a function in %s", self[1]) + if type(self.lazy) ~= "function" and self.lazy ~= true then + logger:log("spec", "spec.lazy must be a function or boolean in %s", + self[1]) return false end end From 8b91fc9c2fdc8be784239f5f98d53ba83b678f3a Mon Sep 17 00:00:00 2001 From: Squibid Date: Sun, 29 Jun 2025 18:12:47 -0400 Subject: [PATCH 41/74] bench doesn't need to be a class --- lua/dep.lua | 2 +- lua/dep/bench.lua | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index da9174a..d9f7c45 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -62,7 +62,7 @@ end return function(opts) M.config_path = debug.getinfo(2, "S").source:sub(2) logger.pipe = logger:setup() - bench:setup() + bench.setup() lazy.setup() --- make comparison for table.sort diff --git a/lua/dep/bench.lua b/lua/dep/bench.lua index 86d3aff..e8b2135 100644 --- a/lua/dep/bench.lua +++ b/lua/dep/bench.lua @@ -7,11 +7,8 @@ local bench = {} local b -function bench:setup() +function bench.setup() local o = {} - self = {} - self.__index = self - setmetatable(o, self) o.perf = {} o.inited = true From 2346a0baa5dbfd15dbefefc8b8c6c2066b614fbb Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 1 Jul 2025 15:55:37 -0400 Subject: [PATCH 42/74] remove lazied field from package as it was unused --- lua/dep/package.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 5606dc7..a8c46e8 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -9,7 +9,6 @@ local bench = require("dep.bench") ---@field lazy boolean if the package is lazy loaded in any way ---@field added boolean if the package has been added in vim ---@field configured boolean if the package has been configured ----@field lazied boolean if the packages lazy loading has been set ---@field loaded boolean if a package has been loaded ---@field subtree_loaded boolean is the subtree has been loaded ---@field on_config function[] table of functions to run on config @@ -89,7 +88,6 @@ function package:new(spec, overrides) o.exists = false o.lazy = false o.added = false - o.lazied = false o.configured = false o.loaded = false o.subtree_loaded = false @@ -296,7 +294,6 @@ function package:ensureadded(force) elseif not self.added and self.lazy then logger:log("lazy", "registering %d lazy hooks for %s", #self.lazy_load, self.id) - self.lazied = true for _, load_cond in pairs(self.lazy_load) do -- configure the lazy loader for the user local l = require('dep.lazy.loader'):new() From f4d1c4cf2590c3cf7c01313afc0866989ce12ed7 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 1 Jul 2025 21:38:10 -0400 Subject: [PATCH 43/74] reorganize the main file a little bit --- lua/dep.lua | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index d9f7c45..ceb91dc 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -17,7 +17,7 @@ local M = {} --- sync a tree of plugins ---@param tree package[] tree of plugins ---@param cb function? callback -function M.synctree(tree, cb) +local function synctree(tree, cb) local progress = 0 local has_errors = false @@ -57,6 +57,29 @@ function M.synctree(tree, cb) end end +--- check if a package should be synced +---@param opts table options +---@param package package package table spec +---@return boolean sync +local function shouldsync(opts, package) + if opts.sync == "new" or opts.sync == nil then + return not package.exists + else + return opts.sync == "always" + end +end + +--- 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 + -- basically the main function of our program ---@param opts speclist return function(opts) @@ -65,17 +88,6 @@ return function(opts) bench.setup() lazy.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/") bench.mark("load", function() @@ -115,27 +127,16 @@ return function(opts) package:reload() end - --- 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 + if shouldsync(opts, package) then table.insert(targets, package) end end -- install all targets - M.synctree(targets) + synctree(targets) end) if not initialized then @@ -163,7 +164,7 @@ return function(opts) end, {}) vim.api.nvim_create_user_command("DepSync", function() - M.synctree(packager.get_packages()) + synctree(packager.get_packages()) end, {}) vim.api.nvim_create_user_command("DepReload", function() From fa3457e4634710a164b2540914f8504b74f628ef Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 1 Jul 2025 21:38:41 -0400 Subject: [PATCH 44/74] formatting --- lua/dep.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index ceb91dc..fcb7155 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -1,8 +1,7 @@ -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') +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") From 44ec0633cebc79d0dedfeaa3c388742ea98b8b48 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 1 Jul 2025 21:44:43 -0400 Subject: [PATCH 45/74] add a ui (wip) --- lua/dep.lua | 30 +++---- lua/dep/log.lua | 4 +- lua/dep/ui/format.lua | 46 ++++++++++ lua/dep/ui/init.lua | 198 ++++++++++++++++++++++++++++++++++++++++++ lua/dep/ui/page.lua | 84 ++++++++++++++++++ 5 files changed, 341 insertions(+), 21 deletions(-) create mode 100644 lua/dep/ui/format.lua create mode 100644 lua/dep/ui/init.lua create mode 100644 lua/dep/ui/page.lua diff --git a/lua/dep.lua b/lua/dep.lua index fcb7155..fe03e5f 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -5,6 +5,7 @@ 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 = {} @@ -143,25 +144,6 @@ return function(opts) 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() synctree(packager.get_packages()) end, {}) @@ -177,5 +159,15 @@ return function(opts) 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 diff --git a/lua/dep/log.lua b/lua/dep/log.lua index 36ef6c6..467d941 100644 --- a/lua/dep/log.lua +++ b/lua/dep/log.lua @@ -79,8 +79,8 @@ function logger:log(level, message, ...) -- write to the pipe if it's open if logger.pipe then - logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%T"), - source.short_src:gsub('.*%/', ''), source.currentline, message)) + logger.pipe:write(string.format("[%s] %s:%s:(%s) %s\n", os.date("%T"), + source.short_src:gsub('.*%/', ''), source.currentline, level, message)) end end) end diff --git a/lua/dep/ui/format.lua b/lua/dep/ui/format.lua new file mode 100644 index 0000000..0428ae4 --- /dev/null +++ b/lua/dep/ui/format.lua @@ -0,0 +1,46 @@ +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) + local log_time = string.sub( log_line, string.find(log_line, "%[") + 1, + string.find(log_line, "%]") - 1) + local colon = string.find(log_line, ":", 11) + local log_path = string.sub(log_line, string.find(log_line, "%]") + 2, + colon - 1) + local log_path_ln = string.sub(log_line, colon + 1, + string.find(log_line, ":", colon + 1) - 1) + local level = string.sub(log_line, string.find(log_line, "%(") + 1, + string.find(log_line, "%)") - 1) + local rest = string.sub(log_line, string.find(log_line, "%)") + 2) + + return { + { "[", "" }, + { log_time, "Boolean" }, + { "] ", "" }, + { log_path, "String" }, + { ":", "" }, + { log_path_ln, "Number" }, + { ": ", "" }, + { rest, logger.stage_colors[level] or "" } + } +end + +return format diff --git a/lua/dep/ui/init.lua b/lua/dep/ui/init.lua new file mode 100644 index 0000000..01d8d34 --- /dev/null +++ b/lua/dep/ui/init.lua @@ -0,0 +1,198 @@ +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() + vim.api.nvim_win_set_cursor(ui.winnr, { #p.content, 0 }) + 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 + +--- setup all the pages +---@param packager package the packager +local function setup_pages(packager) + 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 }) +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 diff --git a/lua/dep/ui/page.lua b/lua/dep/ui/page.lua new file mode 100644 index 0000000..85c568d --- /dev/null +++ b/lua/dep/ui/page.lua @@ -0,0 +1,84 @@ +---@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 From e8276e31374152947a8ebfb0b90652adc5072b6d Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 1 Jul 2025 21:57:08 -0400 Subject: [PATCH 46/74] ensure that the setup step doesn't get run multiple times when opening... the ui --- lua/dep/ui/init.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lua/dep/ui/init.lua b/lua/dep/ui/init.lua index 01d8d34..ca27ddd 100644 --- a/lua/dep/ui/init.lua +++ b/lua/dep/ui/init.lua @@ -108,9 +108,14 @@ function ui.set_page(p) }) 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)) @@ -132,6 +137,8 @@ local function setup_pages(packager) vim.api.nvim_win_close(ui.winnr, false) ui.winnr = nil end, { buffer = ui.bufnr }) + + setup = true end --- setup the ui From e0f39fe0db601e0f77962355b1f98ae2f56d443d Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 1 Jul 2025 23:33:33 -0400 Subject: [PATCH 47/74] allow disabling modules --- lua/dep/modules/module.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lua/dep/modules/module.lua b/lua/dep/modules/module.lua index 7e6d12e..a16ddf6 100644 --- a/lua/dep/modules/module.lua +++ b/lua/dep/modules/module.lua @@ -5,6 +5,7 @@ local packager = require("dep.package") ---@class module ---@field name string name of the module ---@field desc string description of the module +---@field disable boolean weather to disable all the packages inside the module ---@field path string path to the module ---@field mod table the module ---@field packages package[] all packages registed from the module @@ -18,6 +19,8 @@ local module = {} ---@return module|false module false on failure to load module ---@nodiscard function module:new(modpath, prefix, overrides) + overrides = overrides or {} + local ok, err local o = {} self = {} @@ -45,6 +48,11 @@ function module:new(modpath, prefix, overrides) o.name = o.mod.name or o.name o.desc = o.mod.desc or o.desc + -- ensure the overrides are properly set + overrides = vim.tbl_extend("force", overrides, { + disable = o.mod.disable or overrides.disable + }) + -- allow a module to be a spec if spec_man.check(o.mod, true) ~= false then o.mod = { o.mod } From d406ee18f2730aef01b8da8aeb496fe4493fed53 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 1 Jul 2025 23:34:00 -0400 Subject: [PATCH 48/74] allow never syncing --- lua/dep.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/dep.lua b/lua/dep.lua index fe03e5f..96544ca 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -62,7 +62,9 @@ end ---@param package package package table spec ---@return boolean sync local function shouldsync(opts, package) - if opts.sync == "new" or opts.sync == nil then + if opts.sync == "never" then + return false + elseif opts.sync == "new" or opts.sync == nil then return not package.exists else return opts.sync == "always" From 26ef7289f6335210567b9ba0ee6892b2f1327a62 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 1 Jul 2025 23:34:32 -0400 Subject: [PATCH 49/74] update readme --- README.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8118c7d..54297d0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # dep -> This readme is a work in progress. - A versatile, declarative and correct [neovim][2] package manager in [Lua][3]. Originally written for personal use by [luaneko][4]. Adapted by [squibid][5] for general use. @@ -11,13 +9,14 @@ What does that mean? 1. `versatile` - packages can be declared in any Lua file in any order of your liking. 2. `declarative` - packages are declared using simple Lua tables. -3. `correct` - packages are always loaded in a correct and consistent order. +3. `correct` - packages are always loaded in a correct and consistent order +(barring any lazy loading). In addition to the above dep has been built to be completely in control of you, the user. With the help of lazy loading you can choose when your plugin loads down to the finest detail (examples may be found below). -See also squibid's [neovim-configs][10] for an example of how dep can be used in +See also squibid's [neovim-config][10] for an example of how dep can be used in practice. ## Requirements @@ -35,7 +34,7 @@ practice. local path = vim.fn.stdpath("data") .. "/site/pack/deps/opt/dep" if vim.fn.empty(vim.fn.glob(path)) > 0 then - vim.fn.system({ "git", "clone", "--depth=1", "https://git.squi.bid/dep", path }) + vim.fn.system({ "git", "clone", "--depth=1", "https://git.squi.bid/squibid/dep", path }) end vim.cmd("packadd dep") @@ -57,6 +56,7 @@ require "dep" { - `:DepClean` - cleans removed packages. - `:DepReload` - reloads all packages. - `:DepLog` - opens the log file. +- `:DepUi` - opens the ui. ## Package specification @@ -83,8 +83,9 @@ A package must be declared in the following format. os.execute(...) end, - -- [function] Code used to determine when the package should be loaded. + -- [function|true] Code used to determine when the package should be loaded. lazy = function(load) + load:cmd("LoadPackage") end, -- [string] Overrides the short name of the package. @@ -93,7 +94,7 @@ A package must be declared in the following format. -- [string] Overrides the URL of the git repository to clone. -- Defaults to "https://github.com/{full_name}.git". - url = "https://git.chiya.dev/user/package.git", + url = "https://git.squi.bid/user/package.git", -- [string] Overrides the source in which the package is gotten -- from. This is not set by default. @@ -351,7 +352,7 @@ require "dep" { ``` If you're in the need of a deeper understanding of how the utils work go check -out `lua/lazy/utils.lua` for the source code. +out `lua/lazy/loader/init.lua` for the source code. ## Separating code into modules @@ -378,6 +379,13 @@ return { Package specifications from other modules can be loaded using the `modules` option. ```lua +require "dep" { + modules = { + prefix = "packages" + } +} + +-- the above is equivalent to require "dep" { modules = { prefix = "packages.", @@ -386,7 +394,7 @@ require "dep" { } } --- the above is equivalent to +-- or require "dep" { modules = { "packages.search", From a13d616da5ff1130e5d46fea2308b4697094ace8 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 03:11:20 -0400 Subject: [PATCH 50/74] add documentation --- .gitignore | 1 + doc/dep.txt | 597 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lua/dep.lua | 3 + 3 files changed, 601 insertions(+) create mode 100644 doc/dep.txt diff --git a/.gitignore b/.gitignore index e43b0f9..17402e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +doc/tags diff --git a/doc/dep.txt b/doc/dep.txt new file mode 100644 index 0000000..c6f2cd8 --- /dev/null +++ b/doc/dep.txt @@ -0,0 +1,597 @@ +*dep.txt* Declarative Package Manager 02-Jul-2025 + +============================================================================== +Table of Contents *dep-table-of-contents* + +1. Introduction |dep| +2. Setup |dep-setup| +3. Specs |dep-spec| + - Package Spec |dep-package-spec| + - Module Spec |dep-module-spec| +4. Lazy Loading |dep-lazy-loading| + - Lazy Loading API |dep-lazy-loading-api| + - Lazy Loading API Shorthands |dep-lazy-loading-api-shorthands| +5. Commands |dep-commands| +6. Examples |dep-examples| + - Declaring Dependencies |dep-examples-declaring-dependencies| + - Modules |dep-examples-modules| +7. Credits & License |dep-credits| + +============================================================================== +1. Introduction *dep* + +A versatile, declarative and correct neovim package manager in Lua. Originally +written for personal use by luaneko. Adapted by squibid for general use. + +What does that mean? + +1. `versatile` - packages can be declared in any Lua file in any order of your +liking. +2. `declarative` - packages are declared using simple Lua tables. +3. `correct` - packages are always loaded in a correct and consistent order +(barring any lazy loading). + +In addition to the above dep has been built to be completely in control of you, +the user. With the help of lazy loading you can choose when your plugin loads +down to the finest detail (examples may be found below). + +============================================================================== +2. Setup *dep-setup* + +Put the following anywhere before any actual use of dep. + +>lua + local path = vim.fn.stdpath("data") .. "/site/pack/deps/opt/dep" + + if vim.fn.empty(vim.fn.glob(path)) > 0 then + vim.fn.system({ + "git", + "clone", + "--depth=1", + "https://git.squi.bid/squibid/dep", + path, + }) + end + + vim.cmd("packadd dep") +< + +============================================================================== +3. Specs *dep-spec* + +dep uses a variation of specifications to ensure everything works smoothly. +This includes a basic spec used when setting up dep: +>lua + require("dep") { + -- [string] Specifies when dep should automatically synchronize. + -- "never": disable this behavior + -- "new": only install newly declared packages (default) + -- "always": synchronize all packages on startup + sync = "new", + + -- [array] Specifies the modules to load package specifications from. + -- Defaults to an empty table. + -- Items can be either an array of package specifications, + -- or a string that indicates the name of the module from which the array + -- of package specifications is loaded. + -- + -- "."'s are added between the prefix and module names as required. In + -- addition if there is only a prefix and no modules supplied then dep + -- automatically loads all lua files in the directory. + modules = { + -- [string] Prefix string to prepend to all module names. This is + -- optional. + prefix = "", + + -- [string] module names + ... + }, + + -- [table|string] package specification(s) + ... + } +< +PACKAGE SPEC *dep-package-spec* +>lua + { + -- [string] Specifies the full name of the package. + -- This is the only required field; all other fields are optional. + "user/package", + + -- [function] Code to run before the package is loaded into neovim. + setup = function() + vim.g.package_config = ... + end, + + -- [function] Code to run after the package is loaded into neovim. + load = function() + require "package".setup(...) + end, + + -- [function] Code to run after the package is installed or updated. + config = function() + os.execute(...) + end, + + -- [function|true] Code used to determine when the package should be + -- loaded. + lazy = function(load) + load:cmd("LoadPackage") + end, + + -- [string] Overrides the short name of the package. + -- Defaults to a substring of the full name after '/'. + as = "custom_package", + + -- [string] Overrides the URL of the git repository to clone. + -- Defaults to "https://github.com/{full_name}.git". + url = "https://git.squi.bid/user/package.git", + + -- [string] Overrides the source in which the package is gotten + -- from. This is not set by default. + path = "~/my-local-package/", + + -- [string] Overrides the name of the branch to clone. + -- Defaults to whatever the remote configured as their HEAD, which is + -- usually "master". + branch = "develop", + + -- [string] Overrides the commit ref to target + -- Defaults to the latest commit on the current branch + commit = "e76cb03", + + -- [boolean] Prevents the package from being loaded. + disable = true, + + -- [boolean] Prevents the package from being updated. + pin = true, + + -- [string|array] Specifies requirements that must be loaded before the + -- package. If given a string, it is wrapped into an array. + reqs = {...}, + + -- [string|array] Specifies dependents that must be loaded after the + -- package. If given a string, it is wrapped into an array. + deps = {...} + } +< +MODULE SPEC *dep-module-spec* +>lua + { + -- [string] Specifies a name for the module + name = "a name", + + -- [string] Specifies a description of the module + desc = "a description of the module", + + -- [boolean] Prevents all packages in the module from being loaded. + disable = false, + + -- [table|string] package specification(s) + ... + } +< +More information on the package specifications may be found in +|dep-package-spec|. + +============================================================================== +4. Lazy Loading *dep-lazy-loading* + +Lazy loading is important for making sure neovim can load nice and fast unlike +a certain bloated IDE. It has a seperate section in this documentation to +ensure that you can use it to it's full extent. + +If you refer to the |dep-package-spec| you'll notice the `lazy` flag which may +be used to conditionally load a package. When it is set to a function you +choose when it runs and more information on that may be found in +|dep-lazy-loading-api|. In addition to setting it to a function you may set it +to `true` in which case dep takes care of loading it for you. + +When setting a colorscheme dep checks to make sure that the plugin is loaded, +therefore it's recommended that you make use of the `lazy` flags ability to be +set to `true` by setting any colorscheme that you have installed, but do not +use as your main one to lazy. + +LAZY LOADING API *dep-lazy-loading-api* + +Within the |dep-package-spec| the lazy flag when set to a function takes one +argument `load` which is a class containing loading functions. For the +following examples assume that `load` is set to the class which may be found +within `lua/dep/lazy/loader/init.lua`. + +------------------------------------------------------------------------------ +LOAD:CMD *dep-lazy-loading-api-cmd* + +`load:cmd` is a function which allows you to specify a command for the package +to load on. It takes the similar arguments to |nvim_create_user_command()| +with a key difference in what the command runs. The following is an example of +what arguments the function takes: +>lua + load:cmd("Command", {}) +< +Notice the missing 'command' argument which is found in +|nvim_create_user_command|, this is replaced by a callback function. The above +is equivalent to the following: +>lua + load:cmd("Command", { + callback = function() + load:cleanup() + end + }) +< +If you wish the second argument may be completely ommitted. + +------------------------------------------------------------------------------ +LOAD:AUTO *dep-lazy-loading-api-auto* + +`load:auto` is a function which allows you to specify an auto command for the +package to load on. It takes the same arguments as |nvim_create_autocmd()|. +The following is an example of using it: +>lua + load:auto("InsertEnter", {}) +< +Just like with |nvim_create_autocmd()| you may choose to pass in a 'callback' +by default the above is equivalent to the following: +>lua + load:auto("InsertEnter", { + callback = function() + load:cleanup() + end + }) +< +As with `load:cmd` the second argument may be ommitted. + +------------------------------------------------------------------------------ +LOAD:FT *dep-lazy-loading-api-ft* + +`load:ft` is a function which allows you to specify a filetype for the package +to load on. It takes one argument: 'filetype' like so: +>lua + load:ft("lua") +< +Which is equivalent to the following: +>lua + load:auto("FileType", { + pattern = "lua", + callback = function() + load:cleanup() + end + }) +< +Note that this is just an expansion of `load:auto` for your convenience. + +------------------------------------------------------------------------------ +LOAD:KEYMAP *dep-lazy-loading-api-keymap* + +`load:keymap` is a function which allows you to specify a keymap for the +package to load on. It takes the similar arguments to |vim.keymap.set()| with a +key difference in what the command runs. The following is an example of what +arguments the function takes: +>lua + load:keymap("n", "p", {}) +< +Notice the missing 'rhs' argument which is found in |vim.keymap.set|, this is +replaced by a callback function. The above is equivalent to the following: +>lua + load:keymap("n", "p", { + callback = function() + -- register keymap unload + load:cleanup() + + -- call the keymap after the user has mapped it + if type(rerun) == "function" then + rerun() + elseif rerun then + local keys = vim.api.nvim_replace_termcodes(bind, true, false, true) + vim.api.nvim_input(keys) + end + end + }) +< +Note the inclusion of a `rerun` field, this is a parameter which may be passed +into the options table to re-run the binding after loading the package. You +may choose to include your own logic by passing a function to the `rerun` +field or disable the built-in logic by passing false. + +LAZY LOADING API SHORTHANDS *dep-lazy-loading-api-shorthands* + +On occasion you may wish to only define one condition for the package to load. +When that is the case you may choose to use the built-in shorthands. By +loading them: +>lua + require("dep.lazy.loader.short") +< +The shorthands are very similar to those found in |dep-lazy-loading-api| with +a key exception: instead of running the functions within the body of a +function set as the lazy field to a package specification this is the lazy +field and may be use like so: +>lua + { "user/package", + lazy = require("dep.lazy.loader.short").cmd("Command") + } +< +And you may of course put the shorthands in a variable to make this actually +shorter: +>lua + local short = require("dep.lazy.loader.short") + { "user/package", + lazy = short.cmd("Command") + } +< +============================================================================== +5. Commands *dep-commands* + +------------------------------------------------------------------------------ +SYNC ALL PLUGINS *:DepSync* +- installs new packages, updates packages to the latest versions, + cleans removed packages and reloads packages as necessary. + +------------------------------------------------------------------------------ +CLEAN REMOVED PLUGINS *:DepClean* +- cleans removed packages. + +------------------------------------------------------------------------------ +RELOAD ALL PLUGINS *:DepReload* +- reloads all packages. + +------------------------------------------------------------------------------ +OPEN THE UI *:DepUi* +- opens the ui. + +------------------------------------------------------------------------------ +OPEN THE LOG *:DepLog* +- opens the log file. + +============================================================================== +6. Examples *dep-examples* + +When a string is given where a package specification table is expected, it is +assumed to be the package's full name. + +>lua + require("dep") { + -- these two are equivalent + "user/package", + { "user/package" }, + } +< + +A package can be declared multiple times. Multiple declarations of the same +package are combined into one. This is useful when declaring dependencies, +which is explored later. + +>lua + require("dep") { + { "user/package", + reqs = "user/dependency", + disabled = true, + config = function() + print("my config hook") + end + }, + { "user/package", + requires = "user/another_dependency", + deps = "user/dependent", + disabled = false, + config = function() + os.execute("make") + end + } + } + + -- the above is equivalent to + require("dep") { + { "user/package", + reqs = { "user/dependency", "user/another_dependency" }, + deps = "user/dependent", + disabled = true, + config = function() + print("my config hook") + os.execute("make") + end + } + } +< +DECLARING DEPENDENCIES *dep-examples-declaring-dependencies* + +The dependencies and dependents declared in a package specification are +themselves package specifications. If a dependency or dependent is declared +multiple times, they are combined into one just like normal package +specifications. +>lua + require("dep") { + { "user/package", + reqs = { + { "user/dependency1", + reqs = "user/dependency2" + } + } + } + } + + -- the above is equivalent to + require("dep") { + { "user/dependency2", + deps = { + { "user/dependency1", + deps = "user/package" + } + } + } + } + + -- which is equivalent to + require("dep") { + { "user/dependency1", + reqs = "user/dependency2", + deps = "user/package" + } + } + + -- which is equivalent to + require("dep") { + { "user/dependency1", + reqs = "user/dependency2" + }, + { "user/package", + reqs = "user/dependency1" + } + } + + -- which is equivalent to + require("dep") { + { "user/dependency2", + deps = "user/dependency1" + }, + { "user/dependency1", + deps = "user/package" + } + } + + -- all of the above are guaranteed to load in the following order: + -- dependency2, dependency1, package +< +If dep detects a circular dependency cycle, it reports the problematic packages +instead of hanging or crashing. +>lua + -- this throws an error saying package1 depends on package2 which depends on + -- package1 + require("dep") { + { "user/package1", + reqs = "user/package2" + }, + { "user/package2", + reqs = "user/package1" + } + } +< +A dependency can be marked as disabled, which disables all dependents +automatically. +>lua + require("dep") { + { "user/dependency", + disabled = true + }, + { "user/package1", + disabled = true, -- implied + reqs = "user/dependency" + }, + { "user/package2", + disabled = true, -- implied + reqs = "user/dependency" + } + } +< +If a dependency fails to load for some reason, all of its dependents are +guaranteed to not load. +>lua + require("dep") { + { "user/problematic", + load = function() + error("bad hook") + end + }, + { "user/dependent", + requires = "user/problematic", + load = function() + print "unreachable" + end + } + } +< + +MODULES *dep-examples-modules* + +Suppose you split your `init.lua` into two files `packages/search.lua` and +`packages/vcs.lua`, which declare the packages telescope.nvim and vim-fugitive +respectively. + +>lua + -- ~/.config/nvim/lua/packages/search.lua: + return { + { "nvim-telescope/telescope.nvim", + reqs = "nvim-lua/plenary.nvim" + } + } +< +>lua + -- ~/.config/nvim/lua/packages/vcs.lua: + return { "tpope/vim-fugitive" } +< +Package specifications from other modules can be loaded using the `modules` +option. +>lua + require("dep") { + modules = { + prefix = "packages" + } + } + + -- the above is equivalent to + require("dep") { + modules = { + prefix = "packages.", + "search", + "vcs" + } + } + + -- or + require("dep") { + modules = { + "packages.search", + "packages.vcs" + } + } + + -- which is equivalent to + local packages = {} + + for _, package in ipairs(require "packages.search") do + table.insert(packages, package) + end + + for _, package in ipairs(require "packages.vcs") do + table.insert(packages, package) + end + + require("dep")(packages) + + -- which is ultimately equivalent to + require("dep") { + { "nvim-telescope/telescope.nvim", + reqs = "nvim-lua/plenary.nvim" + }, + "tpope/vim-fugitive" + } + + -- all of the above are guaranteed to load plenary.nvim before + -- telescope.nvim. order of telescope.nvim and vim-fugitive is consistent but + -- unspecified. +< +Entire modules can be marked as disabled, which disables all top-level packages +declared in that module. +>lua + return { + disable = true, + { "user/package", + disabled = true, -- implied by module + reqs = { + { "user/dependency", + -- disabled = true -- not implied + } + }, + deps = { + { "user/dependent", + disabled = true -- implied by dependency + } + } + } + } +< +============================================================================== +7. Credits & License *dep-credits* + +dep is licensed under the MIT License. Check the LICENSE file for more info. + +vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/lua/dep.lua b/lua/dep.lua index 96544ca..38c6566 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -90,6 +90,9 @@ return function(opts) bench.setup() lazy.setup() + -- generate doc tags + vim.cmd.helptags(vim.fn.stdpath('data')..'/site/pack/deps/opt/dep/doc') + local initialized, err = pcall(function() packager.set_base_dir(opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/") bench.mark("load", function() From 585f04c74534325a5990fc07f4a535bfd20ee29b Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 03:53:31 -0400 Subject: [PATCH 51/74] add testing... this is very limited right now, but I will expand as I see fit --- Makefile | 4 ++++ tests/dep_spec.lua | 20 ++++++++++++++++++++ tests/minit.lua | 1 + 3 files changed, 25 insertions(+) create mode 100644 Makefile create mode 100644 tests/dep_spec.lua create mode 100644 tests/minit.lua diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..be555db --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +test: + nvim --headless -c "PlenaryBustedDirectory tests/ {minimal_init = './tests/minit.lua'}" + +.PHONY: test diff --git a/tests/dep_spec.lua b/tests/dep_spec.lua new file mode 100644 index 0000000..38d8e56 --- /dev/null +++ b/tests/dep_spec.lua @@ -0,0 +1,20 @@ +---@diagnostic disable: undefined-global, undefined-field +local dep_ui_format = require("dep.ui.format") + +describe("ui log formatting", function() + it("returns the proper chunks to print a formatted line", function() + assert.same( + { + { "[", "" }, + { "11:22:33", "Boolean" }, + { "] ", "" }, + { "file.lua", "String" }, + { ":", "" }, + { "1", "Number" }, + { ": ", "" }, + { "some fancy message", "" } + }, + dep_ui_format.log_line("[11:22:33] file.lua:1:(vim) some fancy message") + ) + end) +end) diff --git a/tests/minit.lua b/tests/minit.lua new file mode 100644 index 0000000..22d1049 --- /dev/null +++ b/tests/minit.lua @@ -0,0 +1 @@ +vim.opt.rtp:prepend(".") From cd43b3b8533a0e9a08306e73d2bc2b1e42e7f286 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 04:14:53 -0400 Subject: [PATCH 52/74] add git pre-commit hooks --- .githooks/pre-commit | 11 +++++++++++ README.md | 8 ++++++++ 2 files changed, 19 insertions(+) create mode 100755 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..eb326ad --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,11 @@ +#!/bin/sh + +echo "Running tests before commit..." + +# run tests +make test || { + echo "Tests failed. Commit aborted." + exit 1 +} + +echo "Tests passed. Proceeding with commit." diff --git a/README.md b/README.md index 54297d0..48e3938 100644 --- a/README.md +++ b/README.md @@ -482,6 +482,14 @@ require "dep" { - Lazy loading nvim-cmp doesn't work as the external sources don't get reconized by nvim-cmp when it's loaded. +## Contributing + +When contributing you may choose to run tests before commiting changes, if that +is so you may choose to run the following: +```sh +git config core.hooksPath .githooks +``` + ## License dep is licensed under the [MIT License](LICENSE). From 59a963a5c5b1f44ccaeb70e02abc541fdb673f4d Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 04:23:14 -0400 Subject: [PATCH 53/74] update the LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 5f28328..f55ba15 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 squibid +Copyright (c) 2023-2025 squibid Copyright (c) 2021-2023 chiya.dev Permission is hereby granted, free of charge, to any person obtaining a copy From 5541a656e29fc3ac1d66ba0a40050b1bbfc488c1 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 15:00:00 -0400 Subject: [PATCH 54/74] add tests for the spec checker --- tests/dep_spec.lua | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/dep_spec.lua b/tests/dep_spec.lua index 38d8e56..9a81049 100644 --- a/tests/dep_spec.lua +++ b/tests/dep_spec.lua @@ -1,5 +1,6 @@ ---@diagnostic disable: undefined-global, undefined-field local dep_ui_format = require("dep.ui.format") +local dep_spec_man = require("dep.spec") describe("ui log formatting", function() it("returns the proper chunks to print a formatted line", function() @@ -18,3 +19,52 @@ describe("ui log formatting", function() ) end) end) + +describe("package specification", function() + it("gets the package's name", function() + assert.equal(dep_spec_man.get_name({ "user/package" }), "package") + assert.equal(dep_spec_man.get_name({ "user/package.git" }), "package.git") + end) + + it("ensurses specs are in the proper format", function() + local correct = { "user/package" } + assert.same(dep_spec_man.correct_spec("user/package"), correct) + assert.same(dep_spec_man.correct_spec({ "user/package" }), correct) + assert.same(dep_spec_man.correct_spec({ { "user/package" } }), correct) + end) + + it("checks a spec for correctness", function() + assert.same( + dep_spec_man.check({ "user/package" }, true), + { "user/package" } + ) + + assert.same( + dep_spec_man.check({ + "user/package", + deps = "user/dependency" + }, true), + { + "user/package", + deps = { + "user/dependency" + } + } + ) + + assert.same( + dep_spec_man.check({ + "user/package", + deps = { + { { "user/dependency" } } + } + }, true), + { + "user/package", + deps = { + "user/dependency" + } + } + ) + end) +end) From 318a86d7869025faedbf4d7ce717b91e07939b5d Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 15:00:37 -0400 Subject: [PATCH 55/74] use the correct_spec function to handle dependencies and requirements --- lua/dep/spec.lua | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lua/dep/spec.lua b/lua/dep/spec.lua index e7b1d9d..674aa71 100644 --- a/lua/dep/spec.lua +++ b/lua/dep/spec.lua @@ -184,10 +184,7 @@ function spec:check(silent) return false end - -- turn an id into a spec - if (is == "string") then - self.reqs = { self.reqs } - end + self.reqs = spec.correct_spec(self.reqs) end if self.deps ~= nil then -- spec.deps @@ -197,10 +194,7 @@ function spec:check(silent) return false end - -- turn an id into a spec - if (is == "string") then - self.deps = { self.deps } - end + self.deps = spec.correct_spec(self.deps) end if silent == true then From 5deffca36e0a5fe5e0d1c0a9ad7a657b1ab5a499 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 17:54:43 -0400 Subject: [PATCH 56/74] improve lazy loading on commands, dep will now load on completion --- lua/dep/lazy/loader/init.lua | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lua/dep/lazy/loader/init.lua b/lua/dep/lazy/loader/init.lua index 7569b9b..5918ef9 100644 --- a/lua/dep/lazy/loader/init.lua +++ b/lua/dep/lazy/loader/init.lua @@ -33,7 +33,21 @@ end ---@param opts vim.api.keyset.user_command? options function lazy_loader:cmd(name, opts) opts = opts or {} - vim.api.nvim_create_user_command(name, opts['callback'] or function() + + -- load the plugin on completion + if not opts["complete"] then + opts["complete"] = function(_, line, _) + self:cleanup() + + -- return all completions for the current input, we need this to ensure + -- that the new completions are loaded from the actual plugin, not our + -- definiton of the command + return vim.fn.getcompletion(line, "cmdline") + end + opts["nargs"] = "*" + end + + vim.api.nvim_create_user_command(name, opts['callback'] or function(_) self:cleanup() end, opts) From 1538046b6f9ba5a11e0dfc3d6a308d7dff2295a4 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 20:34:42 -0400 Subject: [PATCH 57/74] make finding modules synchronous to avoid some bugs when calling... internal neovim api functions --- lua/dep/modules/init.lua | 77 ++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/lua/dep/modules/init.lua b/lua/dep/modules/init.lua index 0902fc5..fb05230 100644 --- a/lua/dep/modules/init.lua +++ b/lua/dep/modules/init.lua @@ -30,54 +30,45 @@ function modules:setup(speclist, overrides, config_path) "lua", (speclist.modules.prefix:gsub("%.", "/")) ) - h.uv.fs_scandir(path, function(err, handle) - if err then - logger:log("error", "failed to load modules; reason: %s", err) - else - while handle do - local name = h.uv.fs_scandir_next(handle) - if name then - -- skip non-lua files - if name:sub(#name - 3) ~= ".lua" then - goto continue - end - - -- remove the file extension from the name so that lua doesn't fail - -- when attempting to load it - name = name:sub(0, #name - 4) - - -- attempt to load the module - local mod = module.new(nil, name, speclist.modules.prefix, overrides) - if not mod then - goto continue - end - - table.insert(o.modules, mod) - ::continue:: - elseif name == nil then - -- no more entries - break - else - -- if there's a single error bail out - logger:log("error", "failed to run clean uv.fs_scandir_next failed") - return - end + local handle = h.uv.fs_scandir(path) + while handle do + local name = h.uv.fs_scandir_next(handle) + if name then + -- skip non-lua files + if name:sub(#name - 3) ~= ".lua" then + goto continue end - end - end) - else - -- loop through all modules and initialize them - for _, modpath in ipairs(speclist.modules) do - local mod = module.new(nil, modpath, speclist.modules.prefix, overrides) - if not mod then - goto continue - end - table.insert(o.modules, mod) - ::continue:: + -- remove the file extension from the name so that lua doesn't fail + -- when attempting to load it + name = name:sub(0, #name - 4) + + -- put the module into the list of modules + table.insert(speclist.modules, name) + + ::continue:: + elseif name == nil then + -- no more entries + break + else + -- if there's a single error bail out + logger:log("error", "failed to run clean uv.fs_scandir_next failed") + break + end end end + -- loop through all modules and initialize them + for _, modpath in ipairs(speclist.modules) do + local mod = module.new(nil, modpath, speclist.modules.prefix, overrides) + if not mod then + goto continue + end + + table.insert(o.modules, mod) + ::continue:: + end + return self end From 5bd30d939711f91f1b1ce193656e20d19c759ecd Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 20:36:14 -0400 Subject: [PATCH 58/74] don't error when the ui is closed --- lua/dep/ui/init.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/dep/ui/init.lua b/lua/dep/ui/init.lua index ca27ddd..12f9668 100644 --- a/lua/dep/ui/init.lua +++ b/lua/dep/ui/init.lua @@ -44,7 +44,9 @@ local function page_log() -- put the cursor at the bottom of the page after drawing p.post_draw = function() - vim.api.nvim_win_set_cursor(ui.winnr, { #p.content, 0 }) + 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 From f6209048f15c2322f986e669fa557d8ef67fa5ec Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 21:07:04 -0400 Subject: [PATCH 59/74] Revert "use the correct_spec function to handle dependencies and requirements" This reverts commit 318a86d7869025faedbf4d7ce717b91e07939b5d. --- lua/dep/spec.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lua/dep/spec.lua b/lua/dep/spec.lua index 674aa71..e7b1d9d 100644 --- a/lua/dep/spec.lua +++ b/lua/dep/spec.lua @@ -184,7 +184,10 @@ function spec:check(silent) return false end - self.reqs = spec.correct_spec(self.reqs) + -- turn an id into a spec + if (is == "string") then + self.reqs = { self.reqs } + end end if self.deps ~= nil then -- spec.deps @@ -194,7 +197,10 @@ function spec:check(silent) return false end - self.deps = spec.correct_spec(self.deps) + -- turn an id into a spec + if (is == "string") then + self.deps = { self.deps } + end end if silent == true then From 1c2f49fcfab2af262de14af799633a5060f55ef4 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 21:13:08 -0400 Subject: [PATCH 60/74] unpin dep internally --- lua/dep.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index 38c6566..9570a52 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -100,8 +100,7 @@ return function(opts) local root = packager:new({ "squibid/dep", url = "https://git.squi.bid/squibid/dep.git", - branch = "lazy", - pin = true + branch = "lazy" }) if not root then logger:log("error", "couldn't register root package") From cfc3f08d53bc1c8dd39875843e5a3d1ddd0c4eeb Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 21:13:30 -0400 Subject: [PATCH 61/74] remove a testcase for specs --- tests/dep_spec.lua | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/dep_spec.lua b/tests/dep_spec.lua index 9a81049..203c6c8 100644 --- a/tests/dep_spec.lua +++ b/tests/dep_spec.lua @@ -51,20 +51,5 @@ describe("package specification", function() } } ) - - assert.same( - dep_spec_man.check({ - "user/package", - deps = { - { { "user/dependency" } } - } - }, true), - { - "user/package", - deps = { - "user/dependency" - } - } - ) end) end) From 542298c1fe8176a121d82591b8244abd02634731 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 2 Jul 2025 21:51:34 -0400 Subject: [PATCH 62/74] registering a filetype lazy load condition should happen on self not the... lazy_loader --- lua/dep/lazy/loader/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/dep/lazy/loader/init.lua b/lua/dep/lazy/loader/init.lua index 5918ef9..1ae3c18 100644 --- a/lua/dep/lazy/loader/init.lua +++ b/lua/dep/lazy/loader/init.lua @@ -71,7 +71,7 @@ end --- create an auto command which will trigger on filetype ---@param filetype string filetype to register the auto on function lazy_loader:ft(filetype) - lazy_loader:auto("FileType", { + self:auto("FileType", { pattern = filetype }) end From 8e46eddecd44e1e8039286615967cf8d2fe36cb5 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 3 Jul 2025 16:13:01 -0400 Subject: [PATCH 63/74] Allow users to lazy load on another package --- doc/dep.txt | 66 +++++++++++++++++++++++++++++++++++ lua/dep/lazy/loader/init.lua | 29 ++++++++++++++- lua/dep/lazy/loader/short.lua | 10 ++++++ lua/dep/package.lua | 12 ++----- 4 files changed, 106 insertions(+), 11 deletions(-) diff --git a/doc/dep.txt b/doc/dep.txt index c6f2cd8..f8b8024 100644 --- a/doc/dep.txt +++ b/doc/dep.txt @@ -15,6 +15,7 @@ Table of Contents *dep-table-of-contents* 6. Examples |dep-examples| - Declaring Dependencies |dep-examples-declaring-dependencies| - Modules |dep-examples-modules| + - Lazy Loading |dep-examples-lazy-loading| 7. Credits & License |dep-credits| ============================================================================== @@ -293,6 +294,27 @@ into the options table to re-run the binding after loading the package. You may choose to include your own logic by passing a function to the `rerun` field or disable the built-in logic by passing false. +------------------------------------------------------------------------------ +LOAD:PLUGIN *dep-lazy-loading-api-plugin* + +`load:plugin` is a function which allows you to specify another plugin for the +package to load after. It takes two arguments: `plugin` which is the name of +the plugin you want to follow like: 'user/package'. The second argument is +`opts` which is a table with one option: `callback` which is a function. The +following is an example: +>lua + load:plugin("user/package", {}) +< +Which is the same as: +>lua + load:plugin("user/package", { + callback = function() + self:cleanup() + end + }) +< +When 'user/package' is already loaded the `callback` is called immediately. + LAZY LOADING API SHORTHANDS *dep-lazy-loading-api-shorthands* On occasion you may wish to only define one condition for the package to load. @@ -589,6 +611,50 @@ declared in that module. } } < +LAZY LOADING *dep-examples-lazy-loading* + +Lazy loading is a very complicated topic, and therefore this part of the +documentation assumes you have experience with regular package managment. +Let's go over loading order, and how the lazy loader determines what needs to +be loaded. + +Let's say we have the following spec: +>lua + { "user/package", + lazy = true, + deps = "user/dependent" + } +< +This is the same as the following: +>lua + { "user/package", + lazy = true + }, + + { "user/dependent", + reqs = "user/package", + lazy = require("dep.lazy.loader.short").plugin("user/package") + } +< +What you're seeing is implicit lazy loading. By default dep will lazy load +dependents who are explicitly defined in the spec. Now if we we're to modify +'user/dependent' like so: +>lua + { "user/package", + lazy = true + }, + + { "user/dependent", + reqs = "user/package", + lazy = function(load) + load:plugin("user/package") + load:cmd("LoadDependent") + end + } +< +If we were to call the command `:LoadDependent` it would first load +'user/package', and then load 'user/dependent'. + ============================================================================== 7. Credits & License *dep-credits* diff --git a/lua/dep/lazy/loader/init.lua b/lua/dep/lazy/loader/init.lua index 1ae3c18..9513f31 100644 --- a/lua/dep/lazy/loader/init.lua +++ b/lua/dep/lazy/loader/init.lua @@ -1,10 +1,12 @@ local logger = require('dep.log') +local packager = require('dep.package') ---@class lazy_loader ---@field load function the function to load the plugin ---@field command_ids table the commands that have been registered ---@field auto_ids table the auto commands that have been registered ---@field keybind_ids table the keybinds that have been registered +---@field plugin_ids table the plugins that have been registered local lazy_loader = {} --- create a new instance of lazy @@ -16,6 +18,7 @@ function lazy_loader:new() o.command_ids = {} o.auto_ids = {} o.keybind_ids = {} + o.plugin_ids = {} self.__index = self @@ -107,9 +110,29 @@ function lazy_loader:keymap(mode, bind, opts) table.insert(self.keybind_ids, { ['mode'] = mode, ['bind'] = bind }) end +--- load a plugin when another plugin loads +---@param plugin string plugin name +---@param opts table? options +function lazy_loader:plugin(plugin, opts) + opts = opts or {} + opts["callback"] = opts["callback"] or function() + self:cleanup() + end + + if packager.get_packages()[plugin].loaded then + opts["callback"]() + else + local on_load = packager.get_packages()[plugin].on_load + local on_load_idx = #on_load + 1 + on_load[on_load_idx] = opts["callback"] + + table.insert(self.plugin_ids, { plugin, on_load_idx }) + end +end + --- cleanup all the callbacks, and load the plugin function lazy_loader:cleanup() - -- cleanup user commands + -- cleanup user commands for _, command_id in pairs(self.command_ids) do local ok, err = pcall(vim.api.nvim_del_user_command, command_id) if not ok then @@ -130,6 +153,10 @@ function lazy_loader:cleanup() logger:log("lazy", err or "failed to delete keymap") end end + -- cleanup plugins + for _, plugin_id in pairs(self.plugin_ids) do + table.remove(packager.get_packages()[plugin_id[0]], plugin_id[1]) + end -- load the plugin self:load() end diff --git a/lua/dep/lazy/loader/short.lua b/lua/dep/lazy/loader/short.lua index d6badde..e7c74f2 100644 --- a/lua/dep/lazy/loader/short.lua +++ b/lua/dep/lazy/loader/short.lua @@ -59,4 +59,14 @@ function short.keymap(mode, bind, opts) end end +--- create a single plugin load event for when another plugin loads +---@param plugin string plugin name +---@param opts table? options +---@return function callback +function short.plugin(plugin, opts) + return function(load) + load:plugin(plugin, opts) + end +end + return short diff --git a/lua/dep/package.lua b/lua/dep/package.lua index a8c46e8..183bef2 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -165,18 +165,10 @@ function package:new(spec, overrides) -- if the child package is lazy loaded make sure the child package -- is only loaded when the parent package has finished loading if o.lazy then - table.insert(o.on_load, function() - local ok = o:loadtree(true) - if not ok then - logger:log("lazy", - "failed to run loadtree for %s, some packages may not be loaded", - o.id) - end - end) - -- tell the dep that it's gonna be lazy pkg.lazy = true - table.insert(pkg.lazy_load, function(_) end) + table.insert(pkg.lazy_load, + require("dep.lazy.loader.short").plugin(id)) end end end From 1cd5f63f8e9956456762fcb59df94f6b60a63856 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 3 Jul 2025 17:00:46 -0400 Subject: [PATCH 64/74] actually error when we fail to setup lazy loading for a package --- lua/dep/package.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 183bef2..7612e58 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -308,7 +308,7 @@ function package:ensureadded(force) if load_cond ~= true then local ok, err = pcall(load_cond, l) if not ok then - logger:log("lazy", "failed to register load conditions for '%s': %s", + logger:log("error", "failed to register lazy load conditions for '%s': %s", self.name, err) end end From adec93b7f4a5db0f74decfa7283dfa53946d64c8 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 3 Jul 2025 17:02:33 -0400 Subject: [PATCH 65/74] fix formatting log lines causing errors --- lua/dep/ui/format.lua | 33 +++++++++++++++++++++++---------- tests/dep_spec.lua | 8 ++++++++ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/lua/dep/ui/format.lua b/lua/dep/ui/format.lua index 0428ae4..63e6068 100644 --- a/lua/dep/ui/format.lua +++ b/lua/dep/ui/format.lua @@ -20,16 +20,29 @@ end ---@param log_line string log line ---@return chunk[] chunks function format.log_line(log_line) - local log_time = string.sub( log_line, string.find(log_line, "%[") + 1, - string.find(log_line, "%]") - 1) - local colon = string.find(log_line, ":", 11) - local log_path = string.sub(log_line, string.find(log_line, "%]") + 2, - colon - 1) - local log_path_ln = string.sub(log_line, colon + 1, - string.find(log_line, ":", colon + 1) - 1) - local level = string.sub(log_line, string.find(log_line, "%(") + 1, - string.find(log_line, "%)") - 1) - local rest = string.sub(log_line, string.find(log_line, "%)") + 2) + -- 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 { { "[", "" }, diff --git a/tests/dep_spec.lua b/tests/dep_spec.lua index 203c6c8..a89d198 100644 --- a/tests/dep_spec.lua +++ b/tests/dep_spec.lua @@ -17,6 +17,14 @@ describe("ui log formatting", function() }, dep_ui_format.log_line("[11:22:33] file.lua:1:(vim) some fancy message") ) + + -- malformed log line + assert.same({}, + dep_ui_format.log_line("11:22:33] file.lua:1:(vim) some fancy message")) + + -- test nil values + assert.same({}, dep_ui_format.log_line("")) + assert.same({}, dep_ui_format.log_line(nil)) end) end) From edf32fbf06df72051ed42f2f90d50057238009c9 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 3 Jul 2025 18:02:44 -0400 Subject: [PATCH 66/74] fix bug: remove the callback from the on_load list --- lua/dep/lazy/loader/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/dep/lazy/loader/init.lua b/lua/dep/lazy/loader/init.lua index 9513f31..26e1bc8 100644 --- a/lua/dep/lazy/loader/init.lua +++ b/lua/dep/lazy/loader/init.lua @@ -155,7 +155,7 @@ function lazy_loader:cleanup() end -- cleanup plugins for _, plugin_id in pairs(self.plugin_ids) do - table.remove(packager.get_packages()[plugin_id[0]], plugin_id[1]) + table.remove(packager.get_packages()[plugin_id[0]].on_load, plugin_id[1]) end -- load the plugin self:load() From 5aff14773117388172f076192f3f0352031f2a56 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 4 Jul 2025 05:04:23 -0400 Subject: [PATCH 67/74] add better type definitions, and fix a bug related to... lua indexing my beloved --- lua/dep/lazy/loader/init.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lua/dep/lazy/loader/init.lua b/lua/dep/lazy/loader/init.lua index 26e1bc8..886204e 100644 --- a/lua/dep/lazy/loader/init.lua +++ b/lua/dep/lazy/loader/init.lua @@ -3,10 +3,10 @@ local packager = require('dep.package') ---@class lazy_loader ---@field load function the function to load the plugin ----@field command_ids table the commands that have been registered ----@field auto_ids table the auto commands that have been registered ----@field keybind_ids table the keybinds that have been registered ----@field plugin_ids table the plugins that have been registered +---@field command_ids string[] the commands that have been registered +---@field auto_ids number[] the auto commands that have been registered +---@field keybind_ids table[] the keybinds that have been registered +---@field plugin_ids table[] the plugins that have been registered local lazy_loader = {} --- create a new instance of lazy @@ -133,29 +133,29 @@ end --- cleanup all the callbacks, and load the plugin function lazy_loader:cleanup() -- cleanup user commands - for _, command_id in pairs(self.command_ids) do + for _, command_id in ipairs(self.command_ids) do local ok, err = pcall(vim.api.nvim_del_user_command, command_id) if not ok then logger:log("lazy", err or "failed to delete user command") end end -- cleanup auto commands - for _, auto_id in pairs(self.auto_ids) do + for _, auto_id in ipairs(self.auto_ids) do local ok, err = pcall(vim.api.nvim_del_autocmd, auto_id) if not ok then logger:log("lazy", err or "failed to delete auto command") end end -- cleanup keymaps - for _, keybind_id in pairs(self.keybind_ids) do + for _, keybind_id in ipairs(self.keybind_ids) do local ok, err = pcall(vim.keymap.del, keybind_id.mode, keybind_id.bind, {}) if not ok then logger:log("lazy", err or "failed to delete keymap") end end -- cleanup plugins - for _, plugin_id in pairs(self.plugin_ids) do - table.remove(packager.get_packages()[plugin_id[0]].on_load, plugin_id[1]) + for _, plugin_id in ipairs(self.plugin_ids) do + table.remove(packager.get_packages()[plugin_id[1]].on_load, plugin_id[2]) end -- load the plugin self:load() From 3b33a604d82184df14495f96b92ef9102a22a047 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 4 Jul 2025 05:25:06 -0400 Subject: [PATCH 68/74] commands get rerun by default now --- doc/dep.txt | 9 ++++++++- lua/dep/lazy/loader/init.lua | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/doc/dep.txt b/doc/dep.txt index f8b8024..be5151d 100644 --- a/doc/dep.txt +++ b/doc/dep.txt @@ -217,10 +217,17 @@ is equivalent to the following: load:cmd("Command", { callback = function() load:cleanup() + + if (rerun) then + vim.cmd("Command") + end end }) < -If you wish the second argument may be completely ommitted. +If you wish the second argument may be completely ommitted. Note the inclusion +of a `rerun` field, this is a parameter which may be passed into the options table +to re-run the binding after loading the package. You may choose to disable the +built-in logic by passing false. ------------------------------------------------------------------------------ LOAD:AUTO *dep-lazy-loading-api-auto* diff --git a/lua/dep/lazy/loader/init.lua b/lua/dep/lazy/loader/init.lua index 886204e..c901b0a 100644 --- a/lua/dep/lazy/loader/init.lua +++ b/lua/dep/lazy/loader/init.lua @@ -37,6 +37,9 @@ end function lazy_loader:cmd(name, opts) opts = opts or {} + -- rerun is on by default + opts["rerun"] = opts["rerun"] or true + -- load the plugin on completion if not opts["complete"] then opts["complete"] = function(_, line, _) @@ -52,6 +55,11 @@ function lazy_loader:cmd(name, opts) vim.api.nvim_create_user_command(name, opts['callback'] or function(_) self:cleanup() + + -- attempt to rerun the command + if not opts["rerun"] then + pcall(vim.cmd, name) + end end, opts) table.insert(self.command_ids, name) From 9d4322572cde28db9fc188825a4a2674f72eb7b1 Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 4 Jul 2025 05:27:36 -0400 Subject: [PATCH 69/74] should've tested the changes, nvim_create_usercommand doesn't like... unkown options --- lua/dep/lazy/loader/init.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lua/dep/lazy/loader/init.lua b/lua/dep/lazy/loader/init.lua index c901b0a..0603aa1 100644 --- a/lua/dep/lazy/loader/init.lua +++ b/lua/dep/lazy/loader/init.lua @@ -37,8 +37,10 @@ end function lazy_loader:cmd(name, opts) opts = opts or {} - -- rerun is on by default - opts["rerun"] = opts["rerun"] or true + -- move the rerun arg to a seperate variable because keymap.set doesn't like + -- options it doesn't know of + local rerun = opts["rerun"] or true + opts['rerun'] = nil -- load the plugin on completion if not opts["complete"] then @@ -57,7 +59,7 @@ function lazy_loader:cmd(name, opts) self:cleanup() -- attempt to rerun the command - if not opts["rerun"] then + if not rerun then pcall(vim.cmd, name) end end, opts) From 70853bd01e4e1f61b5aeba2e887a34f5b07cd302 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 10 Jul 2025 21:20:53 -0400 Subject: [PATCH 70/74] disabled packages were still getting force loaded --- lua/dep/package.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/dep/package.lua b/lua/dep/package.lua index 7612e58..f674a2c 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -244,6 +244,10 @@ function package:ensureadded(force) --- load a package ---@param pkg package local function loadpkg(pkg) + if not self.enabled then + return false + end + -- make sure to load the dependencies first for _, p in pairs(pkg.requirements) do if not p.loaded then From 9b19b61372ecc08ddaa0ea455c9b68442d7f1613 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 1 Jan 2026 14:34:49 -0500 Subject: [PATCH 71/74] switching from handling plugin downloading on our own to use vim.pack --- LICENSE | 2 +- lua/dep.lua | 55 +++-------- lua/dep/fs.lua | 78 --------------- lua/dep/git.lua | 161 ------------------------------ lua/dep/helpers.lua | 4 - lua/dep/lazy/init.lua | 3 +- lua/dep/log.lua | 12 +-- lua/dep/modules/init.lua | 5 +- lua/dep/package.lua | 2 +- lua/dep/proc.lua | 129 ------------------------ lua/dep/ui/format.lua | 59 ----------- lua/dep/ui/init.lua | 207 --------------------------------------- lua/dep/ui/page.lua | 84 ---------------- tests/dep_spec.lua | 27 ----- 14 files changed, 26 insertions(+), 802 deletions(-) delete mode 100644 lua/dep/fs.lua delete mode 100644 lua/dep/git.lua delete mode 100644 lua/dep/helpers.lua delete mode 100644 lua/dep/proc.lua delete mode 100644 lua/dep/ui/format.lua delete mode 100644 lua/dep/ui/init.lua delete mode 100644 lua/dep/ui/page.lua diff --git a/LICENSE b/LICENSE index f55ba15..3fcee62 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 squibid +Copyright (c) 2023-2026 squibid Copyright (c) 2021-2023 chiya.dev Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/lua/dep.lua b/lua/dep.lua index 9570a52..13c0275 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -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 diff --git a/lua/dep/fs.lua b/lua/dep/fs.lua deleted file mode 100644 index 02a799a..0000000 --- a/lua/dep/fs.lua +++ /dev/null @@ -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 diff --git a/lua/dep/git.lua b/lua/dep/git.lua deleted file mode 100644 index beb39b8..0000000 --- a/lua/dep/git.lua +++ /dev/null @@ -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 diff --git a/lua/dep/helpers.lua b/lua/dep/helpers.lua deleted file mode 100644 index 7bd9273..0000000 --- a/lua/dep/helpers.lua +++ /dev/null @@ -1,4 +0,0 @@ -return { - -- vim.loop was depricated in nvim 0.10 - uv = vim.uv or vim.loop -} diff --git a/lua/dep/lazy/init.lua b/lua/dep/lazy/init.lua index 3ca604e..a0237d6 100644 --- a/lua/dep/lazy/init.lua +++ b/lua/dep/lazy/init.lua @@ -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 diff --git a/lua/dep/log.lua b/lua/dep/log.lua index 467d941..c869caf 100644 --- a/lua/dep/log.lua +++ b/lua/dep/log.lua @@ -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 diff --git a/lua/dep/modules/init.lua b/lua/dep/modules/init.lua index fb05230..f34fba5 100644 --- a/lua/dep/modules/init.lua +++ b/lua/dep/modules/init.lua @@ -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 diff --git a/lua/dep/package.lua b/lua/dep/package.lua index f674a2c..072495a 100644 --- a/lua/dep/package.lua +++ b/lua/dep/package.lua @@ -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 diff --git a/lua/dep/proc.lua b/lua/dep/proc.lua deleted file mode 100644 index 14dd9fd..0000000 --- a/lua/dep/proc.lua +++ /dev/null @@ -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 diff --git a/lua/dep/ui/format.lua b/lua/dep/ui/format.lua deleted file mode 100644 index 63e6068..0000000 --- a/lua/dep/ui/format.lua +++ /dev/null @@ -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 diff --git a/lua/dep/ui/init.lua b/lua/dep/ui/init.lua deleted file mode 100644 index 12f9668..0000000 --- a/lua/dep/ui/init.lua +++ /dev/null @@ -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 diff --git a/lua/dep/ui/page.lua b/lua/dep/ui/page.lua deleted file mode 100644 index 85c568d..0000000 --- a/lua/dep/ui/page.lua +++ /dev/null @@ -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 diff --git a/tests/dep_spec.lua b/tests/dep_spec.lua index a89d198..23ee2a0 100644 --- a/tests/dep_spec.lua +++ b/tests/dep_spec.lua @@ -1,33 +1,6 @@ ---@diagnostic disable: undefined-global, undefined-field -local dep_ui_format = require("dep.ui.format") local dep_spec_man = require("dep.spec") -describe("ui log formatting", function() - it("returns the proper chunks to print a formatted line", function() - assert.same( - { - { "[", "" }, - { "11:22:33", "Boolean" }, - { "] ", "" }, - { "file.lua", "String" }, - { ":", "" }, - { "1", "Number" }, - { ": ", "" }, - { "some fancy message", "" } - }, - dep_ui_format.log_line("[11:22:33] file.lua:1:(vim) some fancy message") - ) - - -- malformed log line - assert.same({}, - dep_ui_format.log_line("11:22:33] file.lua:1:(vim) some fancy message")) - - -- test nil values - assert.same({}, dep_ui_format.log_line("")) - assert.same({}, dep_ui_format.log_line(nil)) - end) -end) - describe("package specification", function() it("gets the package's name", function() assert.equal(dep_spec_man.get_name({ "user/package" }), "package") From 995aaac2a59a02cd3f6fc0b81b62f68c8a9e60c4 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 1 Jan 2026 14:57:39 -0500 Subject: [PATCH 72/74] update plugin directory --- lua/dep.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index 13c0275..a00b32e 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -85,10 +85,10 @@ return function(opts) lazy.setup() -- generate doc tags - vim.cmd.helptags(vim.fn.stdpath('data')..'/site/pack/deps/opt/dep/doc') + vim.cmd.helptags(vim.fn.stdpath('data')..'/site/pack/core/opt/dep/doc') local initialized, err = pcall(function() - packager.set_base_dir(opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/") + packager.set_base_dir(opts.base_dir or vim.fn.stdpath("data").."/site/pack/core/opt/") bench.mark("load", function() -- register all packages local root = packager:new({ From 09f1ae6854141bd43928378051c7e26ad5adb98d Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 1 Jan 2026 17:04:54 -0500 Subject: [PATCH 73/74] try to update plugins --- lua/dep.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index a00b32e..79c0ed4 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -45,10 +45,8 @@ local function synctree(tree, cb) }) end - vim.pack.add(vimspecs, { - load = done, - confirm = false, - }) + vim.pack.add(vimspecs, { load = done, confirm = false }) + vim.pack.update(vimspecs, { force = true }) end --- check if a package should be synced From e23bd277a60e432db051dbf483b672611312fd5f Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 1 Jan 2026 17:26:14 -0500 Subject: [PATCH 74/74] configure packages after install --- lua/dep.lua | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/lua/dep.lua b/lua/dep.lua index 79c0ed4..9d43fe4 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -12,41 +12,46 @@ local M = {} ---@param cb function? callback local function synctree(tree, cb) local progress = 0 - local has_errors = false - local function done(err) + ---@param spec vim.pack.Spec + ---@param path string + local function done(spec, path) + ---@type package + local p = spec.data + _ = path 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 - - for _, p in pairs(tree) do - p:reload() - end - - if cb then - cb() - end + local info = vim.pack.get({ spec.name }, { info = false })[1] + if info.active then + p.exists = true + p:unconfiguretree() + p:runhooks("on_config") + logger:log("config", "package: %s configured", p.id) + p.configured = true end end -- convert our spec to vim.pack.Spec + ---@type 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 + version = p.commit or p.branch, + data = p, }) end + -- install/update all packages vim.pack.add(vimspecs, { load = done, confirm = false }) vim.pack.update(vimspecs, { force = true }) + + -- reload all packages + for _, p in pairs(vimspecs) do + p.data:reload() + end + if cb then cb() end end --- check if a package should be synced