mirror of
				https://github.com/folke/lazy.nvim.git
				synced 2025-10-31 06:21:14 +00:00 
			
		
		
		
	feat: lazy caching now works with functions that have upvalues
This commit is contained in:
		
					parent
					
						
							
								48199f8031
							
						
					
				
			
			
				commit
				
					
						fe33e4e3dd
					
				
			
		
					 9 changed files with 161 additions and 187 deletions
				
			
		|  | @ -11,6 +11,7 @@ M.defaults = { | |||
|     ---@type string[] | ||||
|     patterns = {}, | ||||
|   }, | ||||
|   interactive = true, | ||||
|   package_path = vim.fn.stdpath("data") .. "/site/pack/lazy", | ||||
|   view = { | ||||
|     icons = { | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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<string,boolean>> | ||||
|   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<string, LazySpec> | ||||
|     specs = {}, | ||||
|     loaders = require("lazy.core.loader").loaders, | ||||
|     config = Config.options, | ||||
|   } | ||||
| 
 | ||||
|   ---@alias CachedPlugin LazyPlugin | {_funcs: table<string, number|boolean>} | ||||
|   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 | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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") | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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<string, LazyPlugin> | ||||
| 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 | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue