mirror of
				https://github.com/folke/lazy.nvim.git
				synced 2025-10-30 22:11:09 +00:00 
			
		
		
		
	perf: new file-based cache that ensures correct rtp order (#532)
* perf: new file-based cache that ensures rtp is alweays correct and will cache all files, including those after startup * refactor: new cache * test: fix tests * fix(cache): cache file names on Windows * feat(cache): allow to disable the cache * docs: updated cache settings
This commit is contained in:
		
					parent
					
						
							
								06f835d0b4
							
						
					
				
			
			
				commit
				
					
						462633bae1
					
				
			
		
					 10 changed files with 354 additions and 549 deletions
				
			
		
							
								
								
									
										10
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
										
									
									
									
								
							|  | @ -347,7 +347,7 @@ return { | |||
|       init = " ", | ||||
|       import = " ", | ||||
|       keys = " ", | ||||
|       lazy = "鈴 ", | ||||
|       lazy = " ", | ||||
|       loaded = "●", | ||||
|       not_loaded = "○", | ||||
|       plugin = " ", | ||||
|  | @ -409,14 +409,6 @@ return { | |||
|   performance = { | ||||
|     cache = { | ||||
|       enabled = true, | ||||
|       path = vim.fn.stdpath("cache") .. "/lazy/cache", | ||||
|       -- Once one of the following events triggers, caching will be disabled. | ||||
|       -- To cache all modules, set this to `{}`, but that is not recommended. | ||||
|       -- The default is to disable on: | ||||
|       --  * VimEnter: not useful to cache anything else beyond startup | ||||
|       --  * BufReadPre: this will be triggered early when opening a file from the command line directly | ||||
|       disable_events = { "UIEnter", "BufReadPre" }, | ||||
|       ttl = 3600 * 24 * 5, -- keep unused modules for up to 5 days | ||||
|     }, | ||||
|     reset_packpath = true, -- reset the package path to improve startup time | ||||
|     rtp = { | ||||
|  |  | |||
|  | @ -1,240 +1,264 @@ | |||
| local ffi = require("ffi") | ||||
| ---@diagnostic disable-next-line: no-unknown | ||||
| local uv = vim.loop | ||||
| 
 | ||||
| local M = {} | ||||
| M.dirty = false | ||||
| M.VERSION = "1" .. jit.version | ||||
| 
 | ||||
| ---@class LazyCacheConfig | ||||
| M.config = { | ||||
|   enabled = true, | ||||
|   path = vim.fn.stdpath("cache") .. "/lazy/cache", | ||||
|   -- Once one of the following events triggers, caching will be disabled. | ||||
|   -- To cache all modules, set this to `{}`, but that is not recommended. | ||||
|   -- The default is to disable on: | ||||
|   --  * VimEnter: not useful to cache anything else beyond startup | ||||
|   --  * BufReadPre: this will be triggered early when opening a file from the command line directly | ||||
|   disable_events = { "UIEnter", "BufReadPre" }, | ||||
|   ttl = 3600 * 24 * 5, -- keep unused modules for up to 5 days | ||||
| } | ||||
| M.debug = false | ||||
| 
 | ||||
| ---@type CacheHash | ||||
| local cache_hash | ||||
| 
 | ||||
| ---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number} | ||||
| ---@alias CacheEntry {hash:CacheHash, modpath:string, chunk:string, used:number} | ||||
| ---@type table<string,CacheEntry?> | ||||
| M.cache = {} | ||||
| M.enabled = true | ||||
| ---@type string[] | ||||
| M.rtp = nil | ||||
| M.rtp_total = 0 | ||||
| M.stats = { | ||||
|   find = { total = 0, time = 0, rtp = 0, unloaded = 0, index = 0, stat = 0, not_found = 0 }, | ||||
|   autoload = { total = 0, time = 0 }, | ||||
| ---@alias CacheEntry {hash:CacheHash, modpath:string, chunk:string} | ||||
| 
 | ||||
| ---@class CacheFindOpts | ||||
| ---@field rtp? boolean Search for modname in the runtime path (defaults to `true`) | ||||
| ---@field patterns? string[] Paterns to use (defaults to `{"/init.lua", ".lua"}`) | ||||
| ---@field paths? string[] Extra paths to search for modname | ||||
| 
 | ||||
| M.VERSION = 1 | ||||
| M.path = vim.fn.stdpath("cache") .. "/lazy/luac" | ||||
| M.enabled = false | ||||
| M.stats = { total = 0, time = 0, index = 0, stat = 0, not_found = 0 } | ||||
| 
 | ||||
| ---@class ModuleCache | ||||
| ---@field _rtp string[] | ||||
| ---@field _rtp_key string | ||||
| local Cache = { | ||||
|   ---@type table<string, table<string,true>> | ||||
|   _topmods = {}, | ||||
|   _loadfile = loadfile, | ||||
| } | ||||
| M.me = debug.getinfo(1, "S").source:sub(2) | ||||
| M.me = vim.fn.fnamemodify(M.me, ":p:h:h:h:h"):gsub("\\", "/") | ||||
| ---@type table<string, string[]> | ||||
| M.topmods = { lazy = { M.me } } | ||||
| ---@type table<string, string[]> | ||||
| M.indexed = { [M.me] = { "lazy" } } | ||||
| M.indexed_unloaded = false | ||||
| M.indexed_rtp = 0 | ||||
| -- selene:allow(global_usage) | ||||
| M._loadfile = _G.loadfile | ||||
| 
 | ||||
| -- checks whether the cached modpath is still valid | ||||
| function M.check_path(modname, modpath) | ||||
|   -- HACK: never return packer paths | ||||
|   if modpath:find("/site/pack/packer/", 1, true) then | ||||
|     return false | ||||
|   end | ||||
| 
 | ||||
|   -- check rtp excluding plugins. This is a very small list, so should be fast | ||||
|   for _, path in ipairs(M.get_rtp()) do | ||||
|     if modpath:find(path .. "/", 1, true) == 1 then | ||||
|       return true | ||||
| -- slightly faster/different version than vim.fs.normalize | ||||
| -- we also need to have it here, since the cache will load vim.fs | ||||
| ---@private | ||||
| function Cache.normalize(path) | ||||
|   if path:sub(1, 1) == "~" then | ||||
|     local home = vim.loop.os_homedir() | ||||
|     if home:sub(-1) == "\\" or home:sub(-1) == "/" then | ||||
|       home = home:sub(1, -2) | ||||
|     end | ||||
|     path = home .. path:sub(2) | ||||
|   end | ||||
| 
 | ||||
|   -- the correct lazy path should be part of rtp. | ||||
|   -- so if we get here, this is folke using the local dev instance ;) | ||||
|   if modname and (modname == "lazy" or modname:sub(1, 5) == "lazy.") then | ||||
|     return false | ||||
|   end | ||||
| 
 | ||||
|   return modname and M.check_autoload(modname, modpath) | ||||
|   path = path:gsub("\\", "/"):gsub("/+", "/") | ||||
|   return path:sub(-1) == "/" and path:sub(1, -2) or path | ||||
| end | ||||
| 
 | ||||
| function M.check_autoload(modname, modpath) | ||||
|   local start = uv.hrtime() | ||||
|   M.stats.autoload.total = M.stats.autoload.total + 1 | ||||
| 
 | ||||
|   -- check plugins. Again fast, since we check the plugin name from the path. | ||||
|   -- only needed when the plugin mod has been loaded | ||||
|   ---@type LazyCorePlugin | ||||
|   local Plugin = package.loaded["lazy.core.plugin"] | ||||
|   if Plugin then | ||||
|     local plugin = Plugin.find(modpath) | ||||
|     if plugin and modpath:find(plugin.dir, 1, true) == 1 then | ||||
|       -- we're not interested in loader time, so calculate delta here | ||||
|       M.stats.autoload.time = M.stats.autoload.time + uv.hrtime() - start | ||||
|       -- don't load if we're loading specs or if the plugin is already loaded | ||||
|       if not (Plugin.loading or plugin._.loaded) then | ||||
|         if plugin.module == false then | ||||
|           error("Plugin " .. plugin.name .. " is not loaded and is configured with module=false") | ||||
|         end | ||||
|         require("lazy.core.loader").load(plugin, { require = modname }) | ||||
| ---@private | ||||
| function Cache.get_rtp() | ||||
|   if vim.in_fast_event() then | ||||
|     return Cache._rtp or {} | ||||
|   end | ||||
|   local key = vim.go.rtp | ||||
|   if key ~= Cache._rtp_key then | ||||
|     Cache._rtp = {} | ||||
|     for _, path in ipairs(vim.api.nvim_get_runtime_file("", true)) do | ||||
|       path = Cache.normalize(path) | ||||
|       -- skip after directories | ||||
|       if path:sub(-6, -1) ~= "/after" then | ||||
|         Cache._rtp[#Cache._rtp + 1] = path | ||||
|       end | ||||
|       return true | ||||
|     end | ||||
|     Cache._rtp_key = key | ||||
|   end | ||||
|   return Cache._rtp | ||||
| end | ||||
| 
 | ||||
| ---@param name string can be a module name, or a file name | ||||
| ---@private | ||||
| function Cache.cache_file(name) | ||||
|   return M.path .. "/" .. name:gsub("[/\\:]", "%%") .. "c" | ||||
| end | ||||
| 
 | ||||
| ---@param entry CacheEntry | ||||
| ---@private | ||||
| function Cache.write(name, entry) | ||||
|   local cname = Cache.cache_file(name) | ||||
|   local f = assert(uv.fs_open(cname, "w", 438)) | ||||
|   local header = { | ||||
|     M.VERSION, | ||||
|     entry.hash.size, | ||||
|     entry.hash.mtime.sec, | ||||
|     entry.hash.mtime.nsec, | ||||
|     #entry.modpath, | ||||
|   } | ||||
|   uv.fs_write(f, ffi.string(ffi.new("const uint32_t[5]", header), 20)) | ||||
|   uv.fs_write(f, entry.modpath) | ||||
|   uv.fs_write(f, entry.chunk) | ||||
|   uv.fs_close(f) | ||||
| end | ||||
| 
 | ||||
| ---@return CacheEntry? | ||||
| ---@private | ||||
| function Cache.read(name) | ||||
|   local cname = Cache.cache_file(name) | ||||
|   local f = uv.fs_open(cname, "r", 438) | ||||
|   if f then | ||||
|     local hash = uv.fs_fstat(f) --[[@as CacheHash]] | ||||
|     local data = uv.fs_read(f, hash.size, 0) --[[@as string]] | ||||
|     uv.fs_close(f) | ||||
| 
 | ||||
|     ---@type integer[]|{[0]:integer} | ||||
|     local header = ffi.cast("uint32_t*", ffi.new("const char[20]", data:sub(1, 20))) | ||||
|     if header[0] ~= M.VERSION then | ||||
|       return | ||||
|     end | ||||
|     local modpath = data:sub(21, 20 + header[4]) | ||||
|     return { | ||||
|       hash = { size = header[1], mtime = { sec = header[2], nsec = header[3] } }, | ||||
|       chunk = data:sub(20 + header[4] + 1), | ||||
|       modpath = modpath, | ||||
|     } | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| ---@param modname string | ||||
| ---@private | ||||
| function Cache.loader(modname) | ||||
|   modname = modname:gsub("/", ".") | ||||
|   local modpath, hash = Cache.find(modname) | ||||
|   if modpath then | ||||
|     return Cache.load(modpath, { hash = hash }) | ||||
|   end | ||||
|   return "module " .. modname .. " not found" | ||||
| end | ||||
| 
 | ||||
| ---@param filename? string | ||||
| ---@param mode? "b"|"t"|"bt" | ||||
| ---@param env? table | ||||
| ---@return function?, string?  error_message | ||||
| ---@private | ||||
| function Cache.loadfile(filename, mode, env) | ||||
|   filename = Cache.normalize(filename) | ||||
|   return Cache.load(filename, { mode = mode, env = env }) | ||||
| end | ||||
| 
 | ||||
| ---@param h1 CacheHash | ||||
| ---@param h2 CacheHash | ||||
| ---@private | ||||
| function Cache.eq(h1, h2) | ||||
|   return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec | ||||
| end | ||||
| 
 | ||||
| ---@param modpath string | ||||
| ---@param opts? {hash?: CacheHash, mode?: "b"|"t"|"bt", env?:table} | ||||
| ---@return function?, string? error_message | ||||
| ---@private | ||||
| function Cache.load(modpath, opts) | ||||
|   opts = opts or {} | ||||
|   local hash = opts.hash or uv.fs_stat(modpath) | ||||
|   if not hash then | ||||
|     -- trigger correct error | ||||
|     return Cache._loadfile(modpath) | ||||
|   end | ||||
| 
 | ||||
|   ---@type function?, string? | ||||
|   local chunk, err | ||||
|   local entry = Cache.read(modpath) | ||||
|   if entry and Cache.eq(entry.hash, hash) then | ||||
|     -- found in cache and up to date | ||||
|     chunk, err = loadstring(entry.chunk --[[@as string]], "@" .. entry.modpath) | ||||
|     if not (err and err:find("cannot load incompatible bytecode", 1, true)) then | ||||
|       return chunk, err | ||||
|     end | ||||
|   end | ||||
|   M.stats.autoload.time = M.stats.autoload.time + uv.hrtime() - start | ||||
|   return false | ||||
|   entry = { hash = hash, modpath = modpath } | ||||
| 
 | ||||
|   chunk, err = Cache._loadfile(entry.modpath) | ||||
|   if chunk then | ||||
|     entry.chunk = string.dump(chunk) | ||||
|     Cache.write(modpath, entry) | ||||
|   end | ||||
|   return chunk, err | ||||
| end | ||||
| 
 | ||||
| ---@param modname string | ||||
| ---@param opts? CacheFindOpts | ||||
| ---@return string? modpath, CacheHash? hash | ||||
| function Cache.find(modname, opts) | ||||
|   opts = opts or {} | ||||
|   local start = uv.hrtime() | ||||
|   M.stats.total = M.stats.total + 1 | ||||
|   modname = modname:gsub("/", ".") | ||||
|   local basename = modname:gsub("%.", "/") | ||||
|   local idx = modname:find(".", 1, true) | ||||
|   local topmod = idx and modname:sub(1, idx - 1) or modname | ||||
| 
 | ||||
|   -- OPTIM: search for a directory first when topmod == modname | ||||
|   local patterns = opts.patterns or (topmod == modname and { "/init.lua", ".lua" } or { ".lua", "/init.lua" }) | ||||
|   local rtp = opts.rtp ~= false and Cache.get_rtp() or {} | ||||
|   if opts.paths then | ||||
|     rtp = vim.deepcopy(rtp) | ||||
|     for _, dir in ipairs(opts.paths) do | ||||
|       rtp[#rtp + 1] = Cache.normalize(dir) | ||||
|     end | ||||
|   end | ||||
|   for p, pattern in ipairs(patterns) do | ||||
|     patterns[p] = "/lua/" .. basename .. pattern | ||||
|   end | ||||
| 
 | ||||
|   for _, path in ipairs(rtp) do | ||||
|     if M.lsmod(path)[topmod] then | ||||
|       for _, pattern in ipairs(patterns) do | ||||
|         local modpath = path .. pattern | ||||
|         M.stats.stat = M.stats.stat + 1 | ||||
|         local hash = uv.fs_stat(modpath) | ||||
|         if hash then | ||||
|           M.stats.time = M.stats.time + uv.hrtime() - start | ||||
|           return modpath, hash | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   -- module not found | ||||
|   M.stats.not_found = M.stats.not_found + 1 | ||||
|   M.stats.time = M.stats.time + uv.hrtime() - start | ||||
| end | ||||
| 
 | ||||
| --- Resets the topmods cache for the path | ||||
| ---@param path string | ||||
| function M.reset(path) | ||||
|   Cache._topmods[Cache.normalize(path)] = nil | ||||
| end | ||||
| 
 | ||||
| function M.enable() | ||||
|   if M.enabled then | ||||
|     return | ||||
|   end | ||||
|   M.enabled = true | ||||
|   vim.fn.mkdir(vim.fn.fnamemodify(M.path, ":p"), "p") | ||||
|   -- selene: allow(global_usage) | ||||
|   _G.loadfile = Cache.loadfile | ||||
|   table.insert(package.loaders, 2, Cache.loader) | ||||
| end | ||||
| 
 | ||||
| function M.disable() | ||||
|   if not M.enabled then | ||||
|     return | ||||
|   end | ||||
|   -- selene:allow(global_usage) | ||||
|   _G.loadfile = M._loadfile | ||||
|   M.enabled = false | ||||
|   if M.debug and vim.tbl_count(M.topmods) > 1 then | ||||
|     M.log(M.topmods, { level = vim.log.levels.WARN, title = "topmods" }) | ||||
|   end | ||||
|   if M.debug and false then | ||||
|     local stats = vim.deepcopy(M.stats) | ||||
|     stats.time = (stats.time or 0) / 1e6 | ||||
|     stats.find.time = (stats.find.time or 0) / 1e6 | ||||
|     stats.autoload.time = (stats.autoload.time or 0) / 1e6 | ||||
|     M.log(stats, { title = "stats" }) | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| ---@param msg string|table | ||||
| ---@param opts? LazyNotifyOpts | ||||
| function M.log(msg, opts) | ||||
|   if M.debug then | ||||
|     msg = vim.deepcopy(msg) | ||||
|     vim.schedule(function() | ||||
|       require("lazy.core.util").debug(msg, opts) | ||||
|     end) | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| function M.check_loaded(modname) | ||||
|   -- selene: allow(global_usage) | ||||
|   _G.loadfile = Cache._loadfile | ||||
|   ---@diagnostic disable-next-line: no-unknown | ||||
|   local mod = package.loaded[modname] | ||||
|   if type(mod) == "table" then | ||||
|     return function() | ||||
|       return mod | ||||
|   for l, loader in ipairs(package.loaders) do | ||||
|     if loader == Cache.loader then | ||||
|       table.remove(package.loaders, l) | ||||
|     end | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| ---@param modname string | ||||
| ---@return fun()|string | ||||
| function M.loader(modname) | ||||
|   modname = modname:gsub("/", ".") | ||||
|   local entry = M.cache[modname] | ||||
| 
 | ||||
|   local chunk, err | ||||
|   if entry then | ||||
|     if M.check_path(modname, entry.modpath) then | ||||
|       M.stats.find.total = M.stats.find.total + 1 | ||||
|       chunk, err = M.load(modname, entry.modpath) | ||||
|     else | ||||
|       M.cache[modname] = nil | ||||
|       M.dirty = true | ||||
|     end | ||||
|   end | ||||
|   if not chunk then | ||||
|     -- find the modpath and load the module | ||||
|     local modpath = M.find(modname) | ||||
|     if modpath then | ||||
|       M.check_autoload(modname, modpath) | ||||
|       if M.enabled then | ||||
|         chunk, err = M.load(modname, modpath) | ||||
|       else | ||||
|         chunk = M.check_loaded(modname) | ||||
|         if not chunk then | ||||
|           chunk, err = M._loadfile(modpath) | ||||
|         end | ||||
| -- Return the top-level `/lua/*` modules for this path | ||||
| ---@return string[] | ||||
| function M.lsmod(path) | ||||
|   if not Cache._topmods[path] then | ||||
|     M.stats.index = M.stats.index + 1 | ||||
|     Cache._topmods[path] = {} | ||||
|     local handle = vim.loop.fs_scandir(path .. "/lua") | ||||
|     while handle do | ||||
|       local name, t = vim.loop.fs_scandir_next(handle) | ||||
|       if not name then | ||||
|         break | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|   return chunk or err or ("module " .. modname .. " not found") | ||||
| end | ||||
| 
 | ||||
| ---@param modpath string | ||||
| ---@return any, string? | ||||
| function M.loadfile(modpath) | ||||
|   modpath = modpath:gsub("\\", "/") | ||||
|   return M.load(modpath, modpath) | ||||
| end | ||||
| 
 | ||||
| ---@param modkey string | ||||
| ---@param modpath string | ||||
| ---@return function?, string? error_message | ||||
| function M.load(modkey, modpath) | ||||
|   local chunk, err | ||||
|   chunk = M.check_loaded(modkey) | ||||
|   if chunk then | ||||
|     return chunk | ||||
|   end | ||||
|   modpath = modpath:gsub("\\", "/") | ||||
|   local hash = M.hash(modpath) | ||||
|   if not hash then | ||||
|     -- trigger correct error | ||||
|     return M._loadfile(modpath) | ||||
|   end | ||||
| 
 | ||||
|   local entry = M.cache[modkey] | ||||
|   if entry then | ||||
|     entry.modpath = modpath | ||||
|     entry.used = os.time() | ||||
|     if M.eq(entry.hash, hash) then | ||||
|       -- found in cache and up to date | ||||
|       chunk, err = loadstring(entry.chunk --[[@as string]], "@" .. entry.modpath) | ||||
|       if not (err and err:find("cannot load incompatible bytecode", 1, true)) then | ||||
|         return chunk, err | ||||
|       end | ||||
|     end | ||||
|   else | ||||
|     entry = { hash = hash, modpath = modpath, used = os.time() } | ||||
|     M.cache[modkey] = entry | ||||
|   end | ||||
|   entry.hash = hash | ||||
| 
 | ||||
|   if M.debug then | ||||
|     M.log("`" .. modpath .. "`", { level = vim.log.levels.WARN, title = "Cache.load" }) | ||||
|   end | ||||
| 
 | ||||
|   chunk, err = M._loadfile(entry.modpath) | ||||
|   M.dirty = true | ||||
|   if chunk then | ||||
|     entry.chunk = string.dump(chunk) | ||||
|   else | ||||
|     M.cache[modkey] = nil | ||||
|   end | ||||
|   return chunk, err | ||||
| end | ||||
| 
 | ||||
| -- index the top-level lua modules for this path | ||||
| function M._index(path) | ||||
|   if not M.indexed[path] and path:sub(-6, -1) ~= "/after" then | ||||
|     M.stats.find.index = M.stats.find.index + 1 | ||||
|     ---@type LazyUtilCore | ||||
|     local Util = package.loaded["lazy.core.util"] | ||||
|     if not Util then | ||||
|       return false | ||||
|     end | ||||
|     M.indexed[path] = {} | ||||
|     Util.ls(path .. "/lua", function(_, name, t) | ||||
|       -- HACK: type is not always returned due to a bug in luv | ||||
|       t = t or vim.loop.fs_stat(path .. "/" .. name).type | ||||
|       ---@type string | ||||
|       local topname | ||||
|       if name:sub(-4) == ".lua" then | ||||
|         topname = name:sub(1, -5) | ||||
|  | @ -242,283 +266,41 @@ function M._index(path) | |||
|         topname = name | ||||
|       end | ||||
|       if topname then | ||||
|         M.topmods[topname] = M.topmods[topname] or {} | ||||
|         if not vim.tbl_contains(M.topmods[topname], path) then | ||||
|           table.insert(M.topmods[topname], path) | ||||
|         end | ||||
|         if not vim.tbl_contains(M.indexed[path], topname) then | ||||
|           table.insert(M.indexed[path], topname) | ||||
|         end | ||||
|       end | ||||
|     end) | ||||
|     return true | ||||
|   end | ||||
|   return false | ||||
| end | ||||
| 
 | ||||
| function M.get_topmods(path) | ||||
|   M._index(path) | ||||
|   return M.indexed[path] or {} | ||||
| end | ||||
| 
 | ||||
| ---@param modname string | ||||
| ---@return string? | ||||
| function M.find_root(modname) | ||||
|   if M.cache[modname] then | ||||
|     -- check if modname is in cache | ||||
|     local modpath = M.cache[modname].modpath | ||||
|     if M.check_path(modname, modpath) and uv.fs_stat(modpath) then | ||||
|       local root = modpath:gsub("/init%.lua$", ""):gsub("%.lua$", "") | ||||
|       return root | ||||
|     end | ||||
|   else | ||||
|     -- in case modname is just a directory and not a real mod, | ||||
|     -- check for any children in the cache | ||||
|     for child, entry in pairs(M.cache) do | ||||
|       if child:find(modname, 1, true) == 1 then | ||||
|         if M.check_path(child, entry.modpath) and uv.fs_stat(entry.modpath) then | ||||
|           local basename = modname:gsub("%.", "/") | ||||
|           local childbase = child:gsub("%.", "/") | ||||
|           local ret = entry.modpath:gsub("/init%.lua$", ""):gsub("%.lua$", "") | ||||
|           local idx = assert(ret:find(childbase, 1, true)) | ||||
|           return ret:sub(1, idx - 1) .. basename | ||||
|         end | ||||
|         Cache._topmods[path][topname] = true | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   -- not found in cache, so find the root with the special pattern | ||||
|   local modpath = M.find(modname, { patterns = { "" } }) | ||||
|   if modpath then | ||||
|     local root = modpath:gsub("/init%.lua$", ""):gsub("%.lua$", "") | ||||
|     return root | ||||
|   end | ||||
|   return Cache._topmods[path] | ||||
| end | ||||
| 
 | ||||
| ---@param modname string | ||||
| ---@param opts? {patterns?:string[]} | ||||
| ---@return string? | ||||
| ---@param opts? CacheFindOpts | ||||
| ---@return string? modpath | ||||
| function M.find(modname, opts) | ||||
|   opts = opts or {} | ||||
| 
 | ||||
|   M.stats.find.total = M.stats.find.total + 1 | ||||
|   local start = uv.hrtime() | ||||
|   local basename = modname:gsub("%.", "/") | ||||
|   local idx = modname:find(".", 1, true) | ||||
|   local topmod = idx and modname:sub(1, idx - 1) or modname | ||||
| 
 | ||||
|   -- search for a directory first when topmod == modname | ||||
|   local patterns = topmod == modname and { "/init.lua", ".lua" } or { ".lua", "/init.lua" } | ||||
| 
 | ||||
|   if opts.patterns then | ||||
|     vim.list_extend(patterns, opts.patterns) | ||||
|   end | ||||
| 
 | ||||
|   -- check top-level mods to find the module | ||||
|   local function _find() | ||||
|     for _, toppath in ipairs(M.topmods[topmod] or {}) do | ||||
|       for _, pattern in ipairs(patterns) do | ||||
|         local path = toppath .. "/lua/" .. basename .. pattern | ||||
|         M.stats.find.stat = M.stats.find.stat + 1 | ||||
|         if uv.fs_stat(path) then | ||||
|           return path | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   local modpath = _find() | ||||
|   if not modpath then | ||||
|     -- update rtp | ||||
|     local rtp = M.list_rtp() | ||||
|     if #rtp ~= M.indexed_rtp then | ||||
|       M.indexed_rtp = #rtp | ||||
|       local updated = false | ||||
|       for _, path in ipairs(rtp) do | ||||
|         updated = M._index(path) or updated | ||||
|       end | ||||
|       if updated then | ||||
|         modpath = _find() | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     -- update unloaded | ||||
|     if not modpath and not M.indexed_unloaded then | ||||
|       M.indexed_unloaded = true | ||||
|       local updated = false | ||||
|       ---@type LazyCoreConfig | ||||
|       local Config = package.loaded["lazy.core.config"] | ||||
|       if Config and Config.spec then | ||||
|         for _, plugin in pairs(Config.spec.plugins) do | ||||
|           if not (M.indexed[plugin.dir] or plugin._.loaded or plugin.module == false) then | ||||
|             updated = M._index(plugin.dir) or updated | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|       if updated then | ||||
|         modpath = _find() | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     -- module not found | ||||
|     if not modpath then | ||||
|       M.stats.find.not_found = M.stats.find.not_found + 1 | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   M.stats.find.time = M.stats.find.time + uv.hrtime() - start | ||||
|   local modpath = Cache.find(modname, opts) | ||||
|   return modpath | ||||
| end | ||||
| 
 | ||||
| -- returns the cached RTP excluding plugin dirs | ||||
| function M.get_rtp() | ||||
|   local rtp = M.list_rtp() | ||||
|   if not M.rtp or #rtp ~= M.rtp_total then | ||||
|     M.rtp_total = #rtp | ||||
|     M.rtp = {} | ||||
|     ---@type table<string,true> | ||||
|     local skip = {} | ||||
|     -- only skip plugins once Config has been setup | ||||
|     ---@type LazyCoreConfig | ||||
|     local Config = package.loaded["lazy.core.config"] | ||||
|     if Config then | ||||
|       for _, plugin in pairs(Config.plugins) do | ||||
|         if plugin.name ~= "lazy.nvim" then | ||||
|           skip[plugin.dir] = true | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|     for _, path in ipairs(rtp) do | ||||
|       ---@type string | ||||
|       path = path:gsub("\\", "/") | ||||
|       if not skip[path] and not path:find("after/?$") then | ||||
|         M.rtp[#M.rtp + 1] = path | ||||
|       end | ||||
|     end | ||||
| function M.inspect() | ||||
|   local function ms(nsec) | ||||
|     return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. "ms" | ||||
|   end | ||||
|   return M.rtp | ||||
| end | ||||
| 
 | ||||
| function M.list_rtp() | ||||
|   return vim.api.nvim_get_runtime_file("", true) | ||||
| end | ||||
| 
 | ||||
| ---@param opts? LazyConfig | ||||
| function M.setup(opts) | ||||
|   -- no fancy deep extend here. just set the options | ||||
|   if opts and opts.performance and opts.performance.cache then | ||||
|     ---@diagnostic disable-next-line: no-unknown | ||||
|     for k, v in pairs(opts.performance.cache) do | ||||
|       ---@diagnostic disable-next-line: no-unknown | ||||
|       M.config[k] = v | ||||
|     end | ||||
|   local props = { | ||||
|     { "total", M.stats.total, "Number" }, | ||||
|     { "time", ms(M.stats.time), "Bold" }, | ||||
|     { "avg time", ms(M.stats.time / M.stats.total), "Bold" }, | ||||
|     { "index", M.stats.index, "Number" }, | ||||
|     { "fs_stat", M.stats.stat, "Number" }, | ||||
|     { "not found", M.stats.not_found, "Number" }, | ||||
|   } | ||||
|   local chunks = {} ---@type string[][] | ||||
|   for _, prop in ipairs(props) do | ||||
|     chunks[#chunks + 1] = { "* " .. prop[1] .. ": " } | ||||
|     chunks[#chunks + 1] = { tostring(prop[2]) .. "\n", prop[3] } | ||||
|   end | ||||
|   M.debug = opts and opts.debug | ||||
|   M.enabled = M.config.enabled | ||||
| 
 | ||||
|   if M.enabled then | ||||
|     table.insert(package.loaders, 2, M.loader) | ||||
|     M.load_cache() | ||||
|     -- selene:allow(global_usage) | ||||
|     _G.loadfile = M.loadfile | ||||
|     if #M.config.disable_events > 0 then | ||||
|       vim.api.nvim_create_autocmd(M.config.disable_events, { once = true, callback = M.disable }) | ||||
|     end | ||||
|   else | ||||
|     -- we need to always add the loader since this will autoload unloaded modules | ||||
|     table.insert(package.loaders, M.loader) | ||||
|   end | ||||
| 
 | ||||
|   return M | ||||
|   vim.api.nvim_echo(chunks, true, {}) | ||||
| end | ||||
| 
 | ||||
| ---@return CacheHash? | ||||
| function M.hash(file) | ||||
|   local ok, ret = pcall(uv.fs_stat, file) | ||||
|   return ok and ret or nil | ||||
| end | ||||
| 
 | ||||
| ---@param h1 CacheHash | ||||
| ---@param h2 CacheHash | ||||
| function M.eq(h1, h2) | ||||
|   return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec | ||||
| end | ||||
| 
 | ||||
| function M.save_cache() | ||||
|   vim.fn.mkdir(vim.fn.fnamemodify(M.config.path, ":p:h"), "p") | ||||
|   local f = assert(uv.fs_open(M.config.path, "w", 438)) | ||||
|   uv.fs_write(f, M.VERSION) | ||||
|   uv.fs_write(f, "\0") | ||||
|   for modname, entry in pairs(M.cache) do | ||||
|     if entry.used > os.time() - M.config.ttl then | ||||
|       entry.modname = modname | ||||
|       local header = { | ||||
|         entry.hash.size, | ||||
|         entry.hash.mtime.sec, | ||||
|         entry.hash.mtime.nsec, | ||||
|         #modname, | ||||
|         #entry.chunk, | ||||
|         #entry.modpath, | ||||
|         entry.used, | ||||
|       } | ||||
|       uv.fs_write(f, ffi.string(ffi.new("const uint32_t[7]", header), 28)) | ||||
|       uv.fs_write(f, modname) | ||||
|       uv.fs_write(f, entry.chunk) | ||||
|       uv.fs_write(f, entry.modpath) | ||||
|     end | ||||
|   end | ||||
|   uv.fs_close(f) | ||||
| end | ||||
| 
 | ||||
| function M.load_cache() | ||||
|   M.cache = {} | ||||
|   local f = uv.fs_open(M.config.path, "r", 438) | ||||
|   if f then | ||||
|     cache_hash = uv.fs_fstat(f) --[[@as CacheHash]] | ||||
|     local data = uv.fs_read(f, cache_hash.size, 0) --[[@as string]] | ||||
|     uv.fs_close(f) | ||||
| 
 | ||||
|     local zero = data:find("\0", 1, true) | ||||
|     if not zero then | ||||
|       return | ||||
|     end | ||||
| 
 | ||||
|     if M.VERSION ~= data:sub(1, zero - 1) then | ||||
|       return | ||||
|     end | ||||
| 
 | ||||
|     local offset = zero + 1 | ||||
|     while offset + 1 < #data do | ||||
|       local header = ffi.cast("uint32_t*", ffi.new("const char[28]", data:sub(offset, offset + 27))) | ||||
|       offset = offset + 28 | ||||
|       local modname = data:sub(offset, offset + header[3] - 1) | ||||
|       offset = offset + header[3] | ||||
|       local chunk = data:sub(offset, offset + header[4] - 1) | ||||
|       offset = offset + header[4] | ||||
|       local file = data:sub(offset, offset + header[5] - 1) | ||||
|       offset = offset + header[5] | ||||
|       M.cache[modname] = { | ||||
|         hash = { size = header[0], mtime = { sec = header[1], nsec = header[2] } }, | ||||
|         chunk = chunk, | ||||
|         modpath = file, | ||||
|         used = header[6], | ||||
|       } | ||||
|     end | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| function M.autosave() | ||||
|   vim.api.nvim_create_autocmd("VimLeavePre", { | ||||
|     callback = function() | ||||
|       if M.dirty then | ||||
|         local hash = M.hash(M.config.path) | ||||
|         -- abort when the file was changed in the meantime | ||||
|         if hash == nil or M.eq(cache_hash, hash) then | ||||
|           M.save_cache() | ||||
|         end | ||||
|       end | ||||
|     end, | ||||
|   }) | ||||
| end | ||||
| M._Cache = Cache | ||||
| 
 | ||||
| return M | ||||
|  |  | |||
|  | @ -113,8 +113,9 @@ M.defaults = { | |||
|     notify = true, -- get a notification when changes are found | ||||
|   }, | ||||
|   performance = { | ||||
|     ---@type LazyCacheConfig | ||||
|     cache = nil, | ||||
|     cache = { | ||||
|       enabled = true, | ||||
|     }, | ||||
|     reset_packpath = true, -- reset the package path to improve startup time | ||||
|     rtp = { | ||||
|       reset = true, -- reset the runtime path to $VIMRUNTIME and your config directory | ||||
|  | @ -226,7 +227,6 @@ function M.setup(opts) | |||
|     pattern = "VeryLazy", | ||||
|     once = true, | ||||
|     callback = function() | ||||
|       require("lazy.core.cache").autosave() | ||||
|       require("lazy.view.commands").setup() | ||||
|       if M.options.change_detection.enabled then | ||||
|         require("lazy.manage.reloader").enable() | ||||
|  |  | |||
|  | @ -72,7 +72,7 @@ function M.install_missing() | |||
|       -- remove and installed plugins from indexed, so cache will index again | ||||
|       for _, p in pairs(Config.plugins) do | ||||
|         if p._.installed then | ||||
|           Cache.indexed[p.dir] = nil | ||||
|           Cache.reset(p.dir) | ||||
|         end | ||||
|       end | ||||
|       -- reload plugins | ||||
|  | @ -341,7 +341,7 @@ function M.get_main(plugin) | |||
|   local normname = Util.normname(plugin.name) | ||||
|   ---@type string[] | ||||
|   local mods = {} | ||||
|   for _, modname in ipairs(Cache.get_topmods(plugin.dir)) do | ||||
|   for modname, _ in pairs(Cache.lsmod(plugin.dir)) do | ||||
|     mods[#mods + 1] = modname | ||||
|     local modnorm = Util.normname(modname) | ||||
|     -- if we found an exact match, then use that | ||||
|  | @ -450,4 +450,28 @@ function M.colorscheme(name) | |||
|   end | ||||
| end | ||||
| 
 | ||||
| ---@param modname string | ||||
| function M.loader(modname) | ||||
|   local modpath = Cache.find(modname, { rtp = false, paths = Util.get_unloaded_rtp(modname) }) | ||||
|   if modpath then | ||||
|     local plugin = Plugin.find(modpath) | ||||
|     if plugin and modpath:find(plugin.dir, 1, true) == 1 then | ||||
|       -- don't load if we're loading specs or if the plugin is already loaded | ||||
|       if not (Plugin.loading or plugin._.loaded) then | ||||
|         if plugin.module == false then | ||||
|           error("Plugin " .. plugin.name .. " is not loaded and is configured with module=false") | ||||
|         end | ||||
|         M.load(plugin, { require = modname }) | ||||
|       end | ||||
|       local mod = package.loaded[modname] | ||||
|       if type(mod) == "table" then | ||||
|         return function() | ||||
|           return mod | ||||
|         end | ||||
|       end | ||||
|       return loadfile(modpath) | ||||
|     end | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| return M | ||||
|  |  | |||
|  | @ -217,11 +217,46 @@ function M.walkmods(root, fn, modname) | |||
|   end) | ||||
| end | ||||
| 
 | ||||
| ---@param modname string | ||||
| function M.get_unloaded_rtp(modname) | ||||
|   modname = modname:gsub("/", ".") | ||||
|   local idx = modname:find(".", 1, true) | ||||
|   local topmod = idx and modname:sub(1, idx - 1) or modname | ||||
|   topmod = M.normname(topmod) | ||||
| 
 | ||||
|   local rtp = {} | ||||
|   local Config = require("lazy.core.config") | ||||
|   if Config.spec then | ||||
|     for _, plugin in pairs(Config.spec.plugins) do | ||||
|       if not (plugin._.loaded or plugin.module == false) then | ||||
|         if topmod == M.normname(plugin.name) then | ||||
|           table.insert(rtp, 1, plugin.dir) | ||||
|         else | ||||
|           table.insert(rtp, plugin.dir) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|   return rtp | ||||
| end | ||||
| 
 | ||||
| function M.find_root(modname) | ||||
|   local Cache = require("lazy.core.cache") | ||||
|   local modpath = Cache.find(modname, { | ||||
|     rtp = true, | ||||
|     paths = M.get_unloaded_rtp(modname), | ||||
|     patterns = { "", ".lua" }, | ||||
|   }) | ||||
|   if modpath then | ||||
|     local root = modpath:gsub("/init%.lua$", ""):gsub("%.lua$", "") | ||||
|     return root | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| ---@param modname string | ||||
| ---@param fn fun(modname:string, modpath:string) | ||||
| function M.lsmod(modname, fn) | ||||
|   local Cache = require("lazy.core.cache") | ||||
|   local root = Cache.find_root(modname) | ||||
|   local root = M.find_root(modname) | ||||
|   if not root then | ||||
|     return | ||||
|   end | ||||
|  |  | |||
|  | @ -131,12 +131,7 @@ function M.colors() | |||
| end | ||||
| 
 | ||||
| function M.update() | ||||
|   local cache_config = M.extract("lua/lazy/core/cache.lua", "\nM%.config = ({.-\n})") | ||||
|   local config = M.extract("lua/lazy/core/config.lua", "\nM%.defaults = ({.-\n})") | ||||
|   config = config:gsub( | ||||
|     "\n%s*%-%-%-@type LazyCacheConfig.*cache = nil,", | ||||
|     "\n" .. M.indent("cache = " .. cache_config .. ",", 4) | ||||
|   ) | ||||
|   config = config:gsub("%s*debug = false.\n", "\n") | ||||
|   M.save({ | ||||
|     bootstrap = M.extract("lua/lazy/init.lua", "function M%.bootstrap%(%)\n(.-)\nend"), | ||||
|  |  | |||
|  | @ -34,13 +34,16 @@ function M.setup(spec, opts) | |||
|   local start = vim.loop.hrtime() | ||||
| 
 | ||||
|   -- load module cache before anything else | ||||
|   require("lazy.core.cache").setup(opts) | ||||
|   if not (opts and opts.performance and opts.performance.cache and opts.performance.cache.enabled == false) then | ||||
|     require("lazy.core.cache").enable() | ||||
|   end | ||||
| 
 | ||||
|   require("lazy.stats").track("LazyStart") | ||||
| 
 | ||||
|   local Util = require("lazy.core.util") | ||||
|   local Config = require("lazy.core.config") | ||||
|   local Loader = require("lazy.core.loader") | ||||
|   table.insert(package.loaders, 3, Loader.loader) | ||||
| 
 | ||||
|   Util.track({ plugin = "lazy.nvim" }) -- setup start | ||||
|   Util.track("module", vim.loop.hrtime() - start) | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| local Cache = require("lazy.core.cache") | ||||
| local Config = require("lazy.core.config") | ||||
| local Util = require("lazy.util") | ||||
| local Plugin = require("lazy.core.plugin") | ||||
|  | @ -6,12 +5,11 @@ local Loader = require("lazy.core.loader") | |||
| 
 | ||||
| local M = {} | ||||
| 
 | ||||
| ---@type table<string, CacheHash> | ||||
| ---@type table<string, vim.loop.Stat> | ||||
| M.files = {} | ||||
| 
 | ||||
| ---@type vim.loop.Timer | ||||
| M.timer = nil | ||||
| M.root = nil | ||||
| 
 | ||||
| function M.enable() | ||||
|   if M.timer then | ||||
|  | @ -19,7 +17,6 @@ function M.enable() | |||
|   end | ||||
|   if #Config.spec.modules > 0 then | ||||
|     M.timer = vim.loop.new_timer() | ||||
|     M.root = vim.fn.stdpath("config") .. "/lua" | ||||
|     M.check(true) | ||||
|     M.timer:start(2000, 2000, M.check) | ||||
|   end | ||||
|  | @ -32,6 +29,12 @@ function M.disable() | |||
|   end | ||||
| end | ||||
| 
 | ||||
| ---@param h1 vim.loop.Stat | ||||
| ---@param h2 vim.loop.Stat | ||||
| function M.eq(h1, h2) | ||||
|   return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec | ||||
| end | ||||
| 
 | ||||
| function M.check(start) | ||||
|   ---@type table<string,true> | ||||
|   local checked = {} | ||||
|  | @ -41,10 +44,10 @@ function M.check(start) | |||
|   -- spec is a module | ||||
|   local function check(_, modpath) | ||||
|     checked[modpath] = true | ||||
|     local hash = Cache.hash(modpath) | ||||
|     local hash = vim.loop.fs_stat(modpath) | ||||
|     if hash then | ||||
|       if M.files[modpath] then | ||||
|         if not Cache.eq(M.files[modpath], hash) then | ||||
|         if not M.eq(M.files[modpath], hash) then | ||||
|           M.files[modpath] = hash | ||||
|           table.insert(changes, { file = modpath, what = "changed" }) | ||||
|         end | ||||
|  |  | |||
|  | @ -672,33 +672,14 @@ function M:debug() | |||
| 
 | ||||
|   self:append("Cache.find()", "LazyH2"):nl() | ||||
|   self:props({ | ||||
|     { "total", Cache.stats.find.total, "Number" }, | ||||
|     { "time", self:ms(Cache.stats.find.time, 3), "Bold" }, | ||||
|     { "avg time", self:ms(Cache.stats.find.time / Cache.stats.find.total, 3), "Bold" }, | ||||
|     { "index", Cache.stats.find.index, "Number" }, | ||||
|     { "fs_stat", Cache.stats.find.stat, "Number" }, | ||||
|     { "not found", Cache.stats.find.not_found, "Number" }, | ||||
|     { "total", Cache.stats.total, "Number" }, | ||||
|     { "time", self:ms(Cache.stats.time, 3), "Bold" }, | ||||
|     { "avg time", self:ms(Cache.stats.time / Cache.stats.total, 3), "Bold" }, | ||||
|     { "index", Cache.stats.index, "Number" }, | ||||
|     { "fs_stat", Cache.stats.stat, "Number" }, | ||||
|     { "not found", Cache.stats.not_found, "Number" }, | ||||
|   }, { indent = 2 }) | ||||
|   self:nl() | ||||
| 
 | ||||
|   self:append("Cache.autoload()", "LazyH2"):nl() | ||||
|   self:props({ | ||||
|     { "total", Cache.stats.autoload.total, "Number" }, | ||||
|     { "time", self:ms(Cache.stats.autoload.time, 3), "Bold" }, | ||||
|     { "avg time", self:ms(Cache.stats.autoload.time / Cache.stats.autoload.total, 3), "Bold" }, | ||||
|   }, { indent = 2 }) | ||||
|   self:nl() | ||||
| 
 | ||||
|   self:append("Cache", "LazyH2"):nl() | ||||
|   local Cache = require("lazy.core.cache") | ||||
|   Util.foreach(Cache.cache, function(modname, entry) | ||||
|     local kb = math.floor(#entry.chunk / 10.24) / 100 | ||||
|     self:append("● ", "LazySpecial", { indent = 2 }):append(modname):append(" " .. kb .. "Kb", "Bold") | ||||
|     if entry.modpath ~= modname then | ||||
|       self:append(" " .. vim.fn.fnamemodify(entry.modpath, ":p:~:."), "LazyComment") | ||||
|     end | ||||
|     self:nl() | ||||
|   end) | ||||
| end | ||||
| 
 | ||||
| return M | ||||
|  |  | |||
|  | @ -51,10 +51,8 @@ describe("util", function() | |||
|       local files = Helpers.fs_create(test.files) | ||||
| 
 | ||||
|       -- test with empty cache | ||||
|       Cache.cache = {} | ||||
|       Cache.indexed = {} | ||||
|       Cache.indexed_rtp = false | ||||
|       local root = Cache.find_root(test.mod) | ||||
|       package.loaded["lazy.core.cache"] = nil | ||||
|       local root = Util.find_root(test.mod) | ||||
|       assert(root, "no root found for " .. test.mod .. " (test " .. t .. ")") | ||||
|       assert.same(Helpers.path(test.root), root) | ||||
|       local mods = {} | ||||
|  | @ -65,13 +63,8 @@ describe("util", function() | |||
|       assert.same(expected, mods) | ||||
| 
 | ||||
|       -- fill the cache | ||||
|       Cache.cache = {} | ||||
|       for i, file in ipairs(files) do | ||||
|         Cache.cache[test.mods[i]] = { modpath = file } | ||||
|       end | ||||
|       Cache.indexed = {} | ||||
|       Cache.indexed_rtp = false | ||||
|       root = Cache.find_root(test.mod) | ||||
|       package.loaded["lazy.core.cache"] = nil | ||||
|       root = Util.find_root(test.mod) | ||||
|       assert(root, "no root found for " .. test.mod .. " (test " .. t .. ")") | ||||
|       assert.same(Helpers.path(test.root), root) | ||||
|       mods = {} | ||||
|  | @ -85,12 +78,12 @@ describe("util", function() | |||
| 
 | ||||
|   it("find the correct root with dels", function() | ||||
|     Cache.cache = {} | ||||
|     Cache.indexed = {} | ||||
|     Cache.indexed_rtp = false | ||||
|     Cache._topmods = {} | ||||
|     Cache.topmods_rtp = false | ||||
|     vim.opt.rtp:append(Helpers.path("old")) | ||||
|     Helpers.fs_create({ "old/lua/foobar/init.lua" }) | ||||
|     Cache.cache["foobar"] = { modpath = Helpers.path("old/lua/foobar/init.lua") } | ||||
|     local root = Cache.find_root("foobar") | ||||
|     local root = Util.find_root("foobar") | ||||
|     assert(root, "foobar root not found") | ||||
|     assert.same(Helpers.path("old/lua/foobar"), root) | ||||
| 
 | ||||
|  | @ -98,24 +91,22 @@ describe("util", function() | |||
|     assert(not vim.loop.fs_stat(Helpers.path("old/lua/foobar")), "old/lua/foobar should not exist") | ||||
| 
 | ||||
|     -- vim.opt.rtp = rtp | ||||
|     Cache.indexed = {} | ||||
|     Cache.indexed_rtp = false | ||||
|     Cache._topmods = {} | ||||
|     vim.opt.rtp:append(Helpers.path("new")) | ||||
|     Helpers.fs_create({ "new/lua/foobar/init.lua" }) | ||||
|     root = Cache.find_root("foobar") | ||||
|     root = Util.find_root("foobar") | ||||
|     assert(root, "foobar root not found") | ||||
|     assert.same(Helpers.path("new/lua/foobar"), root) | ||||
|   end) | ||||
| 
 | ||||
|   it("find the correct root with mod dels", function() | ||||
|     Cache.cache = {} | ||||
|     Cache.indexed = {} | ||||
|     Cache.indexed_rtp = false | ||||
|     Cache._topmods = {} | ||||
|     Cache.enabled = true | ||||
|     vim.opt.rtp:append(Helpers.path("old")) | ||||
|     Helpers.fs_create({ "old/lua/foobar/test.lua" }) | ||||
|     Cache.cache["foobar.test"] = { modpath = Helpers.path("old/lua/foobar/test.lua") } | ||||
|     local root = Cache.find_root("foobar") | ||||
|     local root = Util.find_root("foobar") | ||||
|     assert(root, "foobar root not found") | ||||
|     assert.same(Helpers.path("old/lua/foobar"), root) | ||||
|     assert(not Cache.cache["foobar"], "foobar should not be in cache") | ||||
|  | @ -124,11 +115,10 @@ describe("util", function() | |||
|     Helpers.fs_rm("old") | ||||
| 
 | ||||
|     -- vim.opt.rtp = rtp | ||||
|     Cache.indexed = {} | ||||
|     Cache.indexed_rtp = false | ||||
|     Cache._topmods = {} | ||||
|     vim.opt.rtp:append(Helpers.path("new")) | ||||
|     Helpers.fs_create({ "new/lua/foobar/test.lua" }) | ||||
|     root = Cache.find_root("foobar") | ||||
|     root = Util.find_root("foobar") | ||||
|     assert(root, "foobar root not found") | ||||
|     assert.same(Helpers.path("new/lua/foobar"), root) | ||||
|   end) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue