Explorar o código

refactor: migrate to lazy.nvim (#3647)

* refactor: convert plugins spec to lazy

* refactor(lazy): remove impatient

* fix(telescope): no more errors if theme is nil

* refactor(lazy): use lazy in plugin_loader

* refactor(lazy): pin plugins with packer's snapshot

* fix: add plugins to rtp before config:init

* fix: fs_stat nil check

* feat: lazy cache

* feat(lazy): reloading

* refactor(lazy): plugin-loader functions

* feat(lazy): cache reset

* refactor: set runtimepath manually

* fix: runtimepath

* refactor(rtp)

* refactor(lazy): packer -> lazy in various places

* fix(lazy): disable tree-sitter ensure installed

* refactor(lazy): restore order to bootstrap

* refactor(lazy): remove unused impatient profiler

* small fixes

* `lvim.plugins` deprecation handling

* fix: deprecation of `requires` in plugin specs

* feat: core plugins pinning

* refactor(lazy): plugin loader tests

* refactor(lazy): use lazy in scripts

* refactor(lazy): which-key keybinds

* chore: format

* fix: installer

* fix: first time setup

* feat: changes required for packaging

commit 951ac2b7c01b5200b973660c967852d1706cce28
Author: LostNeophyte <lostneophyte@tuta.io>
Date:   Wed Dec 28 13:49:44 2022 +0100

    fix: clean folder before copying plugins

commit 64e9afa44b8e528ba527e0510d0d8c2d2237a095
Author: LostNeophyte <lostneophyte@tuta.io>
Date:   Wed Dec 28 13:35:41 2022 +0100

    feat: copy core plugins on first run

commit 2d8e72090c7624f68c09a9aa6582223373a810c1
Author: LostNeophyte <lostneophyte@tuta.io>
Date:   Wed Dec 28 13:11:22 2022 +0100

    feat(utils): fs_copy

commit 85c1f025a6ba13183e85141f75f60e2eefc77bb5
Author: LostNeophyte <lostneophyte@tuta.io>
Date:   Wed Dec 28 13:04:38 2022 +0100

    fix: copy correct example config

* fix: packer specs deprecation handling

* fix: plugin specs deprecation

* feat: pin lazy's version

* fix: remove plugins form rtp before loading lazy

* fix: plugin-loader test

* feat(lazy): add keymappings for profile, log, and debug (#3665)

* feat(lazy): Add keymappings for profile, log, and debug

* feat(lazy): Add keymap for cleaning

* chore: format

* pref: lazy load many plugins

Co-authored-by: Uzair Aftab <uzaaft@outlook.com>

* fix: bootstrap correct version of lazy

* fix: also use CmdLineEnter event for cmp

* fix: don't use lazy's modules before it's set up

* perf: (hack) enable lazy's cache before loading lazy

* fix: plugins.lua

* fix: plugins bump script

* chore: remove debug print

* feat: add rounded border for `:Lazy`

* fix: bufferline flashing

* fix: don't close lazy on startup

* fix: load breadcrumbs on startup

* fix: don't lazy load bufferline

* chore: bump lazy's version

* fix: remove site from rtp (fixes treesitter issues)

* revert default config copying changes

* fix(bootstrap): actually remove plugins dir on windows

* chore: bump lazy's version

* chore: bump lazy's version

Co-authored-by: kylo252 <59826753+kylo252@users.noreply.github.com>
Co-authored-by: Uzair Aftab <48220549+Uzaaft@users.noreply.github.com>
Co-authored-by: Uzair Aftab <uzaaft@outlook.com>
Co-authored-by: opalmay <opal.mizrahi2@gmail.com>
LostNeophyte %!s(int64=2) %!d(string=hai) anos
pai
achega
ccb80e41ee

+ 11 - 16
lua/lvim/bootstrap.lua

@@ -3,6 +3,7 @@ local M = {}
 if vim.fn.has "nvim-0.8" ~= 1 then
   vim.notify("Please upgrade your Neovim base installation. Lunarvim requires v0.8+", vim.log.levels.WARN)
   vim.wait(5000, function()
+    ---@diagnostic disable-next-line: redundant-return-value
     return false
   end)
   vim.cmd "cquit"
@@ -23,7 +24,7 @@ _G.require_safe = require("lvim.utils.modules").require_safe
 _G.reload = require("lvim.utils.modules").reload
 
 ---Get the full path to `$LUNARVIM_RUNTIME_DIR`
----@return string
+---@return string|nil
 function _G.get_runtime_dir()
   local lvim_runtime_dir = os.getenv "LUNARVIM_RUNTIME_DIR"
   if not lvim_runtime_dir then
@@ -34,7 +35,7 @@ function _G.get_runtime_dir()
 end
 
 ---Get the full path to `$LUNARVIM_CONFIG_DIR`
----@return string
+---@return string|nil
 function _G.get_config_dir()
   local lvim_config_dir = os.getenv "LUNARVIM_CONFIG_DIR"
   if not lvim_config_dir then
@@ -44,7 +45,7 @@ function _G.get_config_dir()
 end
 
 ---Get the full path to `$LUNARVIM_CACHE_DIR`
----@return string
+---@return string|nil
 function _G.get_cache_dir()
   local lvim_cache_dir = os.getenv "LUNARVIM_CACHE_DIR"
   if not lvim_cache_dir then
@@ -60,11 +61,11 @@ function M:init(base_dir)
   self.config_dir = get_config_dir()
   self.cache_dir = get_cache_dir()
   self.pack_dir = join_paths(self.runtime_dir, "site", "pack")
-  self.packer_install_dir = join_paths(self.runtime_dir, "site", "pack", "packer", "start", "packer.nvim")
-  self.packer_cache_path = join_paths(self.config_dir, "plugin", "packer_compiled.lua")
+  self.lazy_install_dir = join_paths(self.pack_dir, "lazy", "opt", "lazy.nvim")
 
   ---@meta overridden to use LUNARVIM_CACHE_DIR instead, since a lot of plugins call this function internally
   ---NOTE: changes to "data" are currently unstable, see #2507
+  ---@diagnostic disable-next-line: duplicate-set-field
   vim.fn.stdpath = function(what)
     if what == "cache" then
       return _G.get_cache_dir()
@@ -79,10 +80,9 @@ function M:init(base_dir)
   end
 
   if os.getenv "LUNARVIM_RUNTIME_DIR" then
-    -- vim.opt.rtp:append(os.getenv "LUNARVIM_RUNTIME_DIR" .. path_sep .. "lvim")
     vim.opt.rtp:remove(join_paths(vim.call("stdpath", "data"), "site"))
     vim.opt.rtp:remove(join_paths(vim.call("stdpath", "data"), "site", "after"))
-    vim.opt.rtp:prepend(join_paths(self.runtime_dir, "site"))
+    -- vim.opt.rtp:prepend(join_paths(self.runtime_dir, "site"))
     vim.opt.rtp:append(join_paths(self.runtime_dir, "lvim", "after"))
     vim.opt.rtp:append(join_paths(self.runtime_dir, "site", "after"))
 
@@ -90,22 +90,17 @@ function M:init(base_dir)
     vim.opt.rtp:remove(join_paths(vim.call("stdpath", "config"), "after"))
     vim.opt.rtp:prepend(self.config_dir)
     vim.opt.rtp:append(join_paths(self.config_dir, "after"))
-    -- TODO: we need something like this: vim.opt.packpath = vim.opt.rtp
-
-    vim.cmd [[let &packpath = &runtimepath]]
-  end
 
-  if not vim.env.LVIM_TEST_ENV then
-    require "lvim.impatient"
+    vim.opt.packpath = vim.opt.rtp:get()
   end
 
-  require("lvim.config"):init()
-
   require("lvim.plugin-loader").init {
     package_root = self.pack_dir,
-    install_path = self.packer_install_dir,
+    install_path = self.lazy_install_dir,
   }
 
+  require("lvim.config"):init()
+
   require("lvim.core.mason").bootstrap()
 
   return self

+ 88 - 13
lua/lvim/config/_deprecated.lua

@@ -41,6 +41,24 @@ function M.handle()
     end,
   })
 
+  ---@deprecated
+  lvim.builtin.dashboard = {}
+  setmetatable(lvim.builtin.dashboard, {
+    __newindex = function(_, k, _)
+      deprecate("lvim.builtin.dashboard." .. k, "Use `lvim.builtin.alpha` instead. See LunarVim#1906")
+    end,
+  })
+
+  ---@deprecated
+  lvim.lsp.popup_border = {}
+  setmetatable(lvim.lsp.popup_border, mt)
+
+  ---@deprecated
+  lvim.lang = {}
+  setmetatable(lvim.lang, mt)
+end
+
+function M.post_load()
   if lvim.lsp.override and not vim.tbl_isempty(lvim.lsp.override) then
     deprecate("lvim.lsp.override", "Use `lvim.lsp.automatic_configuration.skipped_servers` instead")
     vim.tbl_map(function(c)
@@ -64,21 +82,78 @@ function M.handle()
     )
   end
 
-  ---@deprecated
-  lvim.builtin.dashboard = {}
-  setmetatable(lvim.builtin.dashboard, {
-    __newindex = function(_, k, _)
-      deprecate("lvim.builtin.dashboard." .. k, "Use `lvim.builtin.alpha` instead. See LunarVim#1906")
-    end,
-  })
+  local function convert_spec_to_lazy(spec)
+    local alternatives = {
+      setup = "init",
+      as = "name",
+      opt = "lazy",
+      run = "build",
+      lock = "pin",
+      tag = "version",
+    }
 
-  ---@deprecated
-  lvim.lsp.popup_border = {}
-  setmetatable(lvim.lsp.popup_border, mt)
+    alternatives.requires = function()
+      if type(spec.requires) == "string" then
+        spec.dependencies = { spec.requires }
+      else
+        spec.dependencies = spec.requires
+      end
 
-  ---@deprecated
-  lvim.lang = {}
-  setmetatable(lvim.lang, mt)
+      return "Use `dependencies` instead"
+    end
+
+    alternatives.disable = function()
+      if type(spec.disabled) == "function" then
+        spec.enabled = function()
+          return not spec.disabled()
+        end
+      else
+        spec.enabled = not spec.disabled
+      end
+      return "Use `enabled` instead"
+    end
+
+    alternatives.wants = function()
+      return "It's not needed in most cases, otherwise use `dependencies`."
+    end
+    alternatives.needs = alternatives.wants
+
+    alternatives.module = function()
+      spec.lazy = true
+      return "Use `lazy = true` instead."
+    end
+
+    for old_key, alternative in pairs(alternatives) do
+      if spec[old_key] ~= nil then
+        local message
+
+        if type(alternative) == "function" then
+          message = alternative()
+        else
+          spec[alternative] = spec[old_key]
+        end
+        spec[old_key] = nil
+
+        message = message or string.format("Use `%s` instead.", alternative)
+        deprecate(
+          string.format("%s` in `lvim.plugins", old_key),
+          message .. " See https://github.com/folke/lazy.nvim#-migration-guide"
+        )
+      end
+    end
+
+    if spec[1] and spec[1]:match "^http" then
+      spec.url = spec[1]
+      spec[1] = nil
+      deprecate("{ 'http...' }` in `lvim.plugins", "Use { url = 'http...' } instead.")
+    end
+  end
+
+  for _, plugin in ipairs(lvim.plugins) do
+    if type(plugin) == "table" then
+      convert_spec_to_lazy(plugin)
+    end
+  end
 end
 
 return M

+ 2 - 0
lua/lvim/config/init.lua

@@ -63,6 +63,8 @@ function M:load(config_path)
 
   Log:set_level(lvim.log.level)
 
+  require("lvim.config._deprecated").post_load()
+
   autocmds.define_autocmds(lvim.autocommands)
 
   vim.g.mapleader = (lvim.leader == "space" and " ") or lvim.leader

+ 1 - 1
lua/lvim/core/breadcrumbs.lua

@@ -12,7 +12,7 @@ M.config = function()
       "help",
       "startify",
       "dashboard",
-      "packer",
+      "lazy",
       "neo-tree",
       "neogitstatus",
       "NvimTree",

+ 2 - 2
lua/lvim/core/bufferline.lua

@@ -117,8 +117,8 @@ M.config = function()
           highlight = "PanelHeading",
         },
         {
-          filetype = "packer",
-          text = "Packer",
+          filetype = "lazy",
+          text = "Lazy",
           highlight = "PanelHeading",
           padding = 1,
         },

+ 1 - 1
lua/lvim/core/illuminate.lua

@@ -23,7 +23,7 @@ M.config = function()
         "fugitive",
         "alpha",
         "NvimTree",
-        "packer",
+        "lazy",
         "neogitstatus",
         "Trouble",
         "lir",

+ 1 - 1
lua/lvim/core/indentlines.lua

@@ -11,7 +11,7 @@ M.config = function()
         "help",
         "startify",
         "dashboard",
-        "packer",
+        "lazy",
         "neogitstatus",
         "NvimTree",
         "Trouble",

+ 0 - 7
lua/lvim/core/log.lua

@@ -21,13 +21,6 @@ function Log:set_level(level)
       s.level = log_level
     end
   end
-
-  local packer_ok, _ = xpcall(function()
-    require("packer.log").cfg { log = { level = level } }
-  end, debug.traceback)
-  if not packer_ok then
-    vim.notify_once("Unable to set packer's log level to " .. level)
-  end
 end
 
 function Log:init()

+ 1 - 1
lua/lvim/core/nvimtree.lua

@@ -185,7 +185,7 @@ function M.config()
             picker = "default",
             chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
             exclude = {
-              filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" },
+              filetype = { "notify", "lazy", "qf", "diff", "fugitive", "fugitiveblame" },
               buftype = { "nofile", "terminal", "help" },
             },
           },

+ 9 - 12
lua/lvim/core/which-key.lua

@@ -156,13 +156,15 @@ M.config = function()
         U = { "<cmd>lua require'dapui'.toggle({reset = true})<cr>", "Toggle UI" },
       },
       p = {
-        name = "Packer",
-        c = { "<cmd>PackerCompile<cr>", "Compile" },
-        i = { "<cmd>PackerInstall<cr>", "Install" },
-        r = { "<cmd>lua require('lvim.plugin-loader').recompile()<cr>", "Re-compile" },
-        s = { "<cmd>PackerSync<cr>", "Sync" },
-        S = { "<cmd>PackerStatus<cr>", "Status" },
-        u = { "<cmd>PackerUpdate<cr>", "Update" },
+        name = "Plugins",
+        i = { "<cmd>Lazy install<cr>", "Install" },
+        s = { "<cmd>Lazy sync<cr>", "Sync" },
+        S = { "<cmd>Lazy clear<cr>", "Status" },
+        c = { "<cmd>Lazy clean<cr>", "Clean" },
+        u = { "<cmd>Lazy update<cr>", "Update" },
+        p = { "<cmd>Lazy profile<cr>", "Profile" },
+        l = { "<cmd>Lazy log<cr>", "Log" },
+        d = { "<cmd>Lazy debug<cr>", "Debug" },
       },
 
       -- " Available Debug Adapters:
@@ -268,11 +270,6 @@ M.config = function()
             "view neovim log",
           },
           N = { "<cmd>edit $NVIM_LOG_FILE<cr>", "Open the Neovim logfile" },
-          p = {
-            "<cmd>lua require('lvim.core.terminal').toggle_log_view(get_cache_dir() .. '/packer.nvim.log')<cr>",
-            "view packer log",
-          },
-          P = { "<cmd>edit $LUNARVIM_CACHE_DIR/packer.nvim.log<cr>", "Open the Packer logfile" },
         },
         r = { "<cmd>LvimReload<cr>", "Reload LunarVim's configuration" },
         u = { "<cmd>LvimUpdate<cr>", "Update LunarVim" },

+ 0 - 477
lua/lvim/impatient.lua

