From 4438faf9a9a72c95d88c620804db99fa44485ec9 Mon Sep 17 00:00:00 2001
From: Folke Lemaitre <folke.lemaitre@gmail.com>
Date: Wed, 30 Nov 2022 00:18:59 +0100
Subject: [PATCH] perf: removed partial spec caching. not worth the tiny
 performance boost

---
 lua/lazy/core/cache.lua    |  23 ++---
 lua/lazy/core/handler.lua  |  32 +++---
 lua/lazy/core/loader.lua   |  12 ---
 lua/lazy/core/module.lua   |  10 +-
 lua/lazy/core/plugin.lua   | 198 ++++++++-----------------------------
 lua/lazy/init.lua          |  17 +---
 lua/lazy/manage/init.lua   |   4 +-
 tests/core/plugin_spec.lua |   9 +-
 8 files changed, 77 insertions(+), 228 deletions(-)

diff --git a/lua/lazy/core/cache.lua b/lua/lazy/core/cache.lua
index 270d6b5..8614190 100644
--- a/lua/lazy/core/cache.lua
+++ b/lua/lazy/core/cache.lua
@@ -68,27 +68,20 @@ function M.setup()
 end
 
 function M.autosave()
-  vim.api.nvim_create_autocmd("User", {
-    pattern = "LazyDone",
-    once = true,
+  vim.api.nvim_create_autocmd("VimLeavePre", {
     callback = function()
-      vim.api.nvim_create_autocmd("VimLeavePre", {
-        callback = function()
-          if M.dirty then
-            local hash = M.hash(cache_path)
-            -- abort when the file was changed in the meantime
-            if hash == nil or cache_hash == hash then
-              M.save()
-            end
-          end
-        end,
-      })
+      if M.dirty then
+        local hash = M.hash(cache_path)
+        -- abort when the file was changed in the meantime
+        if hash == nil or cache_hash == hash then
+          M.save()
+        end
+      end
     end,
   })
 end
 
 function M.save()
-  require("lazy.core.plugin").save()
   require("lazy.core.module").save()
 
   local f = assert(io.open(cache_path, "wb"))
diff --git a/lua/lazy/core/handler.lua b/lua/lazy/core/handler.lua
index b8f5682..fb20810 100644
--- a/lua/lazy/core/handler.lua
+++ b/lua/lazy/core/handler.lua
@@ -1,5 +1,6 @@
 local Util = require("lazy.core.util")
 local Loader = require("lazy.core.loader")
+local Config = require("lazy.core.config")
 
 ---@class LazyPluginHandlers
 ---@field event? string|string[]
@@ -12,34 +13,25 @@ local M = {}
 
 ---@alias LazyHandler fun(grouped:table<string, string[]>)
 
----@type table<string, table<string, string[]>>
-M._groups = nil
-
----@param plugins LazyPlugin[]
----@param rebuild? boolean
-function M.group(plugins, rebuild)
-  if M._groups == nil or rebuild then
-    M._groups = {}
-    local types = vim.tbl_keys(M.handlers) --[[@as string[] ]]
-    for _, key in ipairs(types) do
-      M._groups[key] = {}
-      for _, plugin in pairs(plugins) do
-        if plugin[key] then
-          ---@diagnostic disable-next-line: no-unknown
-          for _, value in pairs(type(plugin[key]) == "table" and plugin[key] or { plugin[key] }) do
-            M._groups[key][value] = M._groups[key][value] or {}
-            table.insert(M._groups[key][value], plugin.name)
-          end
+function M.setup()
+  for key, handler in pairs(M.handlers) do
+    ---@type table<string, string[]>
+    local group = {}
+    for _, plugin in pairs(Config.plugins) do
+      if plugin[key] then
+        ---@diagnostic disable-next-line: no-unknown
+        for _, value in pairs(type(plugin[key]) == "table" and plugin[key] or { plugin[key] }) do
+          group[value] = group[value] or {}
+          table.insert(group[value], plugin.name)
         end
       end
     end
+    handler(group)
   end
-  return M._groups
 end
 
 ---@type table<string, LazyHandler>
 M.handlers = {}
-
 function M.handlers.event(grouped)
   local group = vim.api.nvim_create_augroup("lazy_handler_event", { clear = true })
   for event, plugins in pairs(grouped) do
diff --git a/lua/lazy/core/loader.lua b/lua/lazy/core/loader.lua
index 462e4b7..f8be35e 100644
--- a/lua/lazy/core/loader.lua
+++ b/lua/lazy/core/loader.lua
@@ -6,18 +6,6 @@ local M = {}
 ---@type LazyPlugin[]
 M.loading = {}
 
-function M.setup()
-  local Handler = require("lazy.core.handler")
-  local groups = Handler.group(Config.plugins)
-  for t, handler in pairs(Handler.handlers) do
-    if groups[t] then
-      Util.track(t)
-      handler(groups[t])
-      Util.track()
-    end
-  end
-end
-
 function M.init_plugins()
   Util.track("plugin_init")
   for _, plugin in pairs(Config.plugins) do
diff --git a/lua/lazy/core/module.lua b/lua/lazy/core/module.lua
index 5b0a233..3eb5780 100644
--- a/lua/lazy/core/module.lua
+++ b/lua/lazy/core/module.lua
@@ -5,13 +5,9 @@ local M = {}
 ---@type table<string, string>
 M.hashes = {}
 
-function M.is_dirty(modname, modpath)
-  return not (Cache.get(modname) and M.hashes[modname] and M.hashes[modname] == Cache.hash(modpath))
-end
-
 ---@param modname string
 ---@param modpath string
----@return any, boolean
+---@return any
 function M.load(modname, modpath)
   local err
   ---@type (string|fun())?
@@ -24,9 +20,7 @@ function M.load(modname, modpath)
     chunk = nil
   end
 
-  local cached = false
   if chunk then
-    cached = true
     chunk, err = load(chunk --[[@as string]], "@" .. modpath, "b")
   else
     vim.schedule(function()
@@ -39,7 +33,7 @@ function M.load(modname, modpath)
   end
 
   if chunk then
-    return chunk(), cached
+    return chunk()
   else
     error(err)
   end
diff --git a/lua/lazy/core/plugin.lua b/lua/lazy/core/plugin.lua
index 0f20b24..815a57e 100644
--- a/lua/lazy/core/plugin.lua
+++ b/lua/lazy/core/plugin.lua
@@ -1,17 +1,10 @@
 local Config = require("lazy.core.config")
 local Util = require("lazy.core.util")
 local Module = require("lazy.core.module")
-local Cache = require("lazy.core.cache")
 local Handler = require("lazy.core.handler")
 
 local M = {}
 
----@alias CachedPlugin LazyPlugin | {_funs: string[]}
-local skip = { _ = true, dir = true }
-local funs = { config = true, init = true, run = true }
-
-M.dirty = false
-
 ---@class LazyPluginHooks
 ---@field init? fun(LazyPlugin) Will always be run
 ---@field config? fun(LazyPlugin) Will be executed when loading the plugin
@@ -48,10 +41,7 @@ M.dirty = false
 ---@alias LazySpec string|LazyPlugin|LazySpec[]|{dependencies:LazySpec}
 
 ---@class LazySpecLoader
----@field modname string
----@field modpath string
 ---@field plugins table<string, LazyPlugin>
----@field funs? table<string, string[]>
 local Spec = {}
 M.Spec = Spec
 
@@ -59,30 +49,12 @@ M.Spec = Spec
 function Spec.new(spec)
   local self = setmetatable({}, { __index = Spec })
   self.plugins = {}
-  self.modname = nil
-  self.modpath = nil
   if spec then
     self:normalize(spec)
   end
   return self
 end
 
----@param modname string
----@param modpath string
-function Spec.load(modname, modpath)
-  local self = setmetatable({}, { __index = Spec })
-  self.plugins = {}
-  self.modname = modname
-  self.modpath = modpath
-  local mod, cached = Module.load(modname, modpath)
-  M.dirty = M.dirty or not cached
-  self:normalize(assert(mod))
-  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
 ---@param is_dep? boolean
 function Spec:add(plugin, is_dep)
@@ -113,9 +85,16 @@ function Spec:add(plugin, is_dep)
 
   plugin.dep = is_dep
 
-  M.process_local(plugin)
+  -- check for plugins that should be local
+  for _, pattern in ipairs(Config.options.plugins_local.patterns) do
+    if plugin[1]:find(pattern, 1, true) then
+      plugin.uri = Config.options.plugins_local.path .. "/" .. plugin.name
+      break
+    end
+  end
+
   local other = self.plugins[plugin.name]
-  self.plugins[plugin.name] = other and M.merge(other, plugin) or plugin
+  self.plugins[plugin.name] = other and self:merge(other, plugin) or plugin
   return self.plugins[plugin.name]
 end
 
@@ -140,28 +119,10 @@ function Spec:normalize(spec, results, is_dep)
   return results
 end
 
----@param spec LazySpecLoader
-function Spec.revive(spec)
-  if spec.funs then
-    ---@type LazySpecLoader
-    local loaded = nil
-    for fun, plugins in pairs(spec.funs) do
-      for _, name in pairs(plugins) do
-        ---@diagnostic disable-next-line: no-unknown
-        spec.plugins[name][fun] = function(...)
-          loaded = loaded or Spec.load(spec.modname, spec.modpath)
-          return loaded.plugins[name][fun](...)
-        end
-      end
-    end
-  end
-  return spec
-end
-
 ---@param old LazyPlugin
 ---@param new LazyPlugin
 ---@return LazyPlugin
-function M.merge(old, new)
+function Spec:merge(old, new)
   local is_dep = old.dep and new.dep
 
   ---@diagnostic disable-next-line: no-unknown
@@ -185,25 +146,19 @@ function M.merge(old, new)
   return old
 end
 
----@param opts? {clean:boolean, installed:boolean, plugins?: LazyPlugin[]}
-function M.update_state(opts)
-  opts = opts or {}
-
+function M.update_state()
   ---@type table<"opt"|"start", table<string,FileType>>
   local installed = { opt = {}, start = {} }
-  if opts.installed ~= false then
-    for opt, packs in pairs(installed) do
-      Util.ls(Config.options.packpath .. "/" .. opt, function(_, name, type)
-        if type == "directory" or type == "link" then
-          packs[name] = type
-        end
-      end)
-    end
+  for opt, packs in pairs(installed) do
+    Util.ls(Config.options.packpath .. "/" .. opt, function(_, name, type)
+      if type == "directory" or type == "link" then
+        packs[name] = type
+      end
+    end)
   end
 
-  for _, plugin in pairs(opts.plugins or Config.plugins) do
+  for _, plugin in pairs(Config.plugins) do
     plugin._ = plugin._ or {}
-    plugin[1] = plugin["1"] or plugin[1]
     if plugin.opt == nil then
       plugin.opt = plugin.dep
         or Config.options.opt
@@ -224,121 +179,52 @@ function M.update_state(opts)
     end
   end
 
-  if opts.clean then
-    Config.to_clean = {}
-    for opt, packs in pairs(installed) do
-      for pack in pairs(packs) do
-        table.insert(Config.to_clean, {
-          name = pack,
-          pack = pack,
-          dir = Config.options.packpath .. "/" .. opt .. "/" .. pack,
-          opt = opt == "opt",
-          _ = {
-            installed = true,
-          },
-        })
-      end
+  Config.to_clean = {}
+  for opt, packs in pairs(installed) do
+    for pack, dir_type in pairs(packs) do
+      table.insert(Config.to_clean, {
+        name = pack,
+        dir = Config.options.packpath .. "/" .. opt .. "/" .. pack,
+        opt = opt == "opt",
+        _ = {
+          installed = true,
+          is_symlink = dir_type == "link",
+          is_local = dir_type == "link",
+        },
+      })
     end
   end
 end
 
----@param plugin LazyPlugin
-function M.process_local(plugin)
-  for _, pattern in ipairs(Config.options.plugins_local.patterns) do
-    if plugin[1]:find(pattern, 1, true) then
-      plugin.uri = Config.options.plugins_local.path .. "/" .. plugin.name
-      return
-    end
-  end
-end
-
----@param cache? table<string,LazySpecLoader>
-function M.specs(cache)
-  ---@type LazySpecLoader[]
-  local specs = {}
+function M.spec()
+  local spec = Spec.new()
 
   local function _load(name, modpath)
     local modname = Config.options.plugins .. (name and ("." .. name) or "")
     Util.try(function()
-      local spec = cache and cache[modname]
-      spec = spec and not Module.is_dirty(modname, modpath) and Spec.revive(spec) or Spec.load(modname, modpath)
-      table.insert(specs, spec)
+      local mod = Module.load(modname, modpath)
+      spec:normalize(assert(mod))
     end, "Failed to load **" .. modname .. "**")
   end
 
   _load(nil, Config.paths.main)
   Util.lsmod(Config.paths.plugins, _load)
-  return specs
+  return spec
 end
 
 function M.load()
-  ---@type boolean, LazyState?
-  local ok, state = pcall(vim.json.decode, Cache.get("cache.state"))
-  if not (ok and state and vim.deep_equal(Config.options, state.config)) then
-    M.dirty = true
-    state = nil
-  end
-
   -- load specs
-  Util.track("specs")
-  local specs = M.specs(state and state.specs)
-  Util.track()
-
-  -- merge
-  Config.plugins = {}
-  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 M.merge(other, plugin) or plugin
-    end
+  Util.track("spec")
+  local spec = M.spec()
+  if not spec.plugins["lazy.nvim"] then
+    spec:add({ "folke/lazy.nvim", opt = false })
   end
+  Config.plugins = spec.plugins
+  Util.track()
 
   Util.track("state")
   M.update_state()
   Util.track()
-
-  if M.dirty then
-    Cache.dirty = true
-  elseif state then
-    require("lazy.core.handler")._groups = state.handlers
-  end
-end
-
-function M.save()
-  ---@class LazyState
-  local state = {
-    ---@type table<string, LazySpecLoader>
-    specs = {},
-    handlers = require("lazy.core.handler").group(Config.plugins, true),
-    config = Config.options,
-  }
-
-  for _, spec in ipairs(M.specs()) do
-    spec.funs = {}
-    state.specs[spec.modname] = spec
-    for _, plugin in pairs(spec.plugins) do
-      if plugin.init or (plugin.opt == false and plugin.config) then
-        -- no use in caching specs that need init,
-        -- or specs that are in start and have a config,
-        -- since we'll load the real spec during startup anyway
-        state.specs[spec.modname] = nil
-        break
-      end
-      ---@cast plugin CachedPlugin
-      for k, v in pairs(plugin) do
-        if type(v) == "function" then
-          if funs[k] then
-            spec.funs[k] = spec.funs[k] or {}
-            table.insert(spec.funs[k], plugin.name)
-          end
-          plugin[k] = nil
-        elseif skip[k] then
-          plugin[k] = nil
-        end
-      end
-    end
-  end
-  Cache.set("cache.state", vim.json.encode(state))
 end
 
 return M
diff --git a/lua/lazy/init.lua b/lua/lazy/init.lua
index 06fa663..50aa05f 100644
--- a/lua/lazy/init.lua
+++ b/lua/lazy/init.lua
@@ -10,6 +10,7 @@ function M.setup(opts)
   local Util = require("lazy.core.util")
   local Config = require("lazy.core.config")
   local Loader = require("lazy.core.loader")
+  local Handler = require("lazy.core.handler")
   local Plugin = require("lazy.core.plugin")
 
   Util.track("cache", module_start - cache_start)
@@ -21,9 +22,7 @@ function M.setup(opts)
   Config.setup(opts)
   Util.track()
 
-  Util.track("state")
   Plugin.load()
-  Util.track()
 
   if Config.options.install_missing then
     Util.track("install")
@@ -40,12 +39,13 @@ function M.setup(opts)
     Util.track()
   end
 
-  Util.track("loader")
-  Loader.setup()
+  Util.track("handlers")
+  Handler.setup()
   Util.track()
 
   local lazy_delta = vim.loop.hrtime() - cache_start
 
+  Util.track() -- end setup
   Loader.init_plugins()
 
   if Config.plugins["lazy.nvim"] then
@@ -53,23 +53,16 @@ function M.setup(opts)
   end
 
   vim.cmd("do User LazyDone")
-  Util.track() -- end setup
 end
 
 function M.stats()
-  local ret = {
-    count = 0,
-    loaded = 0,
-  }
-
+  local ret = { count = 0, loaded = 0 }
   for _, plugin in pairs(require("lazy.core.config").plugins) do
     ret.count = ret.count + 1
-
     if plugin._.loaded then
       ret.loaded = ret.loaded + 1
     end
   end
-
   return ret
 end
 
diff --git a/lua/lazy/manage/init.lua b/lua/lazy/manage/init.lua
index d77115f..5dac6e9 100644
--- a/lua/lazy/manage/init.lua
+++ b/lua/lazy/manage/init.lua
@@ -115,7 +115,7 @@ end
 
 ---@param opts? ManagerOpts
 function M.clean(opts)
-  Plugin.update_state({ clean = true })
+  Plugin.update_state()
   M.run({
     pipeline = { "fs.clean" },
     plugins = Config.to_clean,
@@ -123,7 +123,7 @@ function M.clean(opts)
 end
 
 function M.clear()
-  Plugin.update_state({ clean = true })
+  Plugin.update_state()
   for _, plugin in pairs(Config.plugins) do
     plugin._.updated = nil
     plugin._.cloned = nil
diff --git a/tests/core/plugin_spec.lua b/tests/core/plugin_spec.lua
index 1e47788..acf3424 100644
--- a/tests/core/plugin_spec.lua
+++ b/tests/core/plugin_spec.lua
@@ -38,7 +38,8 @@ describe("plugin spec opt", function()
     }
     for _, test in ipairs(tests) do
       local spec = Plugin.Spec.new(test)
-      Plugin.update_state({ plugins = spec.plugins })
+      Config.plugins = spec.plugins
+      Plugin.update_state()
       assert(vim.tbl_count(spec.plugins) == 3)
       assert(#spec.plugins.bar.dependencies == 2)
       assert(spec.plugins.bar.dep ~= true)
@@ -53,7 +54,8 @@ describe("plugin spec opt", function()
   it("handles opt from dep", function()
     Config.options.opt = false
     local spec = Plugin.Spec.new({ "foo/dep1", { "foo/bar", dependencies = { "foo/dep1", "foo/dep2" } } })
-    Plugin.update_state({ plugins = spec.plugins })
+    Config.plugins = spec.plugins
+    Plugin.update_state()
     assert.same(3, vim.tbl_count(spec.plugins))
     assert(spec.plugins.bar.dep ~= true)
     assert(spec.plugins.bar.opt == false)
@@ -66,7 +68,8 @@ describe("plugin spec opt", function()
   it("handles opt from dep", function()
     Config.options.opt = false
     local spec = Plugin.Spec.new({ "foo/bar", module = "foo" })
-    Plugin.update_state({ plugins = spec.plugins })
+    Config.plugins = spec.plugins
+    Plugin.update_state()
     assert.same(1, vim.tbl_count(spec.plugins))
     assert(spec.plugins.bar.dep ~= true)
     assert(spec.plugins.bar.opt == true)