ソースを参照

Merge remote-tracking branch 'origin/rolling'

kylo252 3 年 前
コミット
2d0ba75d03

+ 5 - 0
.github/workflows/format.yaml

@@ -4,6 +4,11 @@ on:
   pull_request:
     branches:
       - "rolling"
+    paths:
+      - 'lua/**'
+      - 'snapshots/**'
+      - 'tests/**'
+      - 'utils/**'
 
 jobs:
   stylua-check:

+ 5 - 0
.github/workflows/install.yaml

@@ -5,6 +5,11 @@ on:
     branches:
       - "master"
       - "rolling"
+    paths:
+      - 'lua/**'
+      - 'snapshots/**'
+      - 'tests/**'
+      - 'utils/**'
 
 jobs:
   unixish:

+ 5 - 0
.github/workflows/lint.yaml

@@ -4,6 +4,11 @@ on:
   pull_request:
     branches:
       - "rolling"
+    paths:
+      - 'lua/**'
+      - 'snapshots/**'
+      - 'tests/**'
+      - 'utils/**'
 
 jobs:
   lua-linter:

+ 65 - 0
.github/workflows/plugins.yml

@@ -0,0 +1,65 @@
+name: plugins-version-bump
+
+on:
+  workflow_dispatch:
+    inputs:
+      logLevel:
+        description: 'Log level'
+        required: false
+        default: 'warning'
+        type: choice
+        options:
+        - info
+        - warning
+        - debug
+  schedule:
+    # note: this will keep updating the existing branch
+    - cron: "*/15 10-14 * * 0,6"
+    - cron: "0 14 * * 1-5"
+
+jobs:
+  plugins-version-bump:
+    runs-on: ubuntu-latest
+    continue-on-error: true
+    permissions:
+      contents: write
+      pull-requests: write
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          ref: ${{ github.head_ref }}
+
+      - name: Install neovim binary
+        uses: rhysd/action-setup-vim@v1
+        with:
+          neovim: true
+          version: v0.6.1
+
+      - name: Install LunarVim
+        timeout-minutes: 4
+        run: |
+          ./utils/installer/install.sh --local --no-install-dependencies
+
+      - name: run upgrade script
+        run: make generate_new_lockfile
+
+      - name: Re-install LunarVim
+        timeout-minutes: 4
+        run: |
+          ./utils/installer/uninstall.sh --remove-backups
+          ./utils/installer/install.sh --local --no-install-dependencies
+
+      - name: Run unit-tests
+        # NOTE: make sure to adjust the timeout if you start adding a lot of tests
+        timeout-minutes: 4
+        run: make test
+
+      - name: Create Pull Request
+        uses: peter-evans/create-pull-request@v4
+        with:
+          branch: plugins-bump
+          delete-branch: true # Delete the branch when closing pull requests, and when undeleted after merging.
+          token: ${{ secrets.GITHUB_TOKEN }}
+          author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
+          title: "chore: bump plugins version"
+          commit-message: "chore: bump plugins version"

+ 3 - 3
Makefile

@@ -16,9 +16,9 @@ uninstall:
 	@echo starting LunarVim uninstaller
 	bash ./utils/installer/uninstall.sh
 
-generate_plugins_sha:
-	@echo generating core-plugins latest SHA list
-	lvim --headless -c 'lua require("lvim.utils.git").generate_plugins_sha("latest-sha.lua")' -c 'qall'
+generate_new_lockfile:
+	@echo generating core-plugins latest lockfile
+	bash ./utils/ci/generate_new_lockfile.sh
 
 lint: lint-lua lint-sh
 

+ 4 - 2
README.md

@@ -41,7 +41,9 @@ To run the install script without any interaction you can pass the `-y` flag to
 
 The same way, you can use `--no-install-dependencies` to skip the dependency installation.
 