@@ -1,477 +0,0 @@
--- modified version from https://github.com/lewis6991/impatient.nvim
-local vim = vim
-local api = vim.api
-local uv = vim.loop
-local _loadfile = loadfile
-local get_runtime = api.nvim__get_runtime
-local fs_stat = uv.fs_stat
-local mpack = vim.mpack
-local loadlib = package.loadlib
-
-local std_cache = vim.fn.stdpath "cache"
-
-local sep
-if jit.os == "Windows" then
-  sep = "\\"
-else
-  sep = "/"
-end
-
-local std_dirs = {
-  ["<APPDIR>"] = os.getenv "APPDIR",
-  ["<VIMRUNTIME>"] = os.getenv "VIMRUNTIME",
-  ["<STD_DATA>"] = vim.fn.stdpath "data",
-  ["<STD_CONFIG>"] = vim.fn.stdpath "config",
-  ["<LVIM_BASE>"] = get_lvim_base_dir(),
-  ["<LVIM_RUNTIME>"] = get_runtime_dir(),
-  ["<LVIM_CONFIG>"] = get_config_dir(),
-}
-
-local function modpath_mangle(modpath)
-  for name, dir in pairs(std_dirs) do
-    modpath = modpath:gsub(dir, name)
-  end
-  return modpath
-end
-
-local function modpath_unmangle(modpath)
-  for name, dir in pairs(std_dirs) do
-    modpath = modpath:gsub(name, dir)
-  end
-  return modpath
-end
-
--- Overridable by user
-local default_config = {
-  chunks = {
-    enable = true,
-    path = std_cache .. sep .. "luacache_chunks",
-  },
-  modpaths = {
-    enable = true,
-    path = std_cache .. sep .. "luacache_modpaths",
-  },
-}
-
--- State used internally
-local default_state = {
-  chunks = {
-    cache = {},
-    profile = nil,
-    dirty = false,
-    get = function(self, path)
-      return self.cache[modpath_mangle(path)]
-    end,
-    set = function(self, path, chunk)
-      self.cache[modpath_mangle(path)] = chunk
-    end,
-  },
-  modpaths = {
-    cache = {},
-    profile = nil,
-    dirty = false,
-    get = function(self, mod)
-      if self.cache[mod] then
-        return modpath_unmangle(self.cache[mod])
-      end
-    end,
-    set = function(self, mod, path)
-      self.cache[mod] = modpath_mangle(path)
-    end,
-  },
-  log = {},
-}
-
----@diagnostic disable-next-line: undefined-field
-local M = vim.tbl_deep_extend("keep", _G.__luacache_config or {}, default_config, default_state)
-_G.__luacache = M
-
-local function log(...)
-  M.log[#M.log + 1] = table.concat({ string.format(...) }, " ")
-end
-
-local function print_log()
-  for _, l in ipairs(M.log) do
-    print(l)
-  end
-end
-
-local function hash(modpath)
-  local stat = fs_stat(modpath)
-  if stat then
-    return stat.mtime.sec .. stat.mtime.nsec .. stat.size
-  end
-  error("Could not hash " .. modpath)
-end
-
-local function profile(m, entry, name, loader)
-  if m.profile then
-    local mp = m.profile
-    mp[entry] = mp[entry] or {}
-    if not mp[entry].loader and loader then
-      mp[entry].loader = loader
-    end
-    if not mp[entry][name] then
-      mp[entry][name] = uv.hrtime()
-    end
-  end
-end
-
-local function mprofile(mod, name, loader)
-  profile(M.modpaths, mod, name, loader)
-end
-
-local function cprofile(path, name, loader)
-  if M.chunks.profile then
-    path = modpath_mangle(path)
-  end
-  profile(M.chunks, path, name, loader)
-end
-
-function M.enable_profile()
-  local P = require "lvim.impatient.profile"
-
-  M.chunks.profile = {}
-  M.modpaths.profile = {}
-
-  loadlib = function(path, fun)
-    cprofile(path, "load_start")
-    local f, err = package.loadlib(path, fun)
-    cprofile(path, "load_end", "standard")
-    return f, err
-  end
-
-  P.setup(M.modpaths.profile)
-
-  api.nvim_create_user_command("LuaCacheProfile", function()
-    P.print_profile(M, std_dirs)
-  end, {})
-end
-
-local function get_runtime_file_from_parent(basename, paths)
-  -- Look in the cache to see if we have already loaded a parent module.
-  -- If we have then try looking in the parents directory first.
-  local parents = vim.split(basename, sep)
-  for i = #parents, 1, -1 do
-    local parent = table.concat(vim.list_slice(parents, 1, i), sep)
-    local ppath = M.modpaths:get(parent)
-    if ppath then
-      if ppath:sub(-9) == (sep .. "init.lua") then
-        ppath = ppath:sub(1, -10) -- a/b/init.lua -> a/b
-      else
-        ppath = ppath:sub(1, -5) -- a/b.lua -> a/b
-      end
-
-      for _, path in ipairs(paths) do
-        -- path should be of form 'a/b/c.lua' or 'a/b/c/init.lua'
-        local modpath = ppath .. sep .. path:sub(#("lua" .. sep .. parent) + 2)
-        if fs_stat(modpath) then
-          return modpath, "cache(p)"
-        end
-      end
-    end
-  end
-end
-
-local rtp = vim.split(vim.o.rtp, ",")
-
--- Make sure modpath is in rtp and that modpath is in paths.
-local function validate_modpath(modpath, paths)
-  local match = false
-  for _, p in ipairs(paths) do
-    if vim.endswith(modpath, p) then
-      match = true
-      break
-    end
-  end
-  if not match then
-    return false
-  end
-  for _, dir in ipairs(rtp) do
-    if vim.startswith(modpath, dir) then
-      return fs_stat(modpath) ~= nil
-    end
-  end
-  return false
-end
-
-local function get_runtime_file_cached(basename, paths)
-  local modpath, loader
-  local mp = M.modpaths
-  if mp.enable then
-    local modpath_cached = mp:get(basename)
-    if modpath_cached then
-      modpath, loader = modpath_cached, "cache"
-    else
-      modpath, loader = get_runtime_file_from_parent(basename, paths)
-    end
-
-    if modpath and not validate_modpath(modpath, paths) then
-      modpath = nil
-
-      -- Invalidate
-      mp.cache[basename] = nil
-      mp.dirty = true
-    end
-  end
-
-  if not modpath then
-    -- What Neovim does by default; slowest
-    modpath, loader = get_runtime(paths, false, { is_lua = true })[1], "standard"
-  end
-
-  if modpath then
-    mprofile(basename, "resolve_end", loader)
-    if mp.enable and loader ~= "cache" then
-      log("Creating cache for module %s", basename)
-      mp:set(basename, modpath)
-      mp.dirty = true
-    end
-  end
-
-  return modpath
-end
-
-local function extract_basename(pats)
-  local basename
-
-  -- Deconstruct basename from pats
-  for _, pat in ipairs(pats) do
-    for i, npat in ipairs {
-      -- Ordered by most specific
-      "lua"
-        .. sep
-        .. "(.*)"
-        .. sep
-        .. "init%.lua",
-      "lua" .. sep .. "(.*)%.lua",
-    } do
-      local m = pat:match(npat)
-      if i == 2 and m and m:sub(-4) == "init" then
-        m = m:sub(0, -6)
-      end
-      if not basename then
-        if m then
-          basename = m
-        end
-      elseif m and m ~= basename then
-        -- matches are inconsistent
-        return
-      end
-    end
-  end
-
-  return basename
-end
-
-local function get_runtime_cached(pats, all, opts)
-  local fallback = false
-  if all or not opts or not opts.is_lua then
-    -- Fallback
-    fallback = true
-  end
-
-  local basename
-
-  if not fallback then
-    basename = extract_basename(pats)
-  end
-
-  if fallback or not basename then
-    return get_runtime(pats, all, opts)
-  end
-
-  return { get_runtime_file_cached(basename, pats) }
-end
-
--- Copied from neovim/src/nvim/lua/vim.lua with two lines changed
-local function load_package(name)
-  local basename = name:gsub("%.", sep)
-  local paths = { "lua" .. sep .. basename .. ".lua", "lua" .. sep .. basename .. sep .. "init.lua" }
-
-  -- Original line:
-  -- local found = vim.api.nvim__get_runtime(paths, false, {is_lua=true})
-  local found = { get_runtime_file_cached(basename, paths) }
-  if #found > 0 then
-    local f, err = loadfile(found[1])
-    return f or error(err)
-  end
-
-  local so_paths = {}
-  for _, trail in ipairs(vim._so_trails) do
-    local path = "lua" .. trail:gsub("?", basename) -- so_trails contains a leading slash
-    table.insert(so_paths, path)
-  end
-
-  -- Original line:
-  -- found = vim.api.nvim__get_runtime(so_paths, false, {is_lua=true})
-  found = { get_runtime_file_cached(basename, so_paths) }
-  if #found > 0 then
-    -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
-    -- a) strip prefix up to and including the first dash, if any
-    -- b) replace all dots by underscores
-    -- c) prepend "luaopen_"
-    -- So "foo-bar.baz" should result in "luaopen_bar_baz"
-    local dash = name:find("-", 1, true)
-    local modname = dash and name:sub(dash + 1) or name
-    local f, err = loadlib(found[1], "luaopen_" .. modname:gsub("%.", "_"))
-    return f or error(err)
-  end
-  return nil
-end
-
-local function load_from_cache(path)
-  local mc = M.chunks
-
-  local cache = mc:get(path)
-
-  if not cache then
-    return nil, string.format("No cache for path %s", path)
-  end
-
-  local mhash, codes = unpack(cache)
-
-  if mhash ~= hash(path) then
-    mc:set(path)
-    mc.dirty = true
-    return nil, string.format("Stale cache for path %s", path)
-  end
-
-  local chunk = loadstring(codes)
-
-  if not chunk then
-    mc:set(path)
-    mc.dirty = true
-    return nil, string.format("Cache error for path %s", path)
-  end
-
-  return chunk
-end
-
-local function loadfile_cached(path)
-  cprofile(path, "load_start")
-
-  local chunk, err
-
-  if M.chunks.enable then
-    chunk, err = load_from_cache(path)
-    if chunk and not err then
-      log("Loaded cache for path %s", path)
-      cprofile(path, "load_end", "cache")
-      return chunk
-    end
-    log(err)
-  end
-
-  chunk, err = _loadfile(path)
-
-  if not err and M.chunks.enable then
-    log("Creating cache for path %s", path)
-    M.chunks:set(path, { hash(path), string.dump(chunk) })
-    M.chunks.dirty = true
-  end
-
-  cprofile(path, "load_end", "standard")
-  return chunk, err
-end
-
-function M.save_cache()
-  local function _save_cache(t)
-    if not t.enable then
-      return
-    end
-    if t.dirty then
-      log("Updating chunk cache file: %s", t.path)
-      local f = assert(io.open(t.path, "w+b"))
-      f:write(mpack.encode(t.cache))
-      f:flush()
-      t.dirty = false
-    end
-  end
-  _save_cache(M.chunks)
-  _save_cache(M.modpaths)
-end
-
-local function clear_cache()
-  local function _clear_cache(t)
-    t.cache = {}
-    os.remove(t.path)
-  end
-  _clear_cache(M.chunks)
-  _clear_cache(M.modpaths)
-end
-
-local function init_cache()
-  local function _init_cache(t)
-    if not t.enable then
-      return
-    end
-    if fs_stat(t.path) then
-      log("Loading cache file %s", t.path)
-      local f = assert(io.open(t.path, "rb"))
-      local ok
-      ok, t.cache = pcall(function()
-        return mpack.decode(f:read "*a")
-      end)
-
-      if not ok then
-        log("Corrupted cache file, %s. Invalidating...", t.path)
-        os.remove(t.path)
-        t.cache = {}
-      end
-      t.dirty = not ok
-    end
-  end
-
-  if not uv.fs_stat(std_cache) then
-    vim.fn.mkdir(std_cache, "p")
-  end
-
-  _init_cache(M.chunks)
-  _init_cache(M.modpaths)
-end
-
-local function setup()
-  init_cache()
-
-  -- Usual package loaders
-  -- 1. package.preload
-  -- 2. vim._load_package
-  -- 3. package.path
-  -- 4. package.cpath
-  -- 5. all-in-one
-
-  -- Override default functions
-  for i, loader in ipairs(package.loaders) do
-    if loader == vim._load_package then
-      package.loaders[i] = load_package
-      break
-    end
-  end
-  vim._load_package = load_package
-
-  vim.api.nvim__get_runtime = get_runtime_cached
-  loadfile = loadfile_cached
-
-  local augroup = api.nvim_create_augroup("impatient", {})
-
-  api.nvim_create_user_command("LuaCacheClear", clear_cache, {})
-  api.nvim_create_user_command("LuaCacheLog", print_log, {})
-
-  api.nvim_create_autocmd({ "VimEnter", "VimLeave" }, {
-    group = augroup,
-    callback = M.save_cache,
-  })
-
-  api.nvim_create_autocmd("OptionSet", {
-    group = augroup,
-    pattern = "runtimepath",
-    callback = function()
-      rtp = vim.split(vim.o.rtp, ",")
-    end,
-  })
-end
-
-setup()
-
-return M

