From e73626a3444cef85c6e191989b97d5deb8d2befd Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Sun, 20 Nov 2022 22:33:47 +0100 Subject: [PATCH] feat: initial commit --- lua/lazy/cache.lua | 87 +++++++++++++++ lua/lazy/config.lua | 68 ++++++++++++ lua/lazy/init.lua | 70 ++++++++++++ lua/lazy/loader.lua | 227 +++++++++++++++++++++++++++++++++++++++ lua/lazy/manager.lua | 137 +++++++++++++++++++++++ lua/lazy/plugin.lua | 141 ++++++++++++++++++++++++ lua/lazy/util.lua | 186 ++++++++++++++++++++++++++++++++ lua/lazy/view/render.lua | 136 +++++++++++++++++++++++ 8 files changed, 1052 insertions(+) create mode 100644 lua/lazy/cache.lua create mode 100644 lua/lazy/config.lua create mode 100644 lua/lazy/init.lua create mode 100644 lua/lazy/loader.lua create mode 100644 lua/lazy/manager.lua create mode 100644 lua/lazy/plugin.lua create mode 100644 lua/lazy/util.lua create mode 100644 lua/lazy/view/render.lua diff --git a/lua/lazy/cache.lua b/lua/lazy/cache.lua new file mode 100644 index 0000000..5fbd2c0 --- /dev/null +++ b/lua/lazy/cache.lua @@ -0,0 +1,87 @@ +local cache_file = vim.fn.stdpath("cache") .. "/lazy/cache.mpack" +vim.fn.mkdir(vim.fn.fnamemodify(cache_file, ":p:h"), "p") + +local M = {} +---@alias CacheEntry {hash:string, chunk:string, used:boolean} + +---@type table +M.cache = {} +M.dirty = false +M.did_setup = false + +function M.hash(modpath) + local stat = vim.loop.fs_stat(modpath) + if stat then + return stat.mtime.sec .. stat.mtime.nsec .. stat.size + end + error("Could not hash " .. modpath) +end + +function M.load_cache() + local f = io.open(cache_file, "rb") + if f then + M.cache = vim.mpack.decode(f:read("*a")) or {} + f:close() + end +end + +function M.save_cache() + if M.dirty then + for key, entry in pairs(M.cache) do + if not entry.used then + M.cache[key] = nil + end + entry.used = nil + end + local f = assert(io.open(cache_file, "wb")) + f:write(vim.mpack.encode(M.cache)) + f:close() + end +end + +function M.setup() + M.load_cache() + vim.api.nvim_create_autocmd("VimLeave", { + callback = function() + M.save_cache() + end, + }) +end + +function M.load(modpath, modname) + if not M.did_setup then + M.setup() + M.did_setup = true + end + if type(package.loaded[modname]) ~= "table" then + ---@type fun()?, string? + local chunk, err + local entry = M.cache[modname] + + if entry and M.hash(modpath) == entry.hash then + entry.used = true + chunk, err = loadstring(entry.chunk, "@" .. modpath) + end + + -- not cached, or failed to load chunk + if not chunk then + vim.schedule(function() + vim.notify("not cached") + end) + chunk, err = loadfile(modpath) + if chunk then + M.cache[modname] = { hash = M.hash(modpath), chunk = string.dump(chunk, true), used = true } + M.dirty = true + end + end + + if not chunk then + error(err) + end + ---@diagnostic disable-next-line: no-unknown + package.loaded[modname] = chunk() + end + return package.loaded[modname] +end + +return M diff --git a/lua/lazy/config.lua b/lua/lazy/config.lua new file mode 100644 index 0000000..a1c0c62 --- /dev/null +++ b/lua/lazy/config.lua @@ -0,0 +1,68 @@ +local Util = require("lazy.util") + +local M = {} + +---@class LazyConfig +M.defaults = { + opt = true, + plugins = {}, + plugins_local = { + path = vim.fn.expand("~/projects"), + patterns = { + "folke", + }, + }, + plugins_config = { + module = "plugins", + path = vim.fn.stdpath("config") .. "/lua/plugins", + }, + package_path = vim.fn.stdpath("data") .. "/site/pack/lazy", +} + +M.ns = vim.api.nvim_create_namespace("lazy") + +---@type table +M.plugins = {} + +---@type LazyConfig +M.options = {} + +---@type table +M.has_config = {} + +---@param opts? LazyConfig +function M.setup(opts) + M.options = vim.tbl_deep_extend("force", M.defaults, opts or {}) + + vim.fn.mkdir(M.options.package_path, "p") + + for _, entry in ipairs(Util.scandir(M.options.plugins_config.path)) do + local name, modpath + + if entry.type == "file" then + modpath = entry.path + name = entry.name:match("(.*)%.lua") + elseif entry.type == "directory" then + modpath = M.options.plugins_config.path .. "/" .. entry.name .. "/init.lua" + if vim.loop.fs_stat(modpath) then + name = entry.name + end + end + + if name then + M.has_config[M.options.plugins_config.module .. "." .. name] = modpath + end + end + + vim.api.nvim_create_autocmd("User", { + pattern = "VeryLazy", + once = true, + callback = function() + -- require("lazy.view").setup() + end, + }) + + Util.very_lazy() +end + +return M diff --git a/lua/lazy/init.lua b/lua/lazy/init.lua new file mode 100644 index 0000000..dc6a333 --- /dev/null +++ b/lua/lazy/init.lua @@ -0,0 +1,70 @@ +local M = {} + +---@param opts? LazyConfig +function M.setup(opts) + --FIXME: preload() + + local Util = require("lazy.util") + local Config = require("lazy.config") + local Plugin = require("lazy.plugin") + + Util.track("lazy_setup") + + Util.track("lazy_config") + Config.setup(opts) + Util.track() + + Util.track("plugin_normalize") + Plugin.normalize(Config.options.plugins) + if not Config.plugins.lazy then + Plugin.plugin({ + "folke/lazy.nvim", + opt = false, + }) + end + Util.track() + + Util.track("plugin_process") + Plugin.process() + Util.track() + + Util.track("lazy_install") + for _, plugin in pairs(Config.plugins) do + if not plugin.installed then + -- require("lazy.manager").install({ + -- wait = true, + -- }) + break + end + end + Util.track() + + Util.track("loader_setup") + local Loader = require("lazy.loader") + Loader.setup() + Util.track() + + Loader.init_plugins() + + Util.track() -- end setup + vim.cmd("do User LazyDone") +end + +function M.stats() + local ret = { + count = 0, + loaded = 0, + } + + for _, plugin in pairs(require("lazy.config").plugins) do + ret.count = ret.count + 1 + + if plugin.loaded then + ret.loaded = ret.loaded + 1 + end + end + + return ret +end + +return M diff --git a/lua/lazy/loader.lua b/lua/lazy/loader.lua new file mode 100644 index 0000000..010dfe9 --- /dev/null +++ b/lua/lazy/loader.lua @@ -0,0 +1,227 @@ +Util = require("lazy.util") +Config = require("lazy.config") + +local M = {} + +---@alias LoaderType "event"|"ft"|"module"|"keys"|"cmd" +---@type LoaderType[] +M.types = { + "event", + "ft", + "module", + "keys", + "cmd", +} + +---@type table> +M.loaders = {} + +for _, type in ipairs(M.types) do + M.loaders[type] = {} +end + +---@type LazyPlugin[] +M.need_setup = {} + +---@param plugin LazyPlugin +function M.add(plugin) + if plugin.init or plugin.opt == false and plugin.config then + table.insert(M.need_setup, plugin) + end + + for _, loader_type in ipairs(M.types) do + ---@type string[]|string + local loaders = plugin[loader_type] + if loaders then + loaders = type(loaders) == "table" and loaders or { loaders } + ---@cast loaders string[] + for _, loader in ipairs(loaders) do + if not M.loaders[loader_type][loader] then + M.loaders[loader_type][loader] = {} + end + table.insert(M.loaders[loader_type][loader], plugin.name) + end + end + end +end + +function M.setup() + local group = vim.api.nvim_create_augroup("lazy_loader", { + clear = true, + }) + + -- modules + table.insert(package.loaders, 2, M.module) + + -- events + Util.track("loader_events") + for event, plugins in pairs(M.loaders.event) do + if event == "VimEnter" and vim.v.vim_did_enter == 1 then + M.load(plugins) + else + local user_event = event:match("User (.*)") + vim.api.nvim_create_autocmd(user_event and "User" or event, { + once = true, + group = group, + pattern = user_event, + callback = function() + Util.track("event: " .. (user_event or event)) + M.load(plugins) + Util.track() + end, + }) + end + end + Util.track() + + -- filetypes + Util.track("loader_filetypes") + for ft, plugins in pairs(M.loaders.ft) do + vim.api.nvim_create_autocmd("FileType", { + once = true, + pattern = ft, + group = group, + callback = function() + Util.track("filetype: " .. ft) + M.load(plugins) + Util.track() + end, + }) + end + Util.track() + + -- keys + Util.track("loader_keys") + for keys, plugins in pairs(M.loaders.keys or {}) do + vim.keymap.set("n", keys, function() + vim.keymap.del("n", keys) + Util.track("keys: " .. keys) + M.load(plugins) + vim.api.nvim_input(keys) + Util.track() + end) + end + Util.track() + + -- commands + Util.track("loader_commands") + for cmd, plugins in pairs(M.loaders.cmd or {}) do + vim.api.nvim_create_user_command(cmd, function(event) + vim.api.nvim_del_user_command(cmd) + Util.track("cmd: " .. cmd) + M.load(plugins) + vim.cmd( + ("%s %s%s%s %s"):format( + event.mods or "", + event.line1 == event.line2 and "" or event.line1 .. "," .. event.line2, + cmd, + event.bang and "!" or "", + event.args + ) + ) + Util.track() + end, { + bang = true, + nargs = "*", + }) + end + Util.track() +end + +function M.init_plugins() + Util.track("loader_plugin_init") + for _, plugin in ipairs(M.need_setup) do + if plugin.init then + Util.track(plugin.name) + plugin.init() + Util.track() + end + if plugin.opt == false then + M.load(plugin) + end + end + Util.track() +end + +---@param modname string +function M.module(modname) + local idx = modname:find(".", 1, true) or #modname + 1 + + while idx do + local name = modname:sub(1, idx - 1) + local plugins = M.loaders.module[name] + if plugins then + M.load(plugins) + M.loaders.module[name] = nil + end + idx = modname:find(".", idx + 1, true) + end + + ---@diagnostic disable-next-line: no-unknown + local mod = package.loaded[modname] + if type(mod) == "table" then + return function() + return mod + end + end +end + +---@param plugins string|LazyPlugin|string[]|LazyPlugin[] +function M.load(plugins) + if type(plugins) == "string" or plugins.name then + plugins = { plugins } + end + + for _, plugin in ipairs(plugins) do + if type(plugin) == "string" then + plugin = Config.plugins[plugin] + end + ---@cast plugin LazyPlugin + + if not plugin.loaded then + plugin.loaded = true + + Util.track(plugin.name) + M.packadd(plugin) + + if plugin.requires then + M.load(plugin.requires) + end + + if plugin.config then + plugin.config() + end + + Util.track() + end + end +end + +---@param plugin LazyPlugin +function M.packadd(plugin) + if plugin.opt then + vim.cmd.packadd(plugin.pack) + M.source_plugin_files(plugin, true) + else + vim.opt.runtimepath:append(plugin.dir) + M.source_plugin_files(plugin) + M.source_plugin_files(plugin, true) + end +end + +---@param plugin LazyPlugin +---@param after? boolean +function M.source_plugin_files(plugin, after) + local pattern = (after and "/after" or "") .. ("/plugin/" .. "**/*.\\(vim\\|lua\\)") + + local _, entries = pcall(vim.fn.glob, plugin.dir .. "/" .. pattern, false, true) + + if entries then + ---@cast entries string[] + for _, file in ipairs(entries) do + vim.cmd("silent source " .. file) + end + end +end + +return M diff --git a/lua/lazy/manager.lua b/lua/lazy/manager.lua new file mode 100644 index 0000000..acef02c --- /dev/null +++ b/lua/lazy/manager.lua @@ -0,0 +1,137 @@ +local Config = require("lazy.config") +local Task = require("lazy.task") +local Runner = require("lazy.runner") +local Util = require("lazy.util") + +local M = {} + +---@alias ManagerOpts {wait?: boolean, plugins?: LazyPlugin[], clear?: boolean, show?: boolean} + +---@param operation TaskType +---@param opts? ManagerOpts +---@param filter? fun(plugin:LazyPlugin):boolean? +function M.run(operation, opts, filter) + opts = opts or {} + local plugins = opts.plugins or Config.plugins + + if opts.clear then + M.clear() + end + + if opts.show then + require("lazy.view").show() + end + + ---@type Runner + local runner = Runner.new() + + local on_done = function() + vim.cmd([[do User LazyRender]]) + end + + -- install missing plugins + for _, plugin in pairs(plugins) do + if filter == nil or filter(plugin) then + runner:add(Task.new(plugin, operation)) + end + end + + if runner:is_empty() then + return on_done() + end + + vim.cmd([[do User LazyRender]]) + + -- wait for install to finish + runner:wait(function() + -- check if we need to do any post-install hooks + for _, plugin in ipairs(runner:plugins()) do + if plugin.dirty and (plugin.opt == false or plugin.run) then + runner:add(Task.new(plugin, "run")) + end + plugin.dirty = false + end + -- wait for post-install to finish + runner:wait(on_done) + end) + + -- auto show if there are tasks running + if opts.show == nil then + require("lazy.view").show() + end + + if opts.wait then + runner:wait() + end + return runner +end + +---@param opts? ManagerOpts +function M.install(opts) + ---@param plugin LazyPlugin + M.run("install", opts, function(plugin) + return plugin.uri and not plugin.installed + end) +end + +---@param opts? ManagerOpts +function M.update(opts) + ---@param plugin LazyPlugin + M.run("update", opts, function(plugin) + return plugin.uri and plugin.installed + end) +end + +---@param opts? ManagerOpts +function M.clean(opts) + M.check_clean() + ---@param plugin LazyPlugin + M.run("clean", opts, function(plugin) + return plugin.uri == nil and plugin.installed + end) +end + +function M.check_clean() + ---@type table + local packs = {} + for _, plugin in pairs(Config.plugins) do + packs[plugin.pack] = plugin.opt + end + + for _, opt in ipairs({ "opt", "start" }) do + local site = Config.options.package_path .. "/" .. opt + if Util.file_exists(site) then + for _, pack in ipairs(Util.scandir(site)) do + if packs[pack.name] ~= (opt == "opt") then + ---@type LazyPlugin + local plugin = { + name = pack.name, + pack = pack.name, + dir = site .. "/" .. pack.name, + opt = opt == "opt", + installed = true, + } + Config.plugins[pack.name] = plugin + end + end + end + end +end + +function M.clear() + for pack, plugin in pairs(Config.plugins) do + -- clear finished tasks + if plugin.tasks then + ---@param task Task + plugin.tasks = vim.tbl_filter(function(task) + return task.running + end, plugin.tasks) + end + -- clear cleaned plugins + if plugin.uri == nil and not plugin.installed then + Config.plugins[pack] = nil + end + end +end + +return M diff --git a/lua/lazy/plugin.lua b/lua/lazy/plugin.lua new file mode 100644 index 0000000..32f40ba --- /dev/null +++ b/lua/lazy/plugin.lua @@ -0,0 +1,141 @@ +local Config = require("lazy.config") +local Util = require("lazy.util") +local Loader = require("lazy.loader") +local Cache = require("lazy.cache") + +local M = {} + +---@class LazyPlugin: {[1]: string} +---@field uri string +---@field as? string +---@field branch? string +---@field dir string +---@field opt? boolean +---@field name string display name and name used for plugin config files +---@field pack string package name +---@field init? fun(LazyPlugin) Will always be run +---@field config? fun(LazyPlugin) Will be executed when loading the plugin +---@field event? string|string[] +---@field cmd? string|string[] +---@field ft? string|string[] +---@field module? string|string[] +---@field keys? string|string[] +---@field requires? string[] +---@field loaded? boolean +---@field installed? boolean +---@field run? string|fun() +---@field tasks? Task[] +---@field dirty? boolean + +---@param plugin LazyPlugin +function M.plugin(plugin) + local pkg = plugin[1] + if type(pkg) ~= "string" then + Util.error("Invalid plugin spec " .. vim.inspect(plugin)) + end + + plugin.uri = plugin.uri or ("https://github.com/" .. pkg .. ".git") + plugin.pack = plugin.pack or plugin.name + + if not plugin.name then + local name = plugin.uri:gsub("%.git$", ""):match("/([^/]+)$") + plugin.pack = name + if not name then + name = pkg:gsub("%W+", "_") + end + name = name:gsub("[%.%-]n?vim$", "") + name = name:gsub("^n?vim[%-%.]", "") + name = name:gsub("%.lua$", "") + name = name:gsub("%.", "_") + plugin.name = name:lower() + end + + if Config.plugins[plugin.name] then + for k, v in ipairs(plugin) do + Config.plugins[plugin.name][k] = v + end + return Config.plugins[plugin.name] + else + Config.plugins[plugin.name] = plugin + end + return plugin +end + +---@param plugin LazyPlugin +function M.process_local(plugin) + for _, pattern in ipairs(Config.options.plugins_local.patterns) do + if plugin[1]:find(pattern) then + plugin.uri = Config.options.plugins_local.path .. "/" .. plugin.pack + return + end + end +end + +---@param plugin LazyPlugin +function M.process_config(plugin) + local name = plugin.name + local modname = Config.options.plugins_config.module .. "." .. name + + local file = Config.has_config[modname] + if file then + -- use dofile and then add to modules. Since we know where to look, this is faster + local ok, spec = pcall(Cache.load, file, modname) + if ok then + -- add to loaded modules + if spec.requires then + spec.requires = M.normalize(spec.requires) + end + + ---@diagnostic disable-next-line: no-unknown + for k, v in pairs(spec) do + ---@diagnostic disable-next-line: no-unknown + plugin[k] = v + end + M.plugin(plugin) + else + Util.error("Failed to load plugin config for " .. name .. "\n" .. spec) + end + end +end + +---@param spec table +---@param results? LazyPlugin[] +function M.normalize(spec, results) + results = results or {} + if type(spec) == "string" then + table.insert(results, M.plugin({ spec }).name) + elseif #spec > 1 or vim.tbl_islist(spec) then + ---@cast spec LazyPlugin[] + for _, s in ipairs(spec) do + M.normalize(s, results) + end + else + ---@cast spec LazyPlugin + spec = M.plugin(spec) + + if spec.requires then + -- TODO: fix multiple requires in different packages + spec.requires = M.normalize(spec.requires) + end + table.insert(results, spec.name) + end + return results +end + +function M.process() + for _, plugin in pairs(Config.plugins) do + M.process_config(plugin) + end + + for _, plugin in pairs(Config.plugins) do + if plugin.opt == nil then + plugin.opt = Config.options.opt + end + plugin.dir = Config.options.package_path .. "/" .. (plugin.opt and "opt" or "start") .. "/" .. plugin.pack + plugin.installed = Util.file_exists(plugin.dir) + M.process_local(plugin) + Loader.add(plugin) + end +end + +return M diff --git a/lua/lazy/util.lua b/lua/lazy/util.lua new file mode 100644 index 0000000..130d93a --- /dev/null +++ b/lua/lazy/util.lua @@ -0,0 +1,186 @@ +local M = {} + +---@alias LazyProfile {name: string, time: number, [number]:LazyProfile} + +---@type LazyProfile[] +M._profiles = { { name = "lazy" } } + +---@param name string? +---@param time number? +function M.track(name, time) + if name then + local entry = { + name = name, + time = time or M.time(), + } + table.insert(M._profiles[#M._profiles], entry) + + if not time then + table.insert(M._profiles, entry) + end + else + local entry = table.remove(M._profiles) + entry.time = M.time() - entry.time + end +end + +function M.time() + return vim.loop.hrtime() / 1000000 +end + +function M.file_exists(file) + return vim.loop.fs_stat(file) ~= nil +end + +---@param ms number +---@param fn fun() +function M.throttle(ms, fn) + local timer = vim.loop.new_timer() + local running = false + local first = true + + return function() + if not running then + if first then + fn() + first = false + end + + timer:start(ms, 0, function() + running = false + vim.schedule(fn) + end) + + running = true + end + end +end + +function M.very_lazy() + local function _load() + vim.defer_fn(function() + vim.cmd("do User VeryLazy") + end, 100) + end + + vim.api.nvim_create_autocmd("User", { + pattern = "LazyDone", + once = true, + callback = function() + if vim.v.vim_did_enter == 1 then + _load() + else + vim.api.nvim_create_autocmd("VimEnter", { + once = true, + callback = function() + _load() + end, + }) + end + end, + }) +end + +---@param path string +function M.scandir(path) + ---@type {name: string, path: string, type: "file"|"directory"|"link"}[] + local ret = {} + + local dir = vim.loop.fs_opendir(path, nil, 100) + + if dir then + ---@type {name: string, path: string, type: "file"|"directory"|"link"}[] + local entries = vim.loop.fs_readdir(dir) + while entries do + for _, entry in ipairs(entries) do + entry.path = path .. "/" .. entry.name + table.insert(ret, entry) + end + entries = vim.loop.fs_readdir(dir) + end + vim.loop.fs_closedir(dir) + end + + return ret +end + +function M.profile() + local lines = { "# Profile" } + + ---@param entry LazyProfile + local function _profile(entry, depth) + if entry.time < 0.5 then + -- Nothing + end + + table.insert( + lines, + (" "):rep(depth) .. "- " .. entry.name .. ": **" .. math.floor((entry.time or 0) * 100) / 100 .. "ms**" + ) + + for _, child in ipairs(entry) do + _profile(child, depth + 1) + end + end + + for _, entry in ipairs(M._profiles[1]) do + _profile(entry, 1) + end + + M.markdown(lines) +end + +---@return string? +function M.head(file) + local f = io.open(file) + if f then + f:close() + return f:read() + end +end + +---@return {branch: string, hash:string}? +function M.git_info(dir) + if M.head(dir .. "/.git/HEAD") then + ---@type string, string + local ref, branch = slot1:match("ref: (refs/heads/(.*))") + + if ref then + return { + branch = branch, + hash = M.head(dir .. "/.git/" .. ref), + } + end + end +end + +---@param msg string|string[] +---@param opts? table +function M.markdown(msg, opts) + if type(msg) == "table" then + msg = table.concat(msg, "\n") or msg + end + + vim.notify( + msg, + vim.log.levels.INFO, + vim.tbl_deep_extend("force", { + title = "lazy.nvim", + on_open = function(win) + vim.wo[win].conceallevel = 3 + vim.wo[win].concealcursor = "n" + vim.wo[win].spell = false + + vim.treesitter.start(vim.api.nvim_win_get_buf(win), "markdown") + end, + }, opts or {}) + ) +end + +function M.error(msg) + vim.notify(msg, vim.log.levels.ERROR, { + title = "lazy.nvim", + }) +end + +return M diff --git a/lua/lazy/view/render.lua b/lua/lazy/view/render.lua new file mode 100644 index 0000000..114c6f4 --- /dev/null +++ b/lua/lazy/view/render.lua @@ -0,0 +1,136 @@ +local Config = require("lazy.config") +local Util = require("lazy.util") +local Manager = require("lazy.manager") +local Sections = require("lazy.view.sections") + +local Text = require("lazy.view.text") + +---@class Render:Text +---@field buf buffer +---@field win window +---@field padding number +---@field plugins LazyPlugin[] +---@field progress {total:number, done:number} +local M = setmetatable({}, { __index = Text }) + +function M.render_plugins(buf, win, padding) + local self = setmetatable({}, { __index = M }) + self._lines = {} + self.buf = buf + self.win = win + self.padding = padding + Manager.check_clean() + + self.plugins = vim.tbl_values(Config.plugins) + table.sort(self.plugins, function(a, b) + return a.name < b.name + end) + + self.progress = { + total = 0, + done = 0, + } + + for _, plugin in ipairs(self.plugins) do + if plugin.tasks then + for _, task in ipairs(plugin.tasks) do + self.progress.total = self.progress.total + 1 + if not task.running then + self.progress.done = self.progress.done + 1 + end + end + end + end + + self:title() + + for _, section in ipairs(Sections) do + self:section(section) + end + + self:trim() + self:render(buf, padding) + return self +end + +function M:title() + self:append("Lazy", "LazyH1") + if self.progress.done < self.progress.total then + self:append(" (" .. self.progress.done .. "/" .. self.progress.total .. ")", "LazyMuted"):nl() + self:progressbar() + else + self:append(" (" .. #self.plugins .. ")", "LazyMuted"):nl() + end + self:nl() +end + +function M:progressbar() + local width = vim.api.nvim_win_get_width(self.win) - 2 * self.padding + local done = math.floor((self.progress.done / self.progress.total) * width + 0.5) + self:append("", { + virt_text_win_col = self.padding, + virt_text = { { string.rep("─", done), "LazyProgressDone" } }, + }) + self:append("", { + virt_text_win_col = done + self.padding, + virt_text = { { string.rep("─", width - done), "LazyProgressTodo" } }, + }) +end + +---@param section LazySection +function M:section(section) + ---@type LazyPlugin[] + local section_plugins = {} + ---@param plugin LazyPlugin + self.plugins = vim.tbl_filter(function(plugin) + if section.filter(plugin) then + table.insert(section_plugins, plugin) + return false + end + return true + end, self.plugins) + + local count = #section_plugins + if count > 0 then + self:append(section.title, "LazyH2"):append(" (" .. count .. ")", "LazyMuted"):nl() + for _, plugin in ipairs(section_plugins) do + self:plugin(plugin) + end + self:nl() + end +end + +---@param plugin LazyPlugin +function M:plugin(plugin) + self:append(" - ", "LazySpecial"):append(plugin.name) + if plugin.tasks then + for _, task in ipairs(plugin.tasks) do + if task.running then + self:append(" [" .. task.type .. "] ", "Identifier") + self:append(task.status, "LazyMuted") + elseif task.error then + local lines = vim.split(vim.trim(task.error), "\n") + self:append(" [" .. task.type .. "] ", "Identifier") + for l, line in ipairs(lines) do + self:append(line, "LazyError") + if l ~= #lines then + self:nl() + end + end + end + end + end + self:nl() + -- self:details(plugin) +end + +---@param plugin LazyPlugin +function M:details(plugin) + local git = Util.git_info(plugin.dir) + if git then + self:append(git.branch) + end + self:nl() +end + +return M