2021-09-08 18:29:44 +02:00
|
|
|
-- modified version from https://github.com/lewis6991/impatient.nvim
|
|
|
|
|
|
|
|
local vim = vim
|
|
|
|
local api = vim.api
|
2022-04-20 13:22:26 +02:00
|
|
|
local uv = vim.loop
|
|
|
|
local _loadfile = loadfile
|
|
|
|
local get_runtime = api.nvim__get_runtime
|
|
|
|
local fs_stat = uv.fs_stat
|
|
|
|
local mpack = vim.mpack
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local appdir = os.getenv "APPDIR"
|
2021-09-08 18:29:44 +02:00
|
|
|
|
|
|
|
local M = {
|
2022-04-20 13:22:26 +02:00
|
|
|
chunks = {
|
|
|
|
cache = {},
|
|
|
|
profile = nil,
|
|
|
|
dirty = false,
|
|
|
|
path = vim.fn.stdpath "cache" .. "/luacache_chunks",
|
|
|
|
},
|
|
|
|
modpaths = {
|
|
|
|
cache = {},
|
|
|
|
profile = nil,
|
|
|
|
dirty = false,
|
|
|
|
path = vim.fn.stdpath "cache" .. "/luacache_modpaths",
|
|
|
|
},
|
2021-09-08 18:29:44 +02:00
|
|
|
log = {},
|
|
|
|
}
|
|
|
|
|
|
|
|
_G.__luacache = M
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
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
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function log(...)
|
|
|
|
M.log[#M.log + 1] = table.concat({ string.format(...) }, " ")
|
|
|
|
end
|
|
|
|
|
|
|
|
function M.print_log()
|
|
|
|
for _, l in ipairs(M.log) do
|
|
|
|
print(l)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function M.enable_profile()
|
2022-04-20 13:22:26 +02:00
|
|
|
local P = require "lvim.impatient.profile"
|
|
|
|
|
|
|
|
M.chunks.profile = {}
|
|
|
|
M.modpaths.profile = {}
|
|
|
|
|
|
|
|
P.setup(M.modpaths.profile)
|
|
|
|
|
2021-09-08 18:29:44 +02:00
|
|
|
M.print_profile = function()
|
2022-04-20 13:22:26 +02:00
|
|
|
P.print_profile(M)
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
vim.cmd [[command! LuaCacheProfile lua _G.__luacache.print_profile()]]
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local function hash(modpath)
|
2022-04-20 13:22:26 +02:00
|
|
|
local stat = fs_stat(modpath)
|
2021-09-08 18:29:44 +02:00
|
|
|
if stat then
|
2022-04-20 13:22:26 +02:00
|
|
|
return stat.mtime.sec .. stat.mtime.nsec .. stat.size
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
2022-04-20 13:22:26 +02:00
|
|
|
error("Could not hash " .. modpath)
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local function modpath_mangle(modpath)
|
|
|
|
if appdir then
|
|
|
|
modpath = modpath:gsub(appdir, "/$APPDIR")
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
2022-04-20 13:22:26 +02:00
|
|
|
return modpath
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local function modpath_unmangle(modpath)
|
|
|
|
if appdir then
|
|
|
|
modpath = modpath:gsub("/$APPDIR", appdir)
|
|
|
|
end
|
|
|
|
return modpath
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
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
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local function mprofile(mod, name, loader)
|
|
|
|
profile(M.modpaths, mod, name, loader)
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local function cprofile(path, name, loader)
|
|
|
|
profile(M.chunks, path, name, loader)
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local function get_runtime_file(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, "/")
|
|
|
|
for i = #parents, 1, -1 do
|
|
|
|
local parent = table.concat(vim.list_slice(parents, 1, i), "/")
|
|
|
|
local ppath = M.modpaths.cache[parent]
|
|
|
|
if ppath then
|
|
|
|
if ppath:sub(-9) == "/init.lua" then
|
|
|
|
ppath = ppath:sub(1, -10) -- a/b/init.lua -> a/b
|
2021-09-08 18:29:44 +02:00
|
|
|
else
|
2022-04-20 13:22:26 +02:00
|
|
|
ppath = ppath:sub(1, -5) -- a/b.lua -> a/b
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
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)
|
|
|
|
if fs_stat(modpath) then
|
|
|
|
return modpath, "cache(p)"
|
|
|
|
end
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
end
|
2022-04-20 13:22:26 +02:00
|
|
|
|
|
|
|
-- What Neovim does by default; slowest
|
|
|
|
local modpath = get_runtime(paths, false, { is_lua = true })[1]
|
|
|
|
return modpath, "standard"
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local function get_runtime_file_cached(basename, paths)
|
|
|
|
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
|
|
|
|
end
|
|
|
|
mp.cache[basename] = nil
|
|
|
|
mp.dirty = true
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local modpath, loader = get_runtime_file(basename, paths)
|
|
|
|
if modpath then
|
|
|
|
mprofile(basename, "resolve_end", loader)
|
|
|
|
log("Creating cache for module %s", basename)
|
|
|
|
mp.cache[basename] = modpath_mangle(modpath)
|
|
|
|
mp.dirty = true
|
|
|
|
end
|
|
|
|
return modpath
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
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/(.*)/init%.lua",
|
|
|
|
"lua/(.*)%.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
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
return basename
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
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
|
2022-04-12 11:18:17 +02:00
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local basename
|
|
|
|
|
|
|
|
if not fallback then
|
|
|
|
basename = extract_basename(pats)
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
if fallback or not basename then
|
|
|
|
return get_runtime(pats, all, opts)
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
return { get_runtime_file_cached(basename, pats) }
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
-- 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" }
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
-- 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
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
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)
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
-- 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 = package.loadlib(found[1], "luaopen_" .. modname:gsub("%.", "_"))
|
|
|
|
return f or error(err)
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local function load_from_cache(path)
|
|
|
|
local mc = M.chunks
|
|
|
|
|
|
|
|
if not mc.cache[path] then
|
|
|
|
return nil, string.format("No cache for path %s", path)
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local mhash, codes = unpack(mc.cache[path])
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
if mhash ~= hash(modpath_unmangle(path)) then
|
|
|
|
mc.cache[path] = nil
|
|
|
|
mc.dirty = true
|
|
|
|
return nil, string.format("Stale cache for path %s", path)
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local chunk = loadstring(codes)
|
|
|
|
|
2021-09-08 18:29:44 +02:00
|
|
|
if not chunk then
|
2022-04-20 13:22:26 +02:00
|
|
|
mc.cache[path] = nil
|
|
|
|
mc.dirty = true
|
|
|
|
return nil, string.format("Cache error for path %s", path)
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
return chunk
|
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
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
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
2022-04-20 13:22:26 +02:00
|
|
|
log(err)
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
chunk, err = _loadfile(path)
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
if not err then
|
|
|
|
log("Creating cache for path %s", path)
|
|
|
|
M.chunks.cache[modpath_mangle(path)] = { hash(path), string.dump(chunk) }
|
|
|
|
M.chunks.dirty = true
|
|
|
|
end
|
2021-09-12 08:15:29 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
cprofile(path, "load_end", "standard")
|
|
|
|
return chunk, err
|
|
|
|
end
|
2021-09-12 08:15:29 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
function M.save_cache()
|
|
|
|
local function _save_cache(t)
|
|
|
|
if t.dirty then
|
|
|
|
log("Updating chunk cache file: %s", t.path)
|
|
|
|
local f = io.open(t.path, "w+b")
|
|
|
|
f:write(mpack.encode(t.cache))
|
|
|
|
f:flush()
|
|
|
|
t.dirty = false
|
|
|
|
end
|
2021-09-12 08:15:29 +02:00
|
|
|
end
|
2022-04-20 13:22:26 +02:00
|
|
|
_save_cache(M.chunks)
|
|
|
|
_save_cache(M.modpaths)
|
|
|
|
end
|
2021-09-12 08:15:29 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
function M.clear_cache()
|
|
|
|
local function _clear_cache(t)
|
|
|
|
t.cache = {}
|
|
|
|
os.remove(t.path)
|
|
|
|
end
|
|
|
|
_clear_cache(M.chunks)
|
|
|
|
_clear_cache(M.modpaths)
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local function init_cache()
|
|
|
|
local function _init_cache(t)
|
|
|
|
if fs_stat(t.path) then
|
|
|
|
log("Loading cache file %s", t.path)
|
|
|
|
local f = io.open(t.path, "rb")
|
|
|
|
local ok
|
|
|
|
ok, t.cache = pcall(function()
|
|
|
|
return mpack.decode(f:read "*a")
|
2021-09-08 18:29:44 +02:00
|
|
|
end)
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
if not ok then
|
|
|
|
log("Corrupted cache file, %s. Invalidating...", t.path)
|
|
|
|
os.remove(t.path)
|
|
|
|
t.cache = {}
|
|
|
|
end
|
|
|
|
t.dirty = not ok
|
2021-09-08 18:29:44 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
_init_cache(M.chunks)
|
|
|
|
_init_cache(M.modpaths)
|
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
local function setup()
|
|
|
|
init_cache()
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
-- Override default functions
|
|
|
|
vim._load_package = load_package
|
|
|
|
vim.api.nvim__get_runtime = get_runtime_cached
|
|
|
|
-- luacheck: ignore 121
|
|
|
|
loadfile = loadfile_cached
|
2021-09-08 18:29:44 +02:00
|
|
|
|
|
|
|
vim.cmd [[
|
|
|
|
augroup impatient
|
|
|
|
autocmd VimEnter,VimLeave * lua _G.__luacache.save_cache()
|
|
|
|
augroup END
|
|
|
|
|
|
|
|
command! LuaCacheClear lua _G.__luacache.clear_cache()
|
|
|
|
command! LuaCacheLog lua _G.__luacache.print_log()
|
|
|
|
]]
|
2021-09-12 08:15:29 +02:00
|
|
|
end
|
2021-09-08 18:29:44 +02:00
|
|
|
|
2022-04-20 13:22:26 +02:00
|
|
|
setup()
|
|
|
|
|
2021-09-08 18:29:44 +02:00
|
|
|
return M
|