-### Windows (Powershell):
+### Windows (Powershell 7+):
+
+Powershell v7+ is required for this script. For instructions on how to install, [click here.](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.2)
 
 ```powershell
 Invoke-WebRequest https://raw.githubusercontent.com/LunarVim/LunarVim/master/utils/installer/install.ps1 -UseBasicParsing | Invoke-Expression
@@ -76,7 +78,7 @@ lvim.keys.normal_mode["<C-s>"] = ":w<cr>"
 -- lvim.builtin.which_key.mappings["P"] = { "<cmd>Telescope projects<CR>", "Projects" }
 
 -- Configure builtin plugins
-lvim.builtin.dashboard.active = true
+lvim.builtin.alpha.active = true
 lvim.builtin.notify.active = true
 lvim.builtin.terminal.active = true
 

+ 10 - 8
lua/lvim/config/init.lua

@@ -124,18 +124,20 @@ end
 --- Override the configuration with a user provided one
 -- @param config_path The path to the configuration overrides
 function M:reload()
-  require_clean("lvim.utils.hooks").run_pre_reload()
+  vim.schedule(function()
+    require_clean("lvim.utils.hooks").run_pre_reload()
 
-  M:init()
-  M:load()
+    M:init()
+    M:load()
 
-  require("lvim.core.autocmds").configure_format_on_save()
+    require("lvim.core.autocmds").configure_format_on_save()
 
-  local plugins = require "lvim.plugins"
-  local plugin_loader = require "lvim.plugin-loader"
+    local plugins = require "lvim.plugins"
+    local plugin_loader = require "lvim.plugin-loader"
 
-  plugin_loader.load { plugins, lvim.plugins }
-  require_clean("lvim.utils.hooks").run_post_reload()
+    plugin_loader.reload { plugins, lvim.plugins }
+    require_clean("lvim.utils.hooks").run_post_reload()
+  end)
 end
 
 return M

+ 17 - 19
lua/lvim/core/info.lua

@@ -120,7 +120,7 @@ local function make_override_info(ft)
 end
 
 function M.toggle_popup(ft)
-  local clients = vim.lsp.get_active_clients(ft)
+  local clients = vim.lsp.get_active_clients()
   local client_names = {}
   local bufnr = vim.api.nvim_get_current_buf()
   local ts_active_buffers = vim.tbl_keys(vim.treesitter.highlighter.active)
@@ -161,24 +161,22 @@ function M.toggle_popup(ft)
   local content_provider = function(popup)
     local content = {}
 
-    for _, section in
-      ipairs {
-        M.banner,
-        { "" },
-        { "" },
-        header,
-        { "" },
-        lsp_info,
-        { "" },
-        override_info,
-        { "" },
-        formatters_info,
-        { "" },
-        linters_info,
-        { "" },
-        code_actions_info,
-      }
-    do
+    for _, section in ipairs {
+      M.banner,
+      { "" },
+      { "" },
+      header,
+      { "" },
+      lsp_info,
+      { "" },
+      override_info,
+      { "" },
+      formatters_info,
+      { "" },
+      linters_info,
+      { "" },
+      code_actions_info,
+    } do
       vim.list_extend(content, section)
     end
 

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

@@ -19,6 +19,9 @@ function Log:init()
     return nil
   end
 
+  package.loaded["packer.log"] = nil
+  require("packer.log").new { level = lvim.log.level }
+
   local log_level = Log.levels[(lvim.log.level):upper() or "WARN"]
   local lvim_log = {
     lvim = {

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

@@ -55,7 +55,7 @@ function M.config()
           ["<C-j>"] = actions.cycle_history_next,
           ["<C-k>"] = actions.cycle_history_prev,
           ["<C-q>"] = actions.smart_send_to_qflist + actions.open_qflist,
-          ["<CR>"] = actions.select_default + actions.center,
+          ["<CR>"] = actions.select_default,
         },
         n = {
           ["<C-n>"] = actions.move_selection_next,

+ 1 - 1
lua/lvim/lsp/config.lua

@@ -78,7 +78,7 @@ return {
     "graphql",
     "jedi_language_server",
     "ltex",
-    "ocamllsp",
+    "ocamlls",
     "phpactor",
     "psalm",
     "pylsp",

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

@@ -2,7 +2,7 @@ local opts = {
   settings = {
     Lua = {
       diagnostics = {
-        globals = { "vim", "lvim" },
+        globals = { "vim", "lvim", "packer_plugins" },
       },
       workspace = {
         library = {

+ 59 - 34
lua/lvim/plugin-loader.lua

@@ -1,41 +1,29 @@
 local plugin_loader = {}
 
-local in_headless = #vim.api.nvim_list_uis() == 0
-
 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 = get_config_dir() .. "/plugin/packer_compiled.lua"
+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")
 
 function plugin_loader.init(opts)
   opts = opts or {}
 
-  local install_path = opts.install_path or vim.fn.stdpath "data" .. "/site/pack/packer/start/packer.nvim"
-  local package_root = opts.package_root or vim.fn.stdpath "data" .. "/site/pack"
-
-  if vim.fn.empty(vim.fn.glob(install_path)) > 0 then
-    vim.fn.system { "git", "clone", "--depth", "1", "https://github.com/wbthomason/packer.nvim", install_path }
-    vim.cmd "packadd packer.nvim"
-  end
-
-  local log_level = in_headless and "debug" or "warn"
-  if lvim.log and lvim.log.level then
-    log_level = lvim.log.level
-  end
+  local install_path = opts.install_path
+    or join_paths(vim.fn.stdpath "data", "site", "pack", "packer", "start", "packer.nvim")
 
-  local _, packer = pcall(require, "packer")
-  packer.init {
-    package_root = package_root,
+  local init_opts = {
+    package_root = opts.package_root or join_paths(vim.fn.stdpath "data", "site", "pack"),
     compile_path = compile_path,
-    log = { level = log_level },
+    snapshot_path = snapshot_path,
+    log = { level = "warn" },
     git = {
       clone_timeout = 300,
-      subcommands = {
-        -- this is more efficient than what Packer is using
-        fetch = "fetch --no-tags --no-recurse-submodules --update-shallow --progress",
-      },
     },
-    max_jobs = 50,
     display = {
       open_fn = function()
         return require("packer.util").float { border = "rounded" }
@@ -43,8 +31,24 @@ function plugin_loader.init(opts)
     },
   }
 
-  if not in_headless then
-    vim.cmd [[autocmd User PackerComplete lua require('lvim.utils.hooks').run_on_packer_complete()]]
+  if in_headless then
+    init_opts.display = nil
+  end
+
+  if vim.fn.empty(vim.fn.glob(install_path)) > 0 then
+    vim.fn.system { "git", "clone", "--depth", "1", "https://github.com/wbthomason/packer.nvim", install_path }
+    vim.cmd "packadd packer.nvim"
+    -- IMPORTANT: we only set this the very first time to avoid constantly triggering the rollback function
+    -- https://github.com/wbthomason/packer.nvim/blob/c576ab3f1488ee86d60fd340d01ade08dcabd256/lua/packer.lua#L998-L995
+    init_opts.snapshot = default_snapshot
+  end
+
+  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
 
@@ -73,6 +77,18 @@ function plugin_loader.recompile()
   end
 end
 
+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
+  end
+  plugin_loader.load(configurations)
+
+  pcall_packer_command "sync"
+end
+
 function plugin_loader.load(configurations)
   Log:debug "loading plugins configuration"
   local packer_available, packer = pcall(require, "packer")
@@ -81,6 +97,7 @@ function plugin_loader.load(configurations)
     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
@@ -102,21 +119,29 @@ function plugin_loader.get_core_plugins()
   local list = {}
   local plugins = require "lvim.plugins"
   for _, item in pairs(plugins) do
-    table.insert(list, item[1]:match "/(%S*)")
+    if not item.disable then
+      table.insert(list, item[1]:match "/(%S*)")
+    end
   end
   return list
 end
 
-function plugin_loader.sync_core_plugins()
+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()
-  Log:trace(string.format("Syncing core plugins: [%q]", table.concat(core_plugins, ", ")))
-  pcall_packer_command("sync", core_plugins)
+  require("packer").rollback(snapshot_file, unpack(core_plugins))
 end
 
-function plugin_loader.ensure_installed()
-  local all_plugins = _G.packer_plugins or plugin_loader.get_core_plugins()
-  Log:trace(string.format("Syncing core plugins: [%q]", table.concat(all_plugins, ", ")))
-  pcall_packer_command("install", all_plugins)
+function plugin_loader.sync_core_plugins()
+  -- problem: rollback() will get stuck if a plugin directory doesn't exist
+  -- solution: call sync() beforehand
+  -- see https://github.com/wbthomason/packer.nvim/issues/862
+  vim.cmd [[autocmd User PackerComplete ++once lua require("lvim.plugin-loader").load_snapshot() ]]
+  pcall_packer_command "sync"
 end
 
 return plugin_loader

+ 25 - 80
lua/lvim/plugins.lua

@@ -1,83 +1,44 @@
-local commit = {
-  alpha_nvim = "14be0ac200f44009672046123c6fcb30724018a5",
-  bufferline = "5e101b1b4e1ea5b868b8865a5f749b0b5b8f3ccd",
-  cmp_buffer = "d66c4c2d376e5be99db68d2362cd94d250987525",
-  cmp_luasnip = "d6f837f4e8fe48eeae288e638691b91b97d1737f",
-  cmp_nvim_lsp = "ebdfc204afb87f15ce3d3d3f5df0b8181443b5ba",
-  cmp_path = "466b6b8270f7ba89abd59f402c73f63c7331ff6e",
-  comment = "a841f73523440c4f32d39f0290cf1e691311db2a",
-  dapinstall = "24923c3819a450a772bb8f675926d530e829665f",
-  fixcursorhold = "1bfb32e7ba1344925ad815cb0d7f901dbc0ff7c1",
-  friendly_snippets = "ad07b2844021b20797adda5b483265802559a693",
-  gitsigns = "2df360de757c39c04076cb04bcbbd361dec3c8c2",
-  lua_dev = "a0ee77789d9948adce64d98700cc90cecaef88d5",
-  lualine = "181b14348f513e6f9eb3bdd2252e13630094fdd3",
-  luasnip = "ee350179f842699a42b3d6277b2ded8ce73bdc33",
-  nlsp_settings = "ea9b88e289359843c3cc5bfbf42e5ed9cc3df5f2",
-  null_ls = "041601cb03daa8982c5af6edc6641f4b97e9d6b5",
-  nvim_autopairs = "6617498bea01c9c628406d7e23030da57f2f8718",
-  nvim_cmp = "71d7f46b930bf08e982925c77bd9b0a9808c1162",
-  nvim_dap = "3d0575a777610b364fea745b85ad497d56b8009a",
-  nvim_lsp_installer = "dc783087bef65cc7c2943d8641ff1b6dfff6e5a9",
-  nvim_lspconfig = "710deb04d9f8b73517e1d995a57a1505cbbaac51",
-  nvim_notify = "f81b48d298c0ff7479b66568d9cc1a4794c196d0",
-  nvim_tree = "20797a8d74e68bce50b98455c76c5de250c6f0e5",
-  nvim_treesitter = "fd92e70c69330dd8f2f6753d3d987c34e7dacd24",
-  nvim_ts_context_commentstring = "097df33c9ef5bbd3828105e4bee99965b758dc3f",
-  nvim_web_devicons = "4415d1aaa56f73b9c05795af84d625c610b05d3b",
-  onedarker = "b00dd2189f264c5aeb4cf04c59439655ecd573ec",
-  packer = "c576ab3f1488ee86d60fd340d01ade08dcabd256",
-  plenary = "14dfb4071022b22e08384ee125a5607464b6d397",
-  popup = "b7404d35d5d3548a82149238289fa71f7f6de4ac",
-  project = "cef52b8da07648b750d7f1e8fb93f12cb9482988",
-  schemastore = "265eabf9f8ab33cc6bf1683c286b04e280a2b2e7",
-  structlog = "6f1403a192791ff1fa7ac845a73de9e860f781f1",
-  telescope = "a36a813d5d031e6f5d52b74986915e68130febd9",
-  telescope_fzf_native = "8ec164b541327202e5e74f99bcc5fe5845720e18",
-  toggleterm = "e97d0c1046512e975a9f3fa95afe98f312752b1c",
-  which_key = "a3c19ec5754debb7bf38a8404e36a9287b282430",
-}
-
-return {
+local core_plugins = {
   -- Packer can manage itself as an optional plugin
-  { "wbthomason/packer.nvim", commit = commit.packer },
-  { "neovim/nvim-lspconfig", commit = commit.nvim_lspconfig },
-  { "tamago324/nlsp-settings.nvim", commit = commit.nlsp_settings },
+  { "wbthomason/packer.nvim" },
+  { "neovim/nvim-lspconfig" },
+  { "tamago324/nlsp-settings.nvim" },
   {
     "jose-elias-alvarez/null-ls.nvim",
-    commit = commit.null_ls,
   },
-  { "antoinemadec/FixCursorHold.nvim", commit = commit.fixcursorhold }, -- Needed while issue https://github.com/neovim/neovim/issues/12587 is still open
+  { "antoinemadec/FixCursorHold.nvim" }, -- Needed while issue https://github.com/neovim/neovim/issues/12587 is still open
   {
     "williamboman/nvim-lsp-installer",
-    commit = commit.nvim_lsp_installer,
   },
   {
     "lunarvim/onedarker.nvim",
     config = function()
-      require("onedarker").setup()
-      lvim.builtin.lualine.options.theme = "onedarker"
+      pcall(function()
+        if lvim and lvim.colorscheme == "onedarker" then
+          require("onedarker").setup()
+          lvim.builtin.lualine.options.theme = "onedarker"
+        end
+      end)
     end,
-    commit = commit.onedarker,
     disable = lvim.colorscheme ~= "onedarker",
   },
   {
     "rcarriga/nvim-notify",
-    commit = commit.nvim_notify,
+
     config = function()
       require("lvim.core.notify").setup()
     end,
     requires = { "nvim-telescope/telescope.nvim" },
     disable = not lvim.builtin.notify.active or not lvim.builtin.telescope.active,
   },
-  { "Tastyep/structlog.nvim", commit = commit.structlog },
+  { "Tastyep/structlog.nvim" },
 
-  { "nvim-lua/popup.nvim", commit = commit.popup },
-  { "nvim-lua/plenary.nvim", commit = commit.plenary },
+  { "nvim-lua/popup.nvim" },
+  { "nvim-lua/plenary.nvim" },
   -- Telescope
   {
     "nvim-telescope/telescope.nvim",
-    commit = commit.telescope,
+
     config = function()
       require("lvim.core.telescope").setup()
     end,
@@ -86,14 +47,12 @@ return {
   {
     "nvim-telescope/telescope-fzf-native.nvim",
     requires = { "nvim-telescope/telescope.nvim" },
-    commit = commit.telescope_fzf_native,
     run = "make",
     disable = not lvim.builtin.telescope.active,
   },
   -- Install nvim-cmp, and buffer source as a dependency
   {
     "hrsh7th/nvim-cmp",
-    commit = commit.nvim_cmp,
     config = function()
       if lvim.builtin.cmp then
         require("lvim.core.cmp").setup()
@@ -106,41 +65,33 @@ return {
   },
   {
     "rafamadriz/friendly-snippets",
-    commit = commit.friendly_snippets,
   },
   {
     "L3MON4D3/LuaSnip",
     config = function()
       require("luasnip/loaders/from_vscode").lazy_load()
     end,
-    commit = commit.luasnip,
   },
   {
     "hrsh7th/cmp-nvim-lsp",
-    commit = commit.cmp_nvim_lsp,
   },
   {
     "saadparwaiz1/cmp_luasnip",
-    commit = commit.cmp_luasnip,
   },
   {
     "hrsh7th/cmp-buffer",
-    commit = commit.cmp_buffer,
   },
   {
     "hrsh7th/cmp-path",
-    commit = commit.cmp_path,
   },
   {
     "folke/lua-dev.nvim",
     module = "lua-dev",
-    commit = commit.lua_dev,
   },
 
   -- Autopairs
   {
     "windwp/nvim-autopairs",
-    commit = commit.nvim_autopairs,
     -- event = "InsertEnter",
     config = function()
       require("lvim.core.autopairs").setup()
@@ -151,7 +102,6 @@ return {
   -- Treesitter
   {
     "nvim-treesitter/nvim-treesitter",
-    commit = commit.nvim_treesitter,
     branch = vim.fn.has "nvim-0.6" == 1 and "master" or "0.5-compat",
     -- run = ":TSUpdate",
     config = function()
@@ -160,7 +110,6 @@ return {
   },
   {
     "JoosepAlviste/nvim-ts-context-commentstring",
-    commit = commit.nvim_ts_context_commentstring,
     event = "BufReadPost",
   },
 
@@ -169,7 +118,6 @@ return {
     "kyazdani42/nvim-tree.lua",
     -- event = "BufWinOpen",
     -- cmd = "NvimTreeToggle",
-    commit = commit.nvim_tree,
     config = function()
       require("lvim.core.nvimtree").setup()
     end,
@@ -178,7 +126,6 @@ return {
 
   {
     "lewis6991/gitsigns.nvim",
-    commit = commit.gitsigns,
 
     config = function()
       require("lvim.core.gitsigns").setup()
@@ -190,7 +137,6 @@ return {
   -- Whichkey
   {
     "folke/which-key.nvim",
-    commit = commit.which_key,
     config = function()
       require("lvim.core.which-key").setup()
     end,
@@ -201,7 +147,6 @@ return {
   -- Comments
   {
     "numToStr/Comment.nvim",
-    commit = commit.comment,
     event = "BufRead",
     config = function()
       require("lvim.core.comment").setup()
@@ -212,7 +157,6 @@ return {
   -- project.nvim
   {
     "ahmedkhalf/project.nvim",
-    commit = commit.project,
     config = function()
       require("lvim.core.project").setup()
     end,
@@ -220,13 +164,12 @@ return {
   },
 
   -- Icons
-  { "kyazdani42/nvim-web-devicons", commit = commit.nvim_web_devicons },
+  { "kyazdani42/nvim-web-devicons" },
 
   -- Status Line and Bufferline
   {
     -- "hoob3rt/lualine.nvim",
     "nvim-lualine/lualine.nvim",
-    commit = commit.lualine,
     -- "Lunarvim/lualine.nvim",
     config = function()
       require("lvim.core.lualine").setup()
@@ -236,7 +179,6 @@ return {
 
   {
     "akinsho/bufferline.nvim",
-    commit = commit.bufferline,
     config = function()
       require("lvim.core.bufferline").setup()
     end,
@@ -247,7 +189,6 @@ return {
   -- Debugging
   {
     "mfussenegger/nvim-dap",
-    commit = commit.nvim_dap,
     -- event = "BufWinEnter",
     config = function()
       require("lvim.core.dap").setup()
@@ -258,7 +199,6 @@ return {
   -- Debugger management
   {
     "Pocco81/DAPInstall.nvim",
-    commit = commit.dapinstall,
     -- event = "BufWinEnter",
     -- event = "BufRead",
     disable = not lvim.builtin.dap.active,
@@ -270,14 +210,12 @@ return {
     config = function()
       require("lvim.core.alpha").setup()
     end,
-    commit = commit.alpha_nvim,
     disable = not lvim.builtin.alpha.active,
   },
 
   -- Terminal
   {
     "akinsho/toggleterm.nvim",
-    commit = commit.toggleterm,
     event = "BufWinEnter",
     config = function()
       require("lvim.core.terminal").setup()
@@ -288,6 +226,13 @@ return {
   -- SchemaStore
   {
     "b0o/schemastore.nvim",
-    commit = commit.schemastore,
   },
 }
+
+for _, entry in ipairs(core_plugins) do
+  if not os.getenv "LVIM_DEV_MODE" then
+    entry["lock"] = true
+  end
+end
+
+return core_plugins

+ 0 - 18
lua/lvim/utils/git.lua

@@ -113,22 +113,4 @@ function M.get_lvim_current_sha()
   return abbrev_version
 end
 
-function M.generate_plugins_sha(output)
-  local list = {}
-  output = output or "commits.lua"
-
-  local core_plugins = require "lvim.plugins"
-  for _, plugin in pairs(core_plugins) do
-    local name = plugin[1]:match "/(%S*)"
-    local url = "https://github.com/" .. plugin[1]
-    print("checking: " .. name .. ", at: " .. url)
-    local retval, latest_sha = git_cmd { args = { "ls-remote", url, "origin", "HEAD" } }
-    if retval == 0 then
-      -- replace dashes, remove postfixes and use lowercase
-      local normalize_name = (name:gsub("-", "_"):gsub("%.%S+", "")):lower()
-      list[normalize_name] = latest_sha[1]:gsub("\tHEAD", "")
-    end
-  end
-  require("lvim.utils").write_file(output, "local commit = " .. vim.inspect(list), "w")
-end
 return M

+ 12 - 7
lua/lvim/utils/hooks.lua

@@ -12,17 +12,22 @@ function M.run_pre_reload()
 end
 
 function M.run_on_packer_complete()
-  if not in_headless then
-    -- manually trigger event to fix colors
-    vim.cmd [[ doautocmd ColorScheme ]]
+  Log:debug "Packer operation complete"
+  vim.cmd [[doautocmd User PackerComplete]]
+
+  vim.g.colors_name = lvim.colorscheme
+  pcall(vim.cmd, "colorscheme " .. lvim.colorscheme)
+
+  if M._reload_triggered then
+    Log:info "Reloaded configuration"
+    M._reload_triggered = nil
   end
-  Log:info "Reloaded configuration"
 end
 
 function M.run_post_reload()
   Log:debug "Starting post-reload hook"
-  require("lvim.plugin-loader").ensure_installed()
   M.reset_cache()
+  M._reload_triggered = true
 end
 
 ---Reset any startup cache files used by Packer and Impatient
@@ -48,8 +53,8 @@ function M.run_post_update()
   Log:debug "Starting post-update hook"
   M.reset_cache()
 
-  Log:debug "Updating core plugins"
-  require("lvim.plugin-loader").ensure_installed()
+  Log:debug "Syncing core plugins"
+  require("lvim.plugin-loader").sync_core_plugins()
 
   if not in_headless then
     vim.schedule(function()

+ 113 - 0
snapshots/default.json

@@ -0,0 +1,113 @@
+{
+  "Comment.nvim": {
+    "commit": "0aaea32"
+  },
+  "DAPInstall.nvim": {
+    "commit": "24923c3"
+  },
+  "FixCursorHold.nvim": {
+    "commit": "1bfb32e"
+  },
+  "LuaSnip": {
+    "commit": "eb5b77e"
+  },
+  "alpha-nvim": {
+    "commit": "534a86b"
+  },
+  "bufferline.nvim": {
+    "commit": "004cd57"
+  },
+  "cmp-buffer": {
+    "commit": "d66c4c2"
+  },
+  "cmp-nvim-lsp": {
+    "commit": "ebdfc20"
+  },
+  "cmp-path": {
+    "commit": "466b6b8"
+  },
+  "cmp_luasnip": {
+    "commit": "b108297"
+  },
+  "friendly-snippets": {
+    "commit": "e302658"
+  },
+  "gitsigns.nvim": {
+    "commit": "83ab3ca"
+  },
+  "lua-dev.nvim": {
+    "commit": "a0ee777"
+  },
+  "lualine.nvim": {
+    "commit": "c8e5a69"
+  },
+  "nlsp-settings.nvim": {
+    "commit": "995b5e6"
+  },
+  "null-ls.nvim": {
+    "commit": "899785c"
+  },
+  "nvim-autopairs": {
+    "commit": "06535b1"
+  },
+  "nvim-cmp": {
+    "commit": "7dbe34e"
+  },
+  "nvim-dap": {
+    "commit": "c20c78d"
+  },
+  "nvim-lsp-installer": {
+    "commit": "c7f1437"
+  },
+  "nvim-lspconfig": {
+    "commit": "3d1baa8"
+  },
+  "nvim-notify": {
+    "commit": "da10302"
+  },
+  "nvim-tree.lua": {
+    "commit": "6368880"
+  },
+  "nvim-treesitter": {
+    "commit": "2472e47"
+  },
+  "nvim-ts-context-commentstring": {
+    "commit": "7810f1f"
+  },
+  "nvim-web-devicons": {
+    "commit": "09e6231"
+  },
+  "onedarker.nvim": {
+    "commit": "b00dd21"
+  },
+  "packer.nvim": {
+    "commit": "4dedd3b"
+  },
+  "plenary.nvim": {
+    "commit": "f9c65cd"
+  },
+  "popup.nvim": {
+    "commit": "b7404d3"
+  },
+  "project.nvim": {
+    "commit": "cef52b8"
+  },
+  "schemastore.nvim": {
+    "commit": "d423f6c"
+  },
+  "structlog.nvim": {
+    "commit": "6f1403a"
+  },
+  "telescope-fzf-native.nvim": {
+    "commit": "8ec164b"
+  },
+  "telescope.nvim": {
+    "commit": "6e7ee38"
+  },
+  "toggleterm.nvim": {
+    "commit": "5733b24"
+  },
+  "which-key.nvim": {
+    "commit": "a3c19ec"
+  }
+}

+ 3 - 6
tests/helpers.lua → tests/lvim/helpers.lua

@@ -13,11 +13,11 @@ function M.search_file(file, args)
       end,
     })
     :sync()
-  return stdout, ret, stderr
+  return ret, stdout, stderr
 end
 
 function M.file_contains(file, query)
-  local stdout, ret, stderr = M.search_file(file, query)
+  local ret, stdout, stderr = M.search_file(file, query)
   if ret == 0 then
     return true
   end
@@ -32,7 +32,7 @@ end
 
 function M.log_contains(query)
   local logfile = require("lvim.core.log"):get_path()
-  local stdout, ret, stderr = M.search_file(logfile, query)
+  local ret, stdout, stderr = M.search_file(logfile, query)
   if ret == 0 then
     return true
   end
@@ -42,9 +42,6 @@ function M.log_contains(query)
   if not vim.tbl_isempty(stdout) then
     error(vim.inspect(stdout))
   end
-  if not vim.tbl_isempty(stderr) then
-    error(vim.inspect(stderr))
-  end
   return false
 end
 

+ 6 - 6
tests/minimal_init.lua

@@ -2,11 +2,11 @@ local path_sep = vim.loop.os_uname().version:match "Windows" and "\\" or "/"
 local base_dir = os.getenv "LUNARVIM_RUNTIME_DIR" .. path_sep .. "lvim"
 local tests_dir = base_dir .. path_sep .. "tests"
 
-vim.opt.rtp = { base_dir, tests_dir, os.getenv "VIMRUNTIME" }
+vim.opt.rtp:append(tests_dir)
+vim.opt.rtp:append(base_dir)
 
-vim.opt.swapfile = false
+require("lvim.bootstrap"):init(base_dir)
 
--- load helper functions before any other plugin to avoid name-collisions
-pcall(require, "tests.helpers")
-
-require("lvim.bootstrap"):init()
+-- NOTE: careful about name collisions
+-- see https://github.com/nvim-lualine/lualine.nvim/pull/621
+require "tests.lvim.helpers"

+ 14 - 8
tests/specs/config_loader_spec.lua

@@ -21,25 +21,31 @@ a.describe("config-loader", function()
     local test_path = "/tmp/lvim"
     os.execute(string.format([[echo "vim.opt.undodir = '%s'" >> %s]], test_path, user_config_path))
     config:reload()
-    assert.equal(vim.opt.undodir:get()[1], test_path)
+    vim.schedule(function()
+      assert.equal(vim.opt.undodir:get()[1], test_path)
+    end)
   end)
 
   a.it("should not get interrupted by errors in user-config", function()
     local test_path = "/tmp/lunarvim"
     os.execute(string.format([[echo "vim.opt.undodir = '%s'" >> %s]], test_path, user_config_path))
     config:reload()
-    assert.equal(vim.opt.undodir:get()[1], test_path)
+    vim.schedule(function()
+      assert.equal(vim.opt.undodir:get()[1], test_path)
+    end)
     os.execute(string.format("echo 'bad_string_test' >> %s", user_config_path))
     local error_handler = function(msg)
       return msg
     end
     local err = xpcall(config:reload(), error_handler)
     assert.falsy(err)
-    assert.equal(vim.opt.undodir:get()[1], test_path)
-    local errmsg = vim.fn.eval "v:errmsg"
-    local exception = vim.fn.eval "v:exception"
-    assert.equal("", errmsg) -- v:errmsg was not updated.
-    assert.equal("", exception)
-    os.execute(string.format("echo '' > %s", user_config_path))
+    vim.schedule(function()
+      assert.equal(vim.opt.undodir:get()[1], test_path)
+      local errmsg = vim.fn.eval "v:errmsg"
+      local exception = vim.fn.eval "v:exception"
+      assert.equal("", errmsg) -- v:errmsg was not updated.
+      assert.equal("", exception)
+      os.execute(string.format("echo '' > %s", user_config_path))
+    end)
   end)
 end)

+ 5 - 4
tests/specs/lsp_spec.lua

@@ -1,6 +1,6 @@
 local a = require "plenary.async_lib.tests"
 local utils = require "lvim.utils"
-local helpers = require "tests.helpers"
+local helpers = require "tests.lvim.helpers"
 local temp_dir = vim.loop.os_getenv "TEMP" or "/tmp"
 lvim.lsp.templates_dir = join_paths(temp_dir, "lvim", "tests", "artifacts")
 
@@ -31,7 +31,7 @@ a.describe("lsp workflow", function()
     lvim.log.level = "debug"
 
     local plugins = require "lvim.plugins"
-    require("lvim.plugin-loader"):load { plugins, lvim.plugins }
+    require("lvim.plugin-loader").load { plugins, lvim.plugins }
 
     if utils.is_file(logfile) then
       assert.equal(vim.fn.delete(logfile), 0)
@@ -51,8 +51,9 @@ a.describe("lsp workflow", function()
     require("lvim.lsp").setup()
 
     for _, file in ipairs(vim.fn.glob(lvim.lsp.templates_dir .. "/*.lua", 1, 1)) do
-      for _, server in ipairs(lvim.lsp.override) do
-        assert.False(helpers.file_contains(file, server))
+      for _, server_name in ipairs(lvim.lsp.override) do
+        local setup_cmd = string.format([[require("lvim.lsp.manager").setup(%q)]], server_name)
+        assert.False(helpers.file_contains(file, setup_cmd))
       end
     end
   end)

+ 34 - 2
tests/specs/plugins_load_spec.lua

@@ -5,7 +5,7 @@ a.describe("plugin-loader", function()
   local loader = require "lvim.plugin-loader"
 
   a.it("should be able to load default packages without errors", function()
-    loader:load { plugins, lvim.plugins }
+    loader.load { plugins, lvim.plugins }
 
     -- TODO: maybe there's a way to avoid hard-coding the names of the modules?
     local startup_plugins = {
@@ -18,7 +18,7 @@ a.describe("plugin-loader", function()
   end)
 
   a.it("should be able to load lsp packages without errors", function()
-    loader:load { plugins, lvim.plugins }
+    loader.load { plugins, lvim.plugins }
 
     require("lvim.lsp").setup()
 
@@ -32,4 +32,36 @@ a.describe("plugin-loader", function()
       assert.truthy(package.loaded[plugin])
     end
   end)
+  a.it("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 "unmached 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)

+ 114 - 0
utils/ci/generate_new_lockfile.lua

@@ -0,0 +1,114 @@
+local sp = os.getenv "SNAPSHOT_PATH"
+
+local function call_proc(process, opts, cb)
+  local std_output = ""
+  local error_output = ""
+
+  local function onread(_, is_stderr)
+    return function(err, data)
+      if data then
+        if is_stderr then
+          error_output = (error_output or "") .. err
+        else
+          std_output = (std_output or "") .. data
+        end
+      end
+    end
+  end
+
+  local uv = vim.loop
+  local handle
+
+  local stdout = uv.new_pipe(false)
+  local stderr = uv.new_pipe(false)
+
+  local stdio = { nil, stdout, stderr }
+
+  handle = uv.spawn(
+    process,
+    { args = opts.args, cwd = uv.cwd(), stdio = stdio },
+    vim.schedule_wrap(function(code)
+      if code ~= 0 then
+        stdout:read_stop()
+        stderr:read_stop()
+      end
+
+      local check = uv.new_check()
+      check:start(function()
+        for _, pipe in ipairs(stdio) do
+          if pipe and not pipe:is_closing() then
+            return
+          end
+        end
+        check:stop()
+        handle:close()
+        cb(code, std_output, error_output)
+      end)
+    end)
+  )
+
+  uv.read_start(stdout, onread(handle, false))
+  uv.read_start(stderr, onread(handle, true))
+
+  return handle
+end
+
+local plugins_list = {}
+
+local completed = 0
+
+local function write_lockfile(verbose)
+  local default_plugins = {}
+  local active_jobs = {}
+
+  local core_plugins = require "lvim.plugins"
+  for _, plugin in pairs(core_plugins) do
+    local name = plugin[1]:match "/(%S*)"
+    local url = "https://github.com/" .. plugin[1]
+    local commit = ""
+    table.insert(default_plugins, {
+      name = name,
+      url = url,
+      commit = commit,
+    })
+  end
+
+  table.sort(default_plugins, function(a, b)
+    return a.name < b.name
+  end)
+
+  for _, entry in pairs(default_plugins) do
+    local on_done = function(success, result, errors)
+      completed = completed + 1
+      if not success then
+        print("error: " .. errors)
+        return
+      end
+      local latest_sha = result:gsub("\tHEAD\n", ""):sub(1, 7)
+      plugins_list[entry.name] = {
+        commit = latest_sha,
+      }
+    end
+
+    local handle = call_proc("git", { args = { "ls-remote", entry.url, "HEAD" } }, on_done)
+    table.insert(active_jobs, handle)
+  end
+
+  print("active: " .. #active_jobs)
+  print("parsers: " .. #default_plugins)
+
+  vim.wait(#active_jobs * 60 * 1000, function()
+    return completed == #active_jobs
+  end)
+
+  if verbose then
+    print(vim.inspect(plugins_list))
+  end
+
+  local fd = assert(io.open(sp, "w"))
+  fd:write(vim.json.encode(plugins_list), "\n")
+  fd:flush()
+end
+
+write_lockfile()
+vim.cmd "q"

+ 19 - 0
utils/ci/generate_new_lockfile.sh

@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+set -e
+
+REPO_DIR=$(git rev-parse --show-toplevel)
+
+export SNAPSHOT_NAME="default.json"
+export SNAPSHOT_DIR="${REPO_DIR}/snapshots"
+
+mkdir -p "${SNAPSHOT_DIR}"
+
+export SNAPSHOT_PATH="${REPO_DIR}/snapshots/${SNAPSHOT_NAME}"
+
+time lvim --headless \
+  -c "luafile ./utils/ci/generate_new_lockfile.lua"
+
+temp=$(mktemp)
+
+jq --sort-keys . "${SNAPSHOT_PATH}" >"${temp}"
+mv "${temp}" "${SNAPSHOT_PATH}"

+ 2 - 2
utils/installer/config.example.lua

@@ -46,10 +46,10 @@ lvim.keys.normal_mode["<C-s>"] = ":w<cr>"
 --   name = "+Trouble",
 --   r = { "<cmd>Trouble lsp_references<cr>", "References" },
 --   f = { "<cmd>Trouble lsp_definitions<cr>", "Definitions" },
---   d = { "<cmd>Trouble lsp_document_diagnostics<cr>", "Diagnostics" },
+--   d = { "<cmd>Trouble document_diagnostics<cr>", "Diagnostics" },
 --   q = { "<cmd>Trouble quickfix<cr>", "QuickFix" },
 --   l = { "<cmd>Trouble loclist<cr>", "LocationList" },
---   w = { "<cmd>Trouble lsp_workspace_diagnostics<cr>", "Diagnostics" },
+--   w = { "<cmd>Trouble workspace_diagnostics<cr>", "Wordspace Diagnostics" },
 -- }
 
 -- TODO: User Config for predefined plugins

+ 2 - 2
utils/installer/config_win.example.lua

@@ -63,10 +63,10 @@ lvim.keys.normal_mode["<C-s>"] = ":w<cr>"
 --   name = "+Trouble",
 --   r = { "<cmd>Trouble lsp_references<cr>", "References" },
 --   f = { "<cmd>Trouble lsp_definitions<cr>", "Definitions" },
---   d = { "<cmd>Trouble lsp_document_diagnostics<cr>", "Diagnostics" },
+--   d = { "<cmd>Trouble document_diagnostics<cr>", "Diagnostics" },
 --   q = { "<cmd>Trouble quickfix<cr>", "QuickFix" },
 --   l = { "<cmd>Trouble loclist<cr>", "LocationList" },
---   w = { "<cmd>Trouble lsp_workspace_diagnostics<cr>", "Diagnostics" },
+--   w = { "<cmd>Trouble workspace_diagnostics<cr>", "Workspace Diagnostics" },
 -- }
 
 -- After changing plugin config exit and reopen LunarVim, Run :PackerInstall :PackerCompile

+ 1 - 1
utils/installer/install.ps1

@@ -267,7 +267,7 @@ function create_alias {
         return
     }
 
-    Add-Content -Path $PROFILE -Value $("Set-Alias lvim $lvim_bin")
+    Add-Content -Path $PROFILE -Value $("`r`nSet-Alias lvim $lvim_bin")
 
     Write-Host 'To use the new alias in this window reload your profile with: `. $PROFILE`' -ForegroundColor Green
 }

+ 8 - 2
utils/installer/uninstall.ps1

@@ -49,9 +49,15 @@ function remove_lvim_dirs($force) {
         if (Test-Path $dir) {
             Remove-Item -Force -Recurse $dir
         }
-        if ($force -eq $true -and (Test-Path "$dir.bak" -or Test-Path "$dir.old")) {
-            Remove-Item -Force -Recurse "$dir.{bak,old}"
+        if ($force -eq $true) {
+            if (Test-Path "$dir.bak") {
+                Remove-Item -Force -Recurse "$dir.bak"
+            }
+            if (Test-Path "$dir.old") {
+                Remove-Item -Force -Recurse "$dir.old"
+            }
         }
     }
 }
 
+main($args)