+ 0 - 253
lua/lvim/impatient/profile.lua

@@ -1,253 +0,0 @@
-local M = {}
-
-local sep
-if jit.os == "Windows" then
-  sep = "\\"
-else
-  sep = "/"
-end
-
-local api, uv = vim.api, vim.loop
-
-local function load_buffer(title, lines)
-  local bufnr = api.nvim_create_buf(false, false)
-  api.nvim_buf_set_lines(bufnr, 0, 0, false, lines)
-  api.nvim_buf_set_option(bufnr, "bufhidden", "wipe")
-  api.nvim_buf_set_option(bufnr, "buftype", "nofile")
-  api.nvim_buf_set_option(bufnr, "swapfile", false)
-  api.nvim_buf_set_option(bufnr, "modifiable", false)
-  api.nvim_buf_set_name(bufnr, title)
-  api.nvim_set_current_buf(bufnr)
-end
-
-local function time_tostr(x)
-  if x == 0 then
-    return "?"
-  end
-  return string.format("%8.3fms", x)
-end
-
-local function mem_tostr(x)
-  local unit = ""
-  for _, u in ipairs { "K", "M", "G" } do
-    if x < 1000 then
-      break
-    end
-    x = x / 1000
-    unit = u
-  end
-  return string.format("%1.1f%s", x, unit)
-end
-
-function M.print_profile(I, std_dirs)
-  local mod_profile = I.modpaths.profile
-  local chunk_profile = I.chunks.profile
-
-  if not mod_profile and not chunk_profile then
-    print "Error: profiling was not enabled"
-    return
-  end
-
-  local total_resolve = 0
-  local total_load = 0
-  local modules = {}
-
-  for path, m in pairs(chunk_profile) do
-    m.load = m.load_end - m.load_start
-    m.load = m.load / 1000000
-    m.path = path or "?"
-  end
-
-  local module_content_width = 0
-
-  local unloaded = {}
-
-  for module, m in pairs(mod_profile) do
-    local module_dot = module:gsub(sep, ".")
-    m.module = module_dot
-
-    if not package.loaded[module_dot] and not package.loaded[module] then
-      unloaded[#unloaded + 1] = m
-    else
-      m.resolve = 0
-      if m.resolve_start and m.resolve_end then
-        m.resolve = m.resolve_end - m.resolve_start
-        m.resolve = m.resolve / 1000000
-      end
-
-      m.loader = m.loader or m.loader_guess
-
-      local path = I.modpaths.cache[module]
-      local path_prof = chunk_profile[path]
-      m.path = path or "?"
-
-      if path_prof then
-        chunk_profile[path] = nil
-        m.load = path_prof.load
-        m.ploader = path_prof.loader
-      else
-        m.load = 0
-        m.ploader = "NA"
-      end
-
-      total_resolve = total_resolve + m.resolve
-      total_load = total_load + m.load
-
-      if #module > module_content_width then
-        module_content_width = #module
-      end
-
-      modules[#modules + 1] = m
-    end
-  end
-
-  table.sort(modules, function(a, b)
-    return (a.resolve + a.load) > (b.resolve + b.load)
-  end)
-
-  local paths = {}
-
-  local total_paths_load = 0
-  for _, m in pairs(chunk_profile) do
-    paths[#paths + 1] = m
-    total_paths_load = total_paths_load + m.load
-  end
-
-  table.sort(paths, function(a, b)
-    return a.load > b.load
-  end)
-
-  local lines = {}
-  local function add(fmt, ...)
-    local args = { ... }
-    for i, a in ipairs(args) do
-      if type(a) == "number" then
-        args[i] = time_tostr(a)
-      end
-    end
-
-    lines[#lines + 1] = string.format(fmt, unpack(args))
-  end
-
-  local time_cell_width = 12
-  local loader_cell_width = 11
-  local time_content_width = time_cell_width - 2
-  local loader_content_width = loader_cell_width - 2
-  local module_cell_width = module_content_width + 2
-
-  local tcwl = string.rep("─", time_cell_width)
-  local lcwl = string.rep("─", loader_cell_width)
-  local mcwl = string.rep("─", module_cell_width + 2)
-
-  local n = string.rep("─", 200)
-
-  local module_cell_format = "%-" .. module_cell_width .. "s"
-  local loader_format = "%-" .. loader_content_width .. "s"
-  local line_format = "%s │ %s │ %s │ %s │ %s │ %s"
-
-  local row_fmt = line_format:format(
-    " %" .. time_content_width .. "s",
-    loader_format,
-    "%" .. time_content_width .. "s",
-    loader_format,
-    module_cell_format,
-    "%s"
-  )
-
-  local title_fmt = line_format:format(
-    " %-" .. time_content_width .. "s",
-    loader_format,
-    "%-" .. time_content_width .. "s",
-    loader_format,
-    module_cell_format,
-    "%s"
-  )
-
-  local title1_width = time_cell_width + loader_cell_width - 1
-  local title1_fmt = ("%s │ %s │"):format(" %-" .. title1_width .. "s", "%-" .. title1_width .. "s")
-
-  add "Note: this report is not a measure of startup time. Only use this for comparing"
-  add "between cached and uncached loads of Lua modules"
-  add ""
-
-  add "Cache files:"
-  for _, f in ipairs { I.chunks.path, I.modpaths.path } do
-    local size = vim.loop.fs_stat(f).size
-    add("  %s %s", f, mem_tostr(size))
-  end
-  add ""
-
-  add "Standard directories:"
-  for alias, path in pairs(std_dirs) do
-    add("  %-12s -> %s", alias, path)
-  end
-  add ""
-
-  add("%s─%s┬%s─%s┐", tcwl, lcwl, tcwl, lcwl)
-  add(title1_fmt, "Resolve", "Load")
-  add("%s┬%s┼%s┬%s┼%s┬%s", tcwl, lcwl, tcwl, lcwl, mcwl, n)
-  add(title_fmt, "Time", "Method", "Time", "Method", "Module", "Path")
-  add("%s┼%s┼%s┼%s┼%s┼%s", tcwl, lcwl, tcwl, lcwl, mcwl, n)
-  add(row_fmt, total_resolve, "", total_load, "", "Total", "")
-  add("%s┼%s┼%s┼%s┼%s┼%s", tcwl, lcwl, tcwl, lcwl, mcwl, n)
-  for _, p in ipairs(modules) do
-    add(row_fmt, p.resolve, p.loader, p.load, p.ploader, p.module, p.path)
-  end
-  add("%s┴%s┴%s┴%s┴%s┴%s", tcwl, lcwl, tcwl, lcwl, mcwl, n)
-
-  if #paths > 0 then
-    add ""
-    add(n)
-    local f3 = " %" .. time_content_width .. "s │ %" .. loader_content_width .. "s │ %s"
-    add "Files loaded with no associated module"
-    add("%s┬%s┬%s", tcwl, lcwl, n)
-    add(f3, "Time", "Loader", "Path")
-    add("%s┼%s┼%s", tcwl, lcwl, n)
-    add(f3, total_paths_load, "", "Total")
-    add("%s┼%s┼%s", tcwl, lcwl, n)
-    for _, p in ipairs(paths) do
-      add(f3, p.load, p.loader, p.path)
-    end
-    add("%s┴%s┴%s", tcwl, lcwl, n)
-  end
-
-  if #unloaded > 0 then
-    add ""
-    add(n)
-    add "Modules which were unable to loaded"
-    add(n)
-    for _, p in ipairs(unloaded) do
-      lines[#lines + 1] = p.module
-    end
-    add(n)
-  end
-
-  load_buffer("Impatient Profile Report", lines)
-end
-
-M.setup = function(profile)
-  local _require = require
-
-  require = function(mod)
-    local basename = mod:gsub("%.", sep)
-    if not profile[basename] then
-      profile[basename] = {}
-      profile[basename].resolve_start = uv.hrtime()
-      profile[basename].loader_guess = ""
-    end
-    return _require(mod)
-  end
-
-  -- Add profiling around all the loaders
-  local pl = package.loaders
-  for i = 1, #pl do
-    local l = pl[i]
-    pl[i] = function(mod)
-      local basename = mod:gsub("%.", sep)
-      profile[basename].loader_guess = i == 1 and "preloader" or "loader #" .. i
-      return l(mod)
-    end
-  end
-end
-
-return M

+ 1 - 1
lua/lvim/lsp/providers/sumneko_lua.lua

@@ -53,7 +53,7 @@ local opts = {
         },
       },
       diagnostics = {
-        globals = { "vim", "lvim", "packer_plugins", "reload" },
+        globals = { "vim", "lvim", "reload" },
       },
       workspace = default_workspace,
     },

+ 104 - 122
lua/lvim/plugin-loader.lua

@@ -3,128 +3,127 @@ local plugin_loader = {}
 local utils = require "lvim.utils"
 local Log = require "lvim.core.log"
 local join_paths = utils.join_paths
-local in_headless = #vim.api.nvim_list_uis() == 0
 
--- we need to reuse this outside of init()
-local compile_path = join_paths(get_config_dir(), "plugin", "packer_compiled.lua")
-local snapshot_path = join_paths(get_cache_dir(), "snapshots")
-local default_snapshot = join_paths(get_lvim_base_dir(), "snapshots", "default.json")
+local plugins_dir = join_paths(get_runtime_dir(), "site", "pack", "lazy", "opt")
 
 function plugin_loader.init(opts)
   opts = opts or {}
 
-  local install_path = opts.install_path
-    or join_paths(vim.fn.stdpath "data", "site", "pack", "packer", "start", "packer.nvim")
+  local lazy_install_dir = opts.install_path
+    or join_paths(vim.fn.stdpath "data", "site", "pack", "lazy", "opt", "lazy.nvim")
 
-  local max_jobs = 100
-  if vim.fn.has "mac" == 1 then
-    max_jobs = 50
+  if not utils.is_directory(lazy_install_dir) then
+    print "Initializing first time setup"
+    local core_plugins_dir = join_paths(get_lvim_base_dir(), "plugins")
+    if utils.is_directory(core_plugins_dir) then
+      vim.fn.mkdir(plugins_dir, "p")
+      vim.loop.fs_rmdir(plugins_dir)
+      require("lvim.utils").fs_copy(core_plugins_dir, plugins_dir)
+    else
+      vim.fn.system {
+        "git",
+        "clone",
+        "--filter=blob:none",
+        "--branch=stable",
+        "https://github.com/folke/lazy.nvim.git",
+        lazy_install_dir,
+      }
+
+      local default_snapshot_path = join_paths(get_lvim_base_dir(), "snapshots", "default.json")
+      local snapshot = assert(vim.fn.json_decode(vim.fn.readfile(default_snapshot_path)))
+      vim.fn.system {
+        "git",
+        "-C",
+        lazy_install_dir,
+        "checkout",
+        snapshot["lazy.nvim"].commit,
+      }
+    end
   end
 
-  local init_opts = {
-    package_root = opts.package_root or join_paths(vim.fn.stdpath "data", "site", "pack"),
-    compile_path = compile_path,
-    snapshot_path = snapshot_path,
-    max_jobs = max_jobs,
-    log = { level = "warn" },
-    git = {
-      clone_timeout = 120,
-    },
-    display = {
-      open_fn = function()
-        return require("packer.util").float { border = "rounded" }
-      end,
+  vim.opt.runtimepath:append(lazy_install_dir)
+  vim.opt.runtimepath:append(join_paths(plugins_dir, "*"))
+
+  local lazy_cache = require "lazy.core.cache"
+  lazy_cache.setup {
+    performance = {
+      cache = {
+        enabled = true,
+        path = join_paths(get_cache_dir(), "lazy", "cache"),
+      },
     },
   }
+  -- HACK: Don't allow lazy to call setup second time
+  lazy_cache.setup = function() end
+end
 
-  if in_headless then
-    init_opts.display = nil
-    init_opts.git.clone_timeout = 300
-  end
+function plugin_loader.reset_cache()
+  os.remove(require("lazy.core.cache").config.path)
+end
 
-  if not utils.is_directory(install_path) then
-    print "Initializing first time setup"
-    print "Installing packer"
-    print(vim.fn.system { "git", "clone", "--depth", "1", "https://github.com/wbthomason/packer.nvim", install_path })
-    vim.cmd "packadd packer.nvim"
-  end
+function plugin_loader.reload(spec)
+  local Config = require "lazy.core.config"
+  local lazy = require "lazy"
 
-  local status_ok, packer = pcall(require, "packer")
-  if status_ok then
-    packer.on_complete = vim.schedule_wrap(function()
-      require("lvim.utils.hooks").run_on_packer_complete()
-    end)
-    packer.init(init_opts)
-  end
-end
+  -- TODO: reset cache? and unload plugins?
 
--- packer expects a space separated list
-local function pcall_packer_command(cmd, kwargs)
-  local status_ok, msg = pcall(function()
-    require("packer")[cmd](unpack(kwargs or {}))
-  end)
-  if not status_ok then
-    Log:warn(cmd .. " failed with: " .. vim.inspect(msg))
-    Log:trace(vim.inspect(vim.fn.eval "v:errmsg"))
-  end
-end
+  Config.spec = spec
 
-function plugin_loader.cache_clear()
-  if not utils.is_file(compile_path) then
-    return
-  end
-  if vim.fn.delete(compile_path) == 0 then
-    Log:debug "deleted packer_compiled.lua"
-  end
-end
+  require("lazy.core.plugin").load(true)
+  require("lazy.core.plugin").update_state()
 
-function plugin_loader.compile()
-  Log:debug "calling packer.compile()"
-  vim.api.nvim_create_autocmd("User", {
-    pattern = "PackerCompileDone",
-    once = true,
-    callback = function()
-      if utils.is_file(compile_path) then
-        Log:debug "finished compiling packer_compiled.lua"
-      end
-    end,
-  })
-  pcall_packer_command "compile"
-end
+  local not_installed_plugins = vim.tbl_filter(function(plugin)
+    return not plugin._.installed
+  end, Config.plugins)
 
-function plugin_loader.recompile()
-  plugin_loader.cache_clear()
-  plugin_loader.compile()
-end
+  require("lazy.manage").clear()
 
-function plugin_loader.reload(configurations)
-  _G.packer_plugins = _G.packer_plugins or {}
-  for k, v in pairs(_G.packer_plugins) do
-    if k ~= "packer.nvim" then
-      _G.packer_plugins[v] = nil
-    end
+  if #not_installed_plugins > 0 then
+    lazy.install { wait = true }
   end
-  plugin_loader.load(configurations)
 
-  plugin_loader.ensure_plugins()
+  if #Config.to_clean > 0 then
+    -- TODO: set show to true when lazy shows something useful on clean
+    lazy.clean { wait = true, show = false }
+  end
 end
 
 function plugin_loader.load(configurations)
   Log:debug "loading plugins configuration"
-  local packer_available, packer = pcall(require, "packer")
-  if not packer_available then
-    Log:warn "skipping loading plugins until Packer is installed"
+  local lazy_available, lazy = pcall(require, "lazy")
+  if not lazy_available then
+    Log:warn "skipping loading plugins until lazy.nvim is installed"
     return
   end
-  local status_ok, _ = xpcall(function()
-    packer.reset()
-    packer.startup(function(use)
-      for _, plugins in ipairs(configurations) do
-        for _, plugin in ipairs(plugins) do
-          use(plugin)
-        end
-      end
-    end)
+
+  -- remove plugins from rtp before loading lazy, so that all plugins won't be loaded on startup
+  vim.opt.runtimepath:remove(join_paths(plugins_dir, "*"))
+
+  local status_ok = xpcall(function()
+    local opts = {
+      install = {
+        missing = true,
+        colorscheme = { lvim.colorscheme, "lunar", "habamax" },
+      },
+      ui = {
+        border = "rounded",
+      },
+      root = plugins_dir,
+      git = {
+        timeout = 120,
+      },
+      lockfile = join_paths(get_config_dir(), "lazy-lock.json"),
+      performance = {
+        rtp = {
+          reset = false,
+        },
+      },
+      readme = {
+        root = join_paths(get_runtime_dir(), "lazy", "readme"),
+      },
+    }
+
+    lazy.setup(configurations, opts)
   end, debug.traceback)
 
   if not status_ok then
@@ -134,43 +133,26 @@ function plugin_loader.load(configurations)
 end
 
 function plugin_loader.get_core_plugins()
-  local list = {}
+  local names = {}
   local plugins = require "lvim.plugins"
-  for _, item in pairs(plugins) do
-    if not item.disable then
-      table.insert(list, item[1]:match "/(%S*)")
+  local get_name = require("lazy.core.plugin").Spec.get_name
+  for _, spec in pairs(plugins) do
+    if spec.enabled == true or spec.enabled == nil then
+      table.insert(names, get_name(spec[1]))
     end
   end
-  return list
-end
-
-function plugin_loader.load_snapshot(snapshot_file)
-  snapshot_file = snapshot_file or default_snapshot
-  if not in_headless then
-    vim.notify("Syncing core plugins is in progress..", vim.log.levels.INFO, { title = "lvim" })
-  end
-  Log:debug(string.format("Using snapshot file [%s]", snapshot_file))
-  local core_plugins = plugin_loader.get_core_plugins()
-  require("packer").rollback(snapshot_file, unpack(core_plugins))
+  return names
 end
 
 function plugin_loader.sync_core_plugins()
-  plugin_loader.cache_clear()
   local core_plugins = plugin_loader.get_core_plugins()
   Log:trace(string.format("Syncing core plugins: [%q]", table.concat(core_plugins, ", ")))
-  pcall_packer_command("sync", core_plugins)
+  require("lazy").sync { wait = true, plugins = core_plugins }
 end
 
 function plugin_loader.ensure_plugins()
-  vim.api.nvim_create_autocmd("User", {
-    pattern = "PackerComplete",
-    once = true,
-    callback = function()
-      plugin_loader.compile()
-    end,
-  })
-  Log:debug "calling packer.install()"
-  pcall_packer_command "install"
+  Log:debug "calling lazy.install()"
+  require("lazy").install { wait = true }
 end
 
 return plugin_loader

+ 64 - 62
lua/lvim/plugins.lua

@@ -1,13 +1,14 @@
 -- local require = require("lvim.utils.require").require
 local core_plugins = {
-  -- Packer can manage itself as an optional plugin
-  { "wbthomason/packer.nvim" },
-  { "neovim/nvim-lspconfig" },
-  { "tamago324/nlsp-settings.nvim" },
+  { "folke/lazy.nvim", tag = "stable" },
   {
-    "jose-elias-alvarez/null-ls.nvim",
+    "neovim/nvim-lspconfig",
+    lazy = true,
+    dependencies = { "mason-lspconfig.nvim", "nlsp-settings.nvim" },
   },
-  { "williamboman/mason-lspconfig.nvim" },
+  { "williamboman/mason-lspconfig.nvim", lazy = true },
+  { "tamago324/nlsp-settings.nvim", lazy = true },
+  { "jose-elias-alvarez/null-ls.nvim", lazy = true },
   {
     "williamboman/mason.nvim",
     config = function()
@@ -31,14 +32,12 @@ local core_plugins = {
     config = function()
       require("lvim.core.telescope").setup()
     end,
-    disable = not lvim.builtin.telescope.active,
-  },
-  {
-    "nvim-telescope/telescope-fzf-native.nvim",
-    requires = { "nvim-telescope/telescope.nvim" },
-    run = "make",
-    disable = not lvim.builtin.telescope.active,
+    dependencies = { "telescope-fzf-native.nvim" },
+    lazy = true,
+    cmd = "Telescope",
+    enabled = lvim.builtin.telescope.active,
   },
+  { "nvim-telescope/telescope-fzf-native.nvim", build = "make", lazy = true, enabled = lvim.builtin.telescope.active },
   -- Install nvim-cmp, and buffer source as a dependency
   {
     "hrsh7th/nvim-cmp",
@@ -47,21 +46,25 @@ local core_plugins = {
         require("lvim.core.cmp").setup()
       end
     end,
-    requires = {
-      "L3MON4D3/LuaSnip",
+    event = { "InsertEnter", "CmdlineEnter" },
+    dependencies = {
+      "cmp-nvim-lsp",
+      "cmp_luasnip",
+      "cmp-buffer",
+      "cmp-path",
     },
   },
-  {
-    "rafamadriz/friendly-snippets",
-    disable = not lvim.builtin.luasnip.sources.friendly_snippets,
-  },
+  { "hrsh7th/cmp-nvim-lsp", lazy = true },
+  { "saadparwaiz1/cmp_luasnip", lazy = true },
+  { "hrsh7th/cmp-buffer", lazy = true },
+  { "hrsh7th/cmp-path", lazy = true },
   {
     "L3MON4D3/LuaSnip",
     config = function()
       local utils = require "lvim.utils"
       local paths = {}
       if lvim.builtin.luasnip.sources.friendly_snippets then
-        paths[#paths + 1] = utils.join_paths(get_runtime_dir(), "site", "pack", "packer", "start", "friendly-snippets")
+        paths[#paths + 1] = utils.join_paths(get_runtime_dir(), "site", "pack", "lazy", "opt", "friendly-snippets")
       end
       local user_snippets = utils.join_paths(get_config_dir(), "snippets")
       if utils.is_directory(user_snippets) then
@@ -73,32 +76,25 @@ local core_plugins = {
       }
       require("luasnip.loaders.from_snipmate").lazy_load()
     end,
+    event = "InsertEnter",
+    dependencies = {
+      "friendly-snippets",
+    },
   },
-  {
-    "hrsh7th/cmp-nvim-lsp",
-  },
-  {
-    "saadparwaiz1/cmp_luasnip",
-  },
-  {
-    "hrsh7th/cmp-buffer",
-  },
-  {
-    "hrsh7th/cmp-path",
-  },
+  { "rafamadriz/friendly-snippets", lazy = true, cond = lvim.builtin.luasnip.sources.friendly_snippets },
   {
     "folke/neodev.nvim",
-    module = "neodev",
+    lazy = true,
   },
 
   -- Autopairs
   {
     "windwp/nvim-autopairs",
-    -- event = "InsertEnter",
+    event = "InsertEnter",
     config = function()
       require("lvim.core.autopairs").setup()
     end,
-    disable = not lvim.builtin.autopairs.active,
+    enabled = lvim.builtin.autopairs.active,
   },
 
   -- Treesitter
@@ -111,7 +107,7 @@ local core_plugins = {
   },
   {
     "JoosepAlviste/nvim-ts-context-commentstring",
-    event = "BufReadPost",
+    event = "VeryLazy",
   },
 
   -- NvimTree
@@ -122,7 +118,7 @@ local core_plugins = {
     config = function()
       require("lvim.core.nvimtree").setup()
     end,
-    disable = not lvim.builtin.nvimtree.active,
+    enabled = lvim.builtin.nvimtree.active,
   },
   -- Lir
   {
@@ -130,17 +126,15 @@ local core_plugins = {
     config = function()
       require("lvim.core.lir").setup()
     end,
-    requires = { "kyazdani42/nvim-web-devicons" },
-    disable = not lvim.builtin.lir.active,
+    enabled = lvim.builtin.lir.active,
   },
   {
     "lewis6991/gitsigns.nvim",
-
     config = function()
       require("lvim.core.gitsigns").setup()
     end,
     event = "BufRead",
-    disable = not lvim.builtin.gitsigns.active,
+    enabled = lvim.builtin.gitsigns.active,
   },
 
   -- Whichkey
@@ -149,8 +143,8 @@ local core_plugins = {
     config = function()
       require("lvim.core.which-key").setup()
     end,
-    event = "BufWinEnter",
-    disable = not lvim.builtin.which_key.active,
+    event = "VeryLazy",
+    enabled = lvim.builtin.which_key.active,
   },
 
   -- Comments
@@ -160,7 +154,7 @@ local core_plugins = {
     config = function()
       require("lvim.core.comment").setup()
     end,
-    disable = not lvim.builtin.comment.active,
+    enabled = lvim.builtin.comment.active,
   },
 
   -- project.nvim
@@ -169,13 +163,13 @@ local core_plugins = {
     config = function()
       require("lvim.core.project").setup()
     end,
-    disable = not lvim.builtin.project.active,
+    enabled = lvim.builtin.project.active,
   },
 
   -- Icons
   {
     "kyazdani42/nvim-web-devicons",
-    disable = not lvim.use_icons,
+    enabled = lvim.use_icons,
   },
 
   -- Status Line and Bufferline
@@ -186,7 +180,7 @@ local core_plugins = {
     config = function()
       require("lvim.core.lualine").setup()
     end,
-    disable = not lvim.builtin.lualine.active,
+    enabled = lvim.builtin.lualine.active,
   },
 
   -- breadcrumbs
@@ -195,7 +189,7 @@ local core_plugins = {
     config = function()
       require("lvim.core.breadcrumbs").setup()
     end,
-    disable = not lvim.builtin.breadcrumbs.active,
+    enabled = lvim.builtin.breadcrumbs.active,
   },
 
   {
@@ -204,8 +198,7 @@ local core_plugins = {
       require("lvim.core.bufferline").setup()
     end,
     branch = "main",
-    event = "BufWinEnter",
-    disable = not lvim.builtin.bufferline.active,
+    enabled = lvim.builtin.bufferline.active,
   },
 
   -- Debugging
@@ -215,7 +208,7 @@ local core_plugins = {
     config = function()
       require("lvim.core.dap").setup()
     end,
-    disable = not lvim.builtin.dap.active,
+    enabled = lvim.builtin.dap.active,
   },
 
   -- Debugger user interface
@@ -224,7 +217,7 @@ local core_plugins = {
     config = function()
       require("lvim.core.dap").setup_ui()
     end,
-    disable = not lvim.builtin.dap.active,
+    enabled = lvim.builtin.dap.active,
   },
 
   -- alpha
@@ -233,23 +226,24 @@ local core_plugins = {
     config = function()
       require("lvim.core.alpha").setup()
     end,
-    disable = not lvim.builtin.alpha.active,
+    enabled = lvim.builtin.alpha.active,
   },
 
   -- Terminal
   {
     "akinsho/toggleterm.nvim",
-    event = "BufWinEnter",
+    event = "VeryLazy",
     branch = "main",
     config = function()
       require("lvim.core.terminal").setup()
     end,
-    disable = not lvim.builtin.terminal.active,
+    enabled = lvim.builtin.terminal.active,
   },
 
   -- SchemaStore
   {
     "b0o/schemastore.nvim",
+    lazy = true,
   },
 
   {
@@ -257,7 +251,8 @@ local core_plugins = {
     config = function()
       require("lvim.core.illuminate").setup()
     end,
-    disable = not lvim.builtin.illuminate.active,
+    event = "VeryLazy",
+    enabled = lvim.builtin.illuminate.active,
   },
 
   {
@@ -265,7 +260,7 @@ local core_plugins = {
     config = function()
       require("lvim.core.indentlines").setup()
     end,
-    disable = not lvim.builtin.indentlines.active,
+    enabled = lvim.builtin.indentlines.active,
   },
 
   {
@@ -279,7 +274,7 @@ local core_plugins = {
         end
       end)
     end,
-    disable = lvim.colorscheme ~= "onedarker",
+    enabled = lvim.colorscheme == "onedarker",
   },
 
   {
@@ -289,22 +284,29 @@ local core_plugins = {
         require("bigfile").config(lvim.builtin.bigfile.config)
       end)
     end,
-    disable = not lvim.builtin.bigfile.active,
+    enabled = lvim.builtin.bigfile.active,
   },
 }
 
 local default_snapshot_path = join_paths(get_lvim_base_dir(), "snapshots", "default.json")
 local content = vim.fn.readfile(default_snapshot_path)
-local default_sha1 = vim.fn.json_decode(content)
+local default_sha1 = assert(vim.fn.json_decode(content))
+
+-- taken form <https://github.com/folke/lazy.nvim/blob/c7122d64cdf16766433588486adcee67571de6d0/lua/lazy/core/plugin.lua#L27>
+local get_short_name = function(long_name)
+  local name = long_name:sub(-4) == ".git" and long_name:sub(1, -5) or long_name
+  local slash = name:reverse():find("/", 1, true) --[[@as number?]]
+  return slash and name:sub(#name - slash + 2) or long_name:gsub("%W+", "_")
+end
 
 local get_default_sha1 = function(spec)
-  local short_name, _ = require("packer.util").get_plugin_short_name(spec)
+  local short_name = get_short_name(spec[1])
   return default_sha1[short_name] and default_sha1[short_name].commit
 end
 
 if not vim.env.LVIM_DEV_MODE then
+  --  Manually lock the commit hashes of core plugins
   for _, spec in ipairs(core_plugins) do
-    -- Manually lock the commit hash since Packer's snapshots are unreliable in headless mode
     spec["commit"] = get_default_sha1(spec)
   end
 end

+ 25 - 0
lua/lvim/utils.lua

@@ -104,4 +104,29 @@ function M.write_file(path, txt, flag)
   end)
 end
 
+---Copies a file or directory recursively
+---@param source string
+---@param destination string
+function M.fs_copy(source, destination)
+  local source_stats = assert(vim.loop.fs_stat(source))
+
+  if source_stats.type == "file" then
+    assert(vim.loop.fs_copyfile(source, destination))
+    return
+  elseif source_stats.type == "directory" then
+    local handle = assert(vim.loop.fs_scandir(source))
+
+    assert(vim.loop.fs_mkdir(destination, source_stats.mode))
+
+    while true do
+      local name = vim.loop.fs_scandir_next(handle)
+      if not name then
+        break
+      end
+
+      M.fs_copy(M.join_paths(source, name), M.join_paths(destination, name))
+    end
+  end
+end
+
 return M

+ 9 - 20
lua/lvim/utils/hooks.lua

@@ -12,31 +12,22 @@ function M.run_pre_reload()
   Log:debug "Starting pre-reload hook"
 end
 
-function M.run_on_packer_complete()
-  Log:debug "Packer operation complete"
-  vim.api.nvim_exec_autocmds("User", { pattern = "PackerComplete" })
-
-  -- -- FIXME(kylo252): nvim-tree.lua/lua/nvim-tree/view.lua:442: Invalid window id
-  -- vim.g.colors_name = lvim.colorscheme
-  -- pcall(vim.cmd.colorscheme, lvim.colorscheme)
-
-  if M._reload_triggered then
-    Log:debug "Reloaded configuration"
-    M._reload_triggered = nil
-  end
-end
+-- TODO: convert to lazy.nvim
+-- function M.run_on_packer_complete()
+-- -- FIXME(kylo252): nvim-tree.lua/lua/nvim-tree/view.lua:442: Invalid window id
+-- vim.g.colors_name = lvim.colorscheme
+-- pcall(vim.cmd.colorscheme, lvim.colorscheme)
+-- end
 
 function M.run_post_reload()
   Log:debug "Starting post-reload hook"
-  M._reload_triggered = true
 end
 
----Reset any startup cache files used by Packer and Impatient
+---Reset any startup cache files used by lazy.nvim
 ---It also forces regenerating any template ftplugin files
 ---Tip: Useful for clearing any outdated settings
 function M.reset_cache()
-  vim.cmd [[LuaCacheClear]]
-  plugin_loader.recompile()
+  plugin_loader.reset_cache()
   local lvim_modules = {}
   for module, _ in pairs(package.loaded) do
     if module:match "lvim.core" or module:match "lvim.lsp" then
@@ -57,9 +48,7 @@ function M.run_post_update()
       "Please upgrade your Neovim base installation. Newer version of Lunarvim requires v0.7+",
       vim.log.levels.WARN
     )
-    vim.wait(1000, function()
-      return false
-    end)
+    vim.wait(1000)
     local ret = reload("lvim.utils.git").switch_lvim_branch(compat_tag)
     if ret then
       vim.notify("Reverted to the last known compatible version: " .. compat_tag, vim.log.levels.WARN)

+ 3 - 3
snapshots/default.json

@@ -35,6 +35,9 @@
   "indent-blankline.nvim": {
     "commit": "c4c203c"
   },
+  "lazy.nvim": {
+    "commit": "d211027"
+  },
   "lir.nvim": {
     "commit": "ae190c3"
   },
@@ -92,9 +95,6 @@
   "onedarker.nvim": {
     "commit": "b00dd21"
   },
-  "packer.nvim": {
-    "commit": "dac4088"
-  },
   "plenary.nvim": {
     "commit": "bb44479"
   },

+ 21 - 18
tests/minimal_lsp.lua

@@ -13,25 +13,21 @@ local temp_dir = vim.loop.os_getenv "TEMP" or "/tmp"
 vim.cmd("set packpath=" .. join_paths(temp_dir, "nvim", "site"))
 
 local package_root = join_paths(temp_dir, "nvim", "site", "pack")
-local install_path = join_paths(package_root, "packer", "start", "packer.nvim")
-local compile_path = join_paths(install_path, "plugin", "packer_compiled.lua")
+local plugins_dir = join_paths(package_root, "lazy", "opt")
+local install_path = join_paths(package_root, "lazy.nvim")
 
 -- Choose whether to use the executable that's managed by mason
 local use_lsp_installer = true
 
 local function load_plugins()
-  require("packer").startup {
-    {
-      "wbthomason/packer.nvim",
-      "neovim/nvim-lspconfig",
-      "williamboman/mason-lspconfig.nvim",
-      "williamboman/mason.nvim",
-    },
-    config = {
-      package_root = package_root,
-      compile_path = compile_path,
-    },
-  }
+  vim.opt.rtp:prepend(install_path)
+  require("lazy").setup({
+    "neovim/nvim-lspconfig",
+    "williamboman/mason-lspconfig.nvim",
+    "williamboman/mason.nvim",
+  }, {
+    root = plugins_dir,
+  })
 end
 
 function _G.dump(...)
@@ -98,12 +94,19 @@ _G.load_config = function()
 end
 
 if vim.fn.isdirectory(install_path) == 0 then
-  vim.fn.system { "git", "clone", "https://github.com/wbthomason/packer.nvim", install_path }
+  print "Installing lazy.nvim"
+  vim.fn.system {
+    "git",
+    "clone",
+    "--filter=blob:none",
+    "--single-branch",
+    "https://github.com/folke/lazy.nvim.git",
+    install_path,
+  }
   load_plugins()
-  require("packer").sync()
-  vim.cmd [[autocmd User PackerComplete ++once lua load_config()]]
+  vim.cmd [[autocmd User LazyDone ++once lua load_config()]]
 else
   load_plugins()
-  require("packer").sync()
+  require("lazy").sync()
   _G.load_config()
 end

+ 2 - 37
tests/specs/plugins_load_spec.lua

@@ -6,16 +6,16 @@ a.describe("plugin-loader", function()
 
   pcall(function()
     lvim.log.level = "debug"
-    package.loaded["packer.log"] = nil
     package.loaded["lvim.core.log"] = nil
   end)
 
   a.it("should be able to load default packages without errors", function()
+    vim.go.loadplugins = true
     loader.load { plugins, lvim.plugins }
 
     -- TODO: maybe there's a way to avoid hard-coding the names of the modules?
     local startup_plugins = {
-      "packer",
+      "lazy",
     }
 
     for _, plugin in ipairs(startup_plugins) do
@@ -24,8 +24,6 @@ a.describe("plugin-loader", function()
   end)
 
   a.it("should be able to load lsp packages without errors", function()
-    loader.load { plugins, lvim.plugins }
-
     require("lvim.lsp").setup()
 
     local lsp_packages = {
@@ -38,37 +36,4 @@ a.describe("plugin-loader", function()
       assert.truthy(package.loaded[plugin])
     end
   end)
-
-  pending("should be able to rollback plugins without errors", function()
-    local plugin = { name = "onedarker.nvim" }
-    plugin.path = vim.tbl_filter(function(package)
-      return package:match(plugin.name)
-    end, vim.api.nvim_list_runtime_paths())[1]
-
-    local get_current_sha = function(repo)
-      local res = vim.fn.system(string.format("git -C %s log -1 --pretty=%%h", repo)):gsub("\n", "")
-      return res
-    end
-    plugin.test_sha = "316b1c9"
-    _G.locked_sha = get_current_sha(plugin.path)
-    loader.load { plugins, lvim.plugins }
-
-    os.execute(string.format("git -C %s fetch --deepen 999 --quiet", plugin.path))
-    os.execute(string.format("git -C %s checkout %s --quiet", plugin.path, plugin.test_sha))
-    assert.equal(plugin.test_sha, get_current_sha(plugin.path))
-    _G.completed = false
-    _G.verify_sha = function()
-      if _G.locked_sha ~= get_current_sha(plugin.path) then
-        error "unmatched results!"
-      else
-        _G.completed = true
-      end
-    end
-    vim.cmd [[autocmd User PackerComplete ++once lua _G.verify_sha()]]
-    loader.load_snapshot()
-    local ret = vim.wait(30 * 10 * 1000, function()
-      return _G.completed == true
-    end, 200)
-    assert.True(ret)
-  end)
 end)

+ 10 - 1
utils/ci/generate_new_lockfile.lua

@@ -71,6 +71,7 @@ local function write_lockfile(verbose)
       url = url,
       commit = commit,
       branch = plugin.branch or "HEAD",
+      tag = plugin.tag,
     })
   end
 
@@ -86,12 +87,20 @@ local function write_lockfile(verbose)
         return
       end
       local latest_sha = result:gsub("\tHEAD\n", ""):sub(1, 7)
+      if entry.tag then
+        local dereferenced_commit = result:match("\n(.*)\trefs/tags/" .. entry.tag .. "%^{}\n")
+        if dereferenced_commit then
+          latest_sha = dereferenced_commit:sub(1, 7)
+        end
+      end
       plugins_list[entry.name] = {
         commit = latest_sha,
       }
     end
 
-    local handle = call_proc("git", { args = { "ls-remote", entry.url, entry.branch } }, on_done)
+    local handle = call_proc("git", {
+      args = { "ls-remote", entry.url, entry.tag and entry.tag .. "*" or entry.branch },
+    }, on_done)
     assert(handle)
     table.insert(active_jobs, handle)
   end

+ 13 - 15
utils/ci/verify_plugins.lua

@@ -24,28 +24,23 @@ local get_default_sha1 = function(spec)
 end
 
 local is_directory = require("lvim.utils").is_directory
--- see packer.init()
-local packdir = join_paths(get_runtime_dir(), "site", "pack", "packer")
+local lazydir = join_paths(get_runtime_dir(), "site", "pack", "lazy")
 
-local verify_packer = function()
-  if not is_directory(packdir) then
-    io.write "Packer not installed!"
+local verify_lazy = function()
+  if not is_directory(lazydir) then
+    io.write "Lazy.nvim not installed!"
     os.exit(1)
   end
-  local status_ok, packer = pcall(require, "packer")
-  if status_ok and packer then
+  local status_ok, lazy = pcall(require, "lazy")
+  if status_ok and lazy then
     return
   end
-  io.write "Packer not installed!"
+  io.write "Lazy.nvim not installed!"
   os.exit(1)
 end
 
-local packer_config = { opt_dir = join_paths(packdir, "opt"), start_dir = join_paths(packdir, "start") }
-local is_optional = function(spec)
-  return spec.opt or spec.event or spec.cmd or spec.module
-end
 local get_install_path = function(spec)
-  local prefix = is_optional(spec) and packer_config.opt_dir or packer_config.start_dir
+  local prefix = join_paths(lazydir, "opt")
   local path = join_paths(prefix, get_short_name(spec))
   return is_directory(path) and path
 end
@@ -79,7 +74,9 @@ local function call_proc(process, opts, cb)
     { args = opts.args, cwd = opts.cwd or uv.cwd(), stdio = stdio },
     vim.schedule_wrap(function(code)
       if code ~= 0 then
+        ---@diagnostic disable-next-line: undefined-field
         stdout:read_stop()
+        ---@diagnostic disable-next-line: undefined-field
         stderr:read_stop()
       end
 
@@ -106,7 +103,7 @@ end
 local function verify_core_plugins(verbose)
   for _, spec in pairs(core_plugins) do
     local path = get_install_path(spec)
-    if not spec.disable and path then
+    if spec.enabled or spec.enabled == nil and path then
       table.insert(collection, {
         name = get_short_name(spec),
         commit = get_default_sha1(spec),
@@ -140,10 +137,11 @@ local function verify_core_plugins(verbose)
   end
 
   vim.wait(#active_jobs * 60 * 1000, function()
+    ---@diagnostic disable-next-line: redundant-return-value
     return completed == #active_jobs
   end)
 end
 
-verify_packer()
+verify_lazy()
 verify_core_plugins()
 vim.cmd "q"

+ 0 - 1
utils/installer/config.example.lua

@@ -33,7 +33,6 @@ lvim.keys.normal_mode["<C-s>"] = ":w<cr>"
 -- -- Change theme settings
 -- lvim.colorscheme = "lunar"
 
--- After changing plugin config exit and reopen LunarVim, Run :PackerSync
 lvim.builtin.alpha.active = true
 lvim.builtin.alpha.mode = "dashboard"
 lvim.builtin.terminal.active = true

+ 0 - 1
utils/installer/config_win.example.lua

@@ -55,7 +55,6 @@ lvim.keys.normal_mode["<C-s>"] = ":w<cr>"
 -- -- Change theme settings
 -- lvim.colorscheme = "lunar"
 
--- After changing plugin config exit and reopen LunarVim, Run :PackerSync
 lvim.builtin.alpha.active = true
 lvim.builtin.alpha.mode = "dashboard"
 lvim.builtin.terminal.active = true

+ 1 - 1
utils/installer/install.ps1

@@ -217,7 +217,7 @@ function setup_lvim() {
     $exampleConfig = "$env:LUNARVIM_BASE_DIR\utils\installer\config_win.example.lua"
     Copy-Item -Force "$exampleConfig" "$env:LUNARVIM_CONFIG_DIR\config.lua"
 
-    Write-Host "Make sure to run `:PackerSync` at first launch" -ForegroundColor Green
+    Write-Host "Make sure to run `:Lazy sync` at first launch" -ForegroundColor Green
 
     create_alias
 

+ 8 - 15
utils/installer/install.sh

@@ -222,7 +222,7 @@ function check_neovim_min_version() {
 function verify_core_plugins() {
   msg "Verifying core plugins"
   if ! bash "$LUNARVIM_BASE_DIR/utils/ci/verify_plugins.sh"; then
-    echo "[ERROR]: Unable to verify plugins, make sure to manually run ':PackerSync' when starting lvim for the first time."
+    echo "[ERROR]: Unable to verify plugins, make sure to manually run ':Lazy sync' when starting lvim for the first time."
     exit 1
   fi
   echo "Verification complete!"
@@ -412,15 +412,10 @@ function setup_shim() {
 }
 
 function remove_old_cache_files() {
-  local packer_cache="$LUNARVIM_CONFIG_DIR/plugin/packer_compiled.lua"
-  if [ -e "$packer_cache" ]; then
-    msg "Removing old packer cache file"
-    rm -f "$packer_cache"
-  fi
-
-  if [ -e "$LUNARVIM_CACHE_DIR/luacache" ] || [ -e "$LUNARVIM_CACHE_DIR/lvim_cache" ]; then
-    msg "Removing old startup cache file"
-    rm -f "$LUNARVIM_CACHE_DIR/{luacache,lvim_cache}"
+  local lazy_cache="$LUNARVIM_CACHE_DIR/lazy/cache"
+  if [ -e "$lazy_cache" ]; then
+    msg "Removing old lazy cache file"
+    rm -f "$lazy_cache"
   fi
 }
 
@@ -435,13 +430,11 @@ function setup_lvim() {
   [ ! -f "$LUNARVIM_CONFIG_DIR/config.lua" ] \
     && cp "$LUNARVIM_BASE_DIR/utils/installer/config.example.lua" "$LUNARVIM_CONFIG_DIR/config.lua"
 
-  echo "Preparing Packer setup"
+  echo "Preparing Lazy setup"
 
-  "$INSTALL_PREFIX/bin/lvim" --headless \
-    -c 'autocmd User PackerComplete quitall' \
-    -c 'PackerSync'
+  "$INSTALL_PREFIX/bin/lvim" --headless -c 'quitall'
 
-  echo "Packer setup complete"
+  echo "Lazy setup complete"
 
   verify_core_plugins
 }