Pārlūkot izejas kodu

feat: latest impatient updates from upstream (#3236)

kylo252 2 gadi atpakaļ
vecāks
revīzija
d433409995
4 mainītis faili ar 274 papildinājumiem un 151 dzēšanām
  1. 2 0
      .luacheckrc
  2. 211 98
      lua/lvim/impatient.lua
  3. 60 49
      lua/lvim/impatient/profile.lua
  4. 1 4
      lua/lvim/utils/hooks.lua

+ 2 - 0
.luacheckrc

@@ -1,3 +1,4 @@
+---@diagnostic disable
 -- vim: ft=lua tw=80
 
 stds.nvim = {
@@ -30,6 +31,7 @@ stds.nvim = {
 std = "lua51+nvim"
 
 files["tests/*_spec.lua"].std = "lua51+nvim+busted"
+files["lua/lvim/impatient*"].ignore = {"121"}
 
 -- Don't report unused self arguments of methods.
 self = false

+ 211 - 98
lua/lvim/impatient.lua

@@ -1,5 +1,4 @@
 -- modified version from https://github.com/lewis6991/impatient.nvim
-
 local vim = vim
 local api = vim.api
 local uv = vim.loop
@@ -7,66 +6,96 @@ 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 appdir = os.getenv "APPDIR"
+local function modpath_mangle(modpath)
+  for name, dir in pairs(std_dirs) do
+    modpath = modpath:gsub(dir, name)
+  end
+  return modpath
+end
 
-local M = {
+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,
-    path = vim.fn.stdpath "cache" .. "/luacache_chunks",
+    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,
-    path = vim.fn.stdpath "cache" .. "/luacache_modpaths",
+    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
 
-if not get_runtime then
-  -- nvim 0.5 compat
-  get_runtime = function(paths, all, _)
-    local r = {}
-    for _, path in ipairs(paths) do
-      local found = api.nvim_get_runtime_file(path, all)
-      for i = 1, #found do
-        r[#r + 1] = found[i]
-      end
-    end
-    return r
-  end
-end
-
 local function log(...)
   M.log[#M.log + 1] = table.concat({ string.format(...) }, " ")
 end
 
-function M.print_log()
+local function print_log()
   for _, l in ipairs(M.log) do
     print(l)
   end
 end
 
-function M.enable_profile()
-  local P = require "lvim.impatient.profile"
-
-  M.chunks.profile = {}
-  M.modpaths.profile = {}
-
-  P.setup(M.modpaths.profile)
-
-  M.print_profile = function()
-    P.print_profile(M)
-  end
-
-  vim.cmd [[command! LuaCacheProfile lua _G.__luacache.print_profile()]]
-end
-
 local function hash(modpath)
   local stat = fs_stat(modpath)
   if stat then
@@ -75,20 +104,6 @@ local function hash(modpath)
   error("Could not hash " .. modpath)
 end
 
-local function modpath_mangle(modpath)
-  if appdir then
-    modpath = modpath:gsub(appdir, "/$APPDIR")
-  end
-  return modpath
-end
-
-local function modpath_unmangle(modpath)
-  if appdir then
-    modpath = modpath:gsub("/$APPDIR", appdir)
-  end
-  return modpath
-end
-
 local function profile(m, entry, name, loader)
   if m.profile then
     local mp = m.profile
@@ -107,18 +122,41 @@ local function mprofile(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
 
-local function get_runtime_file(basename, paths)
+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, "/")
+  local parents = vim.split(basename, sep)
   for i = #parents, 1, -1 do
-    local parent = table.concat(vim.list_slice(parents, 1, i), "/")
-    local ppath = M.modpaths.cache[parent]
+    local parent = table.concat(vim.list_slice(parents, 1, i), sep)
+    local ppath = M.modpaths:get(parent)
     if ppath then
-      if ppath:sub(-9) == "/init.lua" 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
@@ -126,38 +164,71 @@ local function get_runtime_file(basename, paths)
 
       for _, path in ipairs(paths) do
         -- path should be of form 'a/b/c.lua' or 'a/b/c/init.lua'
-        local modpath = ppath .. "/" .. path:sub(#("lua/" .. parent) + 2)
+        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, ",")
 
-  -- What Neovim does by default; slowest
-  local modpath = get_runtime(paths, false, { is_lua = true })[1]
-  return modpath, "standard"
+-- 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.cache[basename] then
-    local modpath = mp.cache[basename]
-    if fs_stat(modpath) then
-      mprofile(basename, "resolve_end", "cache")
-      return modpath
+  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
-    mp.cache[basename] = nil
-    mp.dirty = true
   end
 
-  local modpath, loader = get_runtime_file(basename, paths)
+  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)
-    log("Creating cache for module %s", basename)
-    mp.cache[basename] = modpath_mangle(modpath)
-    mp.dirty = true
+    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
 
@@ -168,8 +239,12 @@ local function extract_basename(pats)
   for _, pat in ipairs(pats) do
     for i, npat in ipairs {
       -- Ordered by most specific
-      "lua/(.*)/init%.lua",
-      "lua/(.*)%.lua",
+      "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
@@ -211,8 +286,8 @@ end
 
 -- Copied from neovim/src/nvim/lua/vim.lua with two lines changed
 local function load_package(name)
-  local basename = name:gsub("%.", "/")
-  local paths = { "lua/" .. basename .. ".lua", "lua/" .. basename .. "/init.lua" }
+  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})
@@ -239,7 +314,7 @@ local function load_package(name)
     -- 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 = package.loadlib(found[1], "luaopen_" .. modname:gsub("%.", "_"))
+    local f, err = loadlib(found[1], "luaopen_" .. modname:gsub("%.", "_"))
     return f or error(err)
   end
   return nil
@@ -248,14 +323,16 @@ end
 local function load_from_cache(path)
   local mc = M.chunks
 
-  if not mc.cache[path] then
+  local cache = mc:get(path)
+
+  if not cache then
     return nil, string.format("No cache for path %s", path)
   end
 
-  local mhash, codes = unpack(mc.cache[path])
+  local mhash, codes = unpack(cache)
 
-  if mhash ~= hash(modpath_unmangle(path)) then
-    mc.cache[path] = nil
+  if mhash ~= hash(path) then
+    mc:set(path)
     mc.dirty = true
     return nil, string.format("Stale cache for path %s", path)
   end
@@ -263,7 +340,7 @@ local function load_from_cache(path)
   local chunk = loadstring(codes)
 
   if not chunk then
-    mc.cache[path] = nil
+    mc:set(path)
     mc.dirty = true
     return nil, string.format("Cache error for path %s", path)
   end
@@ -274,19 +351,23 @@ end
 local function loadfile_cached(path)
   cprofile(path, "load_start")
 
-  local 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
+  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
-  log(err)
 
   chunk, err = _loadfile(path)
 
-  if not err then
+  if not err and M.chunks.enable then
     log("Creating cache for path %s", path)
-    M.chunks.cache[modpath_mangle(path)] = { hash(path), string.dump(chunk) }
+    M.chunks:set(path, { hash(path), string.dump(chunk) })
     M.chunks.dirty = true
   end
 
@@ -296,9 +377,12 @@ 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 = io.open(t.path, "w+b")
+      local f = assert(io.open(t.path, "w+b"))
       f:write(mpack.encode(t.cache))
       f:flush()
       t.dirty = false
@@ -308,7 +392,7 @@ function M.save_cache()
   _save_cache(M.modpaths)
 end
 
-function M.clear_cache()
+local function clear_cache()
   local function _clear_cache(t)
     t.cache = {}
     os.remove(t.path)
@@ -319,9 +403,12 @@ 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 = io.open(t.path, "rb")
+      local f = assert(io.open(t.path, "rb"))
       local ok
       ok, t.cache = pcall(function()
         return mpack.decode(f:read "*a")
@@ -336,6 +423,10 @@ local function init_cache()
     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
@@ -343,20 +434,42 @@ 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
-  -- luacheck: ignore 121
   loadfile = loadfile_cached
 
-  vim.cmd [[
-    augroup impatient
-      autocmd VimEnter,VimLeave * lua _G.__luacache.save_cache()
-    augroup END
+  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,
+  })
 
-    command! LuaCacheClear lua _G.__luacache.clear_cache()
-    command! LuaCacheLog   lua _G.__luacache.print_log()
-  ]]
+  api.nvim_create_autocmd("OptionSet", {
+    group = augroup,
+    pattern = "runtimepath",
+    callback = function()
+      rtp = vim.split(vim.o.rtp, ",")
+    end,
+  })
 end
 
 setup()

+ 60 - 49
lua/lvim/impatient/profile.lua

@@ -1,12 +1,13 @@
 local M = {}
 
-local api, uv = vim.api, vim.loop
+local sep
+if jit.os == "Windows" then
+  sep = "\\"
+else
+  sep = "/"
+end
 
-local std_data = vim.fn.stdpath "data"
-local std_config = vim.fn.stdpath "config"
-local vimruntime = os.getenv "VIMRUNTIME"
-local lvim_runtime = get_runtime_dir()
-local lvim_config = get_config_dir()
+local api, uv = vim.api, vim.loop
 
 local function load_buffer(title, lines)
   local bufnr = api.nvim_create_buf(false, false)
@@ -19,19 +20,6 @@ local function load_buffer(title, lines)
   api.nvim_set_current_buf(bufnr)
 end
 
-local function mod_path(path)
-  if not path then
-    return "?"
-  end
-  path = path:gsub(std_data .. "/site/pack/packer/", "<PACKER>/")
-  path = path:gsub(std_data .. "/", "<STD_DATA>/")
-  path = path:gsub(std_config .. "/", "<STD_CONFIG>/")
-  path = path:gsub(vimruntime .. "/", "<VIMRUNTIME>/")
-  path = path:gsub(lvim_runtime .. "/", "<LVIM_RUNTIME>/")
-  path = path:gsub(lvim_config .. "/", "<LVIM_CONFIG>/")
-  return path
-end
-
 local function time_tostr(x)
   if x == 0 then
     return "?"
@@ -51,7 +39,7 @@ local function mem_tostr(x)
   return string.format("%1.1f%s", x, unit)
 end
 
-function M.print_profile(I)
+function M.print_profile(I, std_dirs)
   local mod_profile = I.modpaths.profile
   local chunk_profile = I.chunks.profile
 
@@ -67,42 +55,50 @@ function M.print_profile(I)
   for path, m in pairs(chunk_profile) do
     m.load = m.load_end - m.load_start
     m.load = m.load / 1000000
-    m.path = mod_path(path)
+    m.path = path or "?"
   end
 
   local module_content_width = 0
 
+  local unloaded = {}
+
   for module, m in pairs(mod_profile) do
-    m.resolve = 0
-    if m.resolve_end then
-      m.resolve = m.resolve_end - m.resolve_start
-      m.resolve = m.resolve / 1000000
-    end
+    local module_dot = module:gsub(sep, ".")
+    m.module = module_dot
 
-    m.module = module:gsub("/", ".")
-    m.loader = m.loader or m.loader_guess
+    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
 
-    local path = I.modpaths.cache[module]
-    local path_prof = chunk_profile[path]
-    m.path = mod_path(path)
+      m.loader = m.loader or m.loader_guess
 
-    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
+      local path = I.modpaths.cache[module]
+      local path_prof = chunk_profile[path]
+      m.path = path or "?"
 
-    total_resolve = total_resolve + m.resolve
-    total_load = total_load + m.load
+      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
 
-    if #module > module_content_width then
-      module_content_width = #module
-    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
+      modules[#modules + 1] = m
+    end
   end
 
   table.sort(modules, function(a, b)
@@ -181,6 +177,12 @@ function M.print_profile(I)
   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)
@@ -207,7 +209,17 @@ function M.print_profile(I)
       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)
@@ -216,13 +228,12 @@ end
 M.setup = function(profile)
   local _require = require
 
-  -- luacheck: ignore 121
   require = function(mod)
-    local basename = mod:gsub("%.", "/")
+    local basename = mod:gsub("%.", sep)
     if not profile[basename] then
       profile[basename] = {}
       profile[basename].resolve_start = uv.hrtime()
-      profile[basename].loader_guess = "C"
+      profile[basename].loader_guess = ""
     end
     return _require(mod)
   end
@@ -232,7 +243,7 @@ M.setup = function(profile)
   for i = 1, #pl do
     local l = pl[i]
     pl[i] = function(mod)
-      local basename = mod:gsub("%.", "/")
+      local basename = mod:gsub("%.", sep)
       profile[basename].loader_guess = i == 1 and "preloader" or "loader #" .. i
       return l(mod)
     end

+ 1 - 4
lua/lvim/utils/hooks.lua

@@ -34,10 +34,7 @@ end
 ---It also forces regenerating any template ftplugin files
 ---Tip: Useful for clearing any outdated settings
 function M.reset_cache()
-  local impatient = _G.__luacache
-  if impatient then
-    impatient.clear_cache()
-  end
+  vim.cmd [[LuaCacheClear]]
   local lvim_modules = {}
   for module, _ in pairs(package.loaded) do
     if module:match "lvim.core" or module:match "lvim.lsp" then