123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- -- 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 appdir = os.getenv "APPDIR"
- local M = {
- 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",
- },
- log = {},
- }
- _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()
- 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
- return stat.mtime.sec .. stat.mtime.nsec .. stat.size
- end
- 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
- 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)
- profile(M.chunks, path, name, loader)
- end
- 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
- 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 .. "/" .. path:sub(#("lua/" .. parent) + 2)
- if fs_stat(modpath) then
- return modpath, "cache(p)"
- end
- end
- end
- end
- -- What Neovim does by default; slowest
- local modpath = get_runtime(paths, false, { is_lua = true })[1]
- return modpath, "standard"
- end
- 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
- 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
- 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
- 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("%.", "/")
- local paths = { "lua/" .. basename .. ".lua", "lua/" .. basename .. "/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 = package.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
- if not mc.cache[path] then
- return nil, string.format("No cache for path %s", path)
- end
- local mhash, codes = unpack(mc.cache[path])
- if mhash ~= hash(modpath_unmangle(path)) then
- mc.cache[path] = nil
- mc.dirty = true
- return nil, string.format("Stale cache for path %s", path)
- end
- local chunk = loadstring(codes)
- if not chunk then
- mc.cache[path] = nil
- 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 = 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)
- chunk, err = _loadfile(path)
- 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
- cprofile(path, "load_end", "standard")
- return chunk, err
- end
- 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
- end
- _save_cache(M.chunks)
- _save_cache(M.modpaths)
- end
- 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
- 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")
- 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
- _init_cache(M.chunks)
- _init_cache(M.modpaths)
- end
- local function setup()
- init_cache()
- -- Override default functions
- 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
- command! LuaCacheClear lua _G.__luacache.clear_cache()
- command! LuaCacheLog lua _G.__luacache.print_log()
- ]]
- end
- setup()
- return M
|