diff --git a/lua/lazy/core/config.lua b/lua/lazy/core/config.lua index 084105e..ad4188f 100644 --- a/lua/lazy/core/config.lua +++ b/lua/lazy/core/config.lua @@ -11,6 +11,7 @@ M.defaults = { ---@type string[] patterns = {}, }, + interactive = true, package_path = vim.fn.stdpath("data") .. "/site/pack/lazy", view = { icons = { diff --git a/lua/lazy/core/loader.lua b/lua/lazy/core/loader.lua index 19559d7..9372463 100644 --- a/lua/lazy/core/loader.lua +++ b/lua/lazy/core/loader.lua @@ -149,6 +149,9 @@ function M.init_plugins() Util.track("plugin_init") for _, name in ipairs(M.loaders.init) do local plugin = Config.plugins[name] + if not plugin then + error(name) + end if plugin.init then Util.track(plugin.name) plugin.init() @@ -247,7 +250,7 @@ end ---@param plugin LazyPlugin function M.packadd(plugin, load_start) if plugin.opt then - vim.cmd.packadd(plugin.pack) + vim.cmd.packadd(plugin.name) M.source_plugin_files(plugin, true) elseif load_start then vim.opt.runtimepath:append(plugin.dir) diff --git a/lua/lazy/core/state.lua b/lua/lazy/core/state.lua index 84b76d1..cb77787 100644 --- a/lua/lazy/core/state.lua +++ b/lua/lazy/core/state.lua @@ -1,13 +1,18 @@ local Cache = require("lazy.core.cache") local Module = require("lazy.core.module") +local Config = require("lazy.core.config") local M = {} M.dirty = true +---@alias CachedPlugin LazyPlugin | {_funs: string[]} +local skip = { installed = true, loaded = true, tasks = true, dirty = true, dir = true } +local funs = { config = true, init = true, run = true } + function M.update_state(check_clean) local Util = require("lazy.core.util") - local Config = require("lazy.core.config") + ---@type table<"opt"|"start", table> local installed = { opt = {}, start = {} } for opt, packs in pairs(installed) do @@ -19,9 +24,11 @@ function M.update_state(check_clean) end for _, plugin in pairs(Config.plugins) do + plugin.opt = plugin.opt == nil and Config.options.opt or plugin.opt local opt = plugin.opt and "opt" or "start" - plugin.installed = installed[opt][plugin.pack] == true - installed[opt][plugin.pack] = nil + plugin.dir = Config.options.package_path .. "/" .. opt .. "/" .. plugin.name + plugin.installed = installed[opt][plugin.name] == true + installed[opt][plugin.name] = nil end if check_clean then @@ -44,37 +51,30 @@ function M.save() if not M.dirty then return end - local Config = require("lazy.core.config") + local Plugin = require("lazy.plugin") ---@class LazyState local state = { - ---@type CachedPlugin[] - plugins = {}, + ---@type table + specs = {}, loaders = require("lazy.core.loader").loaders, config = Config.options, } - ---@alias CachedPlugin LazyPlugin | {_funcs: table} - local skip = { installed = true, loaded = true, tasks = true, dirty = true, dir = true } - local funcount = 0 - - for _, plugin in pairs(Config.plugins) do - ---@type CachedPlugin - local save = {} - table.insert(state.plugins, save) - ---@diagnostic disable-next-line: no-unknown - for k, v in pairs(plugin) do - if type(v) == "function" then - save._funcs = save._funcs or {} - if plugin.modname then - save._funcs[k] = true - else - funcount = funcount + 1 - Cache.set("cache.state.fun." .. funcount, string.dump(v)) - save._funcs[k] = funcount + for _, spec in ipairs(Plugin.specs()) do + state.specs[spec.modname] = spec + for _, plugin in pairs(spec.plugins) do + ---@cast plugin CachedPlugin + for k, v in pairs(plugin) do + if type(v) == "function" then + if funs[k] then + plugin._funs = plugin._funs or {} + table.insert(plugin._funs, k) + end + plugin[k] = nil + elseif skip[k] then + plugin[k] = nil end - elseif not skip[k] then - save[k] = v end end end @@ -82,63 +82,50 @@ function M.save() end function M.load() - ---@type boolean, LazyState + local Plugin = require("lazy.plugin") + local dirty = false + + ---@type boolean, LazyState? local ok, state = pcall(vim.json.decode, Cache.get("cache.state")) - if not ok then - Cache.dirty() - return false + if not (ok and state and vim.deep_equal(Config.options, state.config)) then + dirty = true + state = nil end - local Config = require("lazy.core.config") + local function _loader(modname, modpath) + local spec = state and state.specs[modname] + if (not spec) or Module.is_dirty(modname, modpath) then + dirty = true + vim.schedule(function() + vim.notify("Reloading " .. modname) + end) + return Plugin.Spec.load(modname, modpath) + end + ---@type LazySpec + local loaded = nil - if not vim.deep_equal(Config.options, state.config) then - Cache.dirty() - return false - end - - if Module.is_dirty(Config.options.plugins, Config.paths.main) then - return false - end - - -- plugins - for _, plugin in ipairs(state.plugins) do - Config.plugins[plugin.name] = plugin - plugin.loaded = nil - plugin.dir = Config.options.package_path .. "/" .. (plugin.opt and "opt" or "start") .. "/" .. plugin.pack - if plugin.modname then - if Module.is_dirty(plugin.modname, plugin.modpath) then - return false - end - for fun in pairs(plugin._funcs or {}) do - ---@diagnostic disable-next-line: assign-type-mismatch + for name, plugin in pairs(spec.plugins) do + ---@cast plugin CachedPlugin + for _, fun in ipairs(plugin._funs or {}) do plugin[fun] = function(...) - local mod = Module.load(plugin.modname, plugin.modpath) - for k in pairs(plugin._funcs) do - plugin[k] = mod[k] - end - return plugin[fun](...) - end - end - elseif plugin._funcs then - for fun, id in pairs(plugin._funcs) do - local chunk = assert(Cache.get("cache.state.fun." .. id)) - ---@diagnostic disable-next-line: assign-type-mismatch - plugin[fun] = function(...) - ---@diagnostic disable-next-line: assign-type-mismatch - plugin[fun] = loadstring(chunk) - return plugin[fun](...) + loaded = loaded or Plugin.Spec.load(spec.modname, spec.modpath) + return loaded.plugins[name][fun](...) end end end + return spec end - M.update_state() - -- loaders - require("lazy.core.loader").loaders = state.loaders + Plugin.load(_loader) - M.dirty = false + if state and not dirty then + require("lazy.core.loader").loaders = state.loaders + else + Cache.dirty() + end - return true + M.dirty = dirty + return not dirty end return M diff --git a/lua/lazy/core/util.lua b/lua/lazy/core/util.lua index cbad215..99077d0 100644 --- a/lua/lazy/core/util.lua +++ b/lua/lazy/core/util.lua @@ -27,6 +27,20 @@ function M.track(name, time) end end +-- Fast implementation to check if a table is a list +---@param t table +function M.is_list(t) + local i = 0 + ---@diagnostic disable-next-line: no-unknown + for _ in pairs(t) do + i = i + 1 + if t[i] == nil then + return false + end + end + return true +end + function M.very_lazy() local function _load() vim.defer_fn(function() diff --git a/lua/lazy/init.lua b/lua/lazy/init.lua index 2b18fbc..348a9bd 100644 --- a/lua/lazy/init.lua +++ b/lua/lazy/init.lua @@ -36,21 +36,7 @@ function M.setup(opts) Util.track() Util.track("state") - if not State.load() then - local Plugin = require("lazy.plugin") - vim.schedule(function() - Util.info("Reloading...") - end) - Util.track("reload") - Plugin.reload() - Util.track() - -- if not Config.plugins.lazy then - -- Plugin.plugin({ - -- "folke/lazy.nvim", - -- opt = false, - -- }) - -- end - end + State.load() Util.track() Util.track("install") @@ -59,6 +45,7 @@ function M.setup(opts) vim.cmd("do User LazyInstallPre") require("lazy.manager").install({ wait = true, + show = Config.options.interactive, }) break end @@ -75,7 +62,7 @@ function M.setup(opts) Loader.init_plugins() - Config.plugins.lazy.loaded.time = lazy_delta + Config.plugins["lazy.nvim"].loaded.time = lazy_delta done = true vim.cmd("do User LazyDone") diff --git a/lua/lazy/manager.lua b/lua/lazy/manager.lua index 029aed4..adb0c5d 100644 --- a/lua/lazy/manager.lua +++ b/lua/lazy/manager.lua @@ -66,11 +66,6 @@ function M.run(operation, opts, filter) 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 diff --git a/lua/lazy/plugin.lua b/lua/lazy/plugin.lua index 6d58bf2..dc3c5a4 100644 --- a/lua/lazy/plugin.lua +++ b/lua/lazy/plugin.lua @@ -1,21 +1,17 @@ local Config = require("lazy.core.config") -local Util = require("lazy.util") +local Util = require("lazy.core.util") local Module = require("lazy.core.module") local State = require("lazy.core.state") local M = {} -M.funcs = { run = "run", init = "init", config = "config" } - ---@class LazyPlugin ---@field [1] string ---@field name string display name and name used for plugin config files ----@field pack string package name ---@field uri string ----@field modname? string ----@field modpath? string ---@field branch? string ---@field dir string +---@field enabled? boolean ---@field opt? boolean ---@field init? fun(LazyPlugin) Will always be run ---@field config? fun(LazyPlugin) Will be executed when loading the plugin @@ -32,120 +28,107 @@ M.funcs = { run = "run", init = "init", config = "config" } ---@field dirty? boolean ---@field updated? {from:string, to:string} +---@class LazySpec +---@field modname string +---@field modpath string +---@field plugins table +local Spec = {} + +---@param modname string +---@param modpath string +function Spec.load(modname, modpath) + local self = setmetatable({}, { __index = Spec }) + self.plugins = {} + self.modname = modname + self.modpath = modpath + self:normalize(assert(Module.load(modname, modpath))) + if modname == Config.options.plugins and not self.plugins["lazy.nvim"] then + self:add({ "folke/lazy.nvim", opt = false }) + end + return self +end + ---@param plugin LazyPlugin -function M.plugin(plugin) - local pkg = plugin[1] - if type(pkg) ~= "string" then +function Spec:add(plugin) + if type(plugin[1]) ~= "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 - + plugin.uri = plugin.uri or ("https://github.com/" .. plugin[1] .. ".git") 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() + -- PERF: optimized code to get package name without using lua patterns + local name = plugin[1]:sub(-4) == ".git" and plugin[1]:sub(1, -5) or plugin[1] + local slash = name:reverse():find("/", 1, true) --[[@as number?]] + plugin.name = slash and name:sub(#name - slash + 2) or plugin[1]:gsub("%W+", "_") end - if Config.plugins[plugin.name] and Config.plugins[plugin.name] ~= plugin then - for k, v in pairs(plugin) do - Config.plugins[plugin.name][k] = v + M.process_local(plugin) + local other = self.plugins[plugin.name] + self.plugins[plugin.name] = other and vim.tbl_extend("force", self.plugins[plugin.name], plugin) or plugin + return self.plugins[plugin.name] +end + +---@param spec table +---@param results? string[] +function Spec:normalize(spec, results) + results = results or {} + if type(spec) == "string" then + table.insert(results, self:add({ spec }).name) + elseif #spec > 1 or Util.is_list(spec) then + ---@cast spec table[] + for _, s in ipairs(spec) do + self:normalize(s, results) end - return Config.plugins[plugin.name] - else - Config.plugins[plugin.name] = plugin + elseif spec.enabled ~= false then + local plugin = self:add(spec) + plugin.requires = plugin.requires and self:normalize(plugin.requires, {}) or nil + table.insert(results, plugin.name) end - return plugin + return results 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 + if plugin[1]:find(pattern, 1, true) then + plugin.uri = Config.options.plugins_local.path .. "/" .. plugin.name return end end end -function M.process_config() +---@alias LazySpecLoader fun(modname:string, modpath:string):LazySpec +---@param loader? LazySpecLoader +function M.specs(loader) + loader = loader or Spec.load + ---@type LazySpec[] + local specs = {} + table.insert(specs, loader(Config.options.plugins, Config.paths.main)) Util.lsmod(Config.paths.plugins, function(name, modpath) - local plugin = Config.plugins[name] - if plugin then - local modname = Config.options.plugins .. "." .. name - local ok, spec = pcall(Module.load, modname, modpath) - if ok and spec then - ---@diagnostic disable-next-line: no-unknown - for k, v in pairs(spec) do - if k == "requires" then - plugin.requires = M.normalize(v) - elseif type(v) ~= "function" or M.funcs[k] then - ---@diagnostic disable-next-line: no-unknown - plugin[k] = v - end - end - plugin.modname = modname - plugin.modpath = modpath - M.plugin(plugin) - else - Util.error("Failed to load " .. modname .. "\n" .. spec) - end - end + table.insert(specs, loader(Config.options.plugins .. "." .. name, modpath)) end) + return specs end -function M.reload() +---@param loader? LazySpecLoader +function M.load(loader) + Util.track("specs") + local specs = M.specs(loader) + Util.track() + Config.plugins = {} - M.normalize(assert(Module.load(Config.options.plugins, Config.paths.main))) - if not Config.plugins.lazy then - M.plugin({ - "folke/lazy.nvim", - opt = false, - }) - end - - M.process_config() - for _, plugin in pairs(Config.plugins) do - if plugin.opt == nil then - plugin.opt = Config.options.opt + for _, spec in ipairs(specs) do + for _, plugin in pairs(spec.plugins) do + local other = Config.plugins[plugin.name] + Config.plugins[plugin.name] = other and vim.tbl_extend("force", other, plugin) or plugin end - plugin.dir = Config.options.package_path .. "/" .. (plugin.opt and "opt" or "start") .. "/" .. plugin.pack - M.process_local(plugin) end + + Util.track("state") State.update_state() + Util.track() 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 - spec.requires = M.normalize(spec.requires) - end - table.insert(results, spec.name) - end - return results -end - --- profile(M.rebuild, 1000, true) +M.Spec = Spec return M diff --git a/lua/lazy/task.lua b/lua/lazy/task.lua index 7d9dbb5..3ba2296 100644 --- a/lua/lazy/task.lua +++ b/lua/lazy/task.lua @@ -42,6 +42,10 @@ end function Task:_done() self.running = false vim.cmd("do User LazyRender") + vim.api.nvim_exec_autocmds("User", { + pattern = "LazyPlugin" .. self.type:sub(1, 1):upper() .. self.type:sub(2), + data = { plugin = self.plugin.name }, + }) end function Task:clean() diff --git a/lua/lazy/view/render.lua b/lua/lazy/view/render.lua index f3588a5..8cb4278 100644 --- a/lua/lazy/view/render.lua +++ b/lua/lazy/view/render.lua @@ -153,9 +153,9 @@ function M:reason(plugin) if modname then modname = modname:gsub("/", ".") end - local pack = source:match("/([^/]-)/lua") + local name = source:match("/([^/]-)/lua") for _, other in pairs(Config.plugins) do - if (modname and other.modname == modname) or (pack and other.pack == pack) then + if (modname and other.modname == modname) or (name and other.name == name) then reason.plugin = other.name reason.source = nil break