From 2257ade3dc2daab5ee12d27807c0b3bcf103cd29 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Sun, 26 Mar 2023 12:42:15 +0200 Subject: feat(lua): add `vim.loader` feat: new faster lua loader using byte-compilation --- runtime/lua/vim/loader.lua | 536 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 runtime/lua/vim/loader.lua (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua new file mode 100644 index 0000000000..41d5579664 --- /dev/null +++ b/runtime/lua/vim/loader.lua @@ -0,0 +1,536 @@ +local uv = vim.loop + +local M = {} + +---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number} +---@alias CacheEntry {hash:CacheHash, chunk:string} + +---@class ModuleFindOpts +---@field all? boolean Search for all matches (defaults to `false`) +---@field rtp? boolean Search for modname in the runtime path (defaults to `true`) +---@field patterns? string[] Paterns to use (defaults to `{"/init.lua", ".lua"}`) +---@field paths? string[] Extra paths to search for modname + +---@class ModuleInfo +---@field modpath string Path of the module +---@field modname string Name of the module +---@field stat? uv_fs_t File stat of the module path + +---@alias LoaderStats table + +M.path = vim.fn.stdpath('cache') .. '/luac' +M.enabled = false + +---@class Loader +---@field _rtp string[] +---@field _rtp_pure string[] +---@field _rtp_key string +local Loader = { + VERSION = 3, + ---@type table> + _indexed = {}, + ---@type table + _topmods = {}, + _loadfile = loadfile, + ---@type LoaderStats + _stats = { + find = { total = 0, time = 0, not_found = 0 }, + }, +} + +--- Tracks the time spent in a function +---@private +function Loader.track(stat, start) + Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 } + Loader._stats[stat].total = Loader._stats[stat].total + 1 + Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start +end + +--- slightly faster/different version than vim.fs.normalize +--- we also need to have it here, since the loader will load vim.fs +---@private +function Loader.normalize(path) + if path:sub(1, 1) == '~' then + local home = vim.loop.os_homedir() or '~' + if home:sub(-1) == '\\' or home:sub(-1) == '/' then + home = home:sub(1, -2) + end + path = home .. path:sub(2) + end + path = path:gsub('\\', '/'):gsub('/+', '/') + return path:sub(-1) == '/' and path:sub(1, -2) or path +end + +--- Gets the rtp excluding after directories. +--- The result is cached, and will be updated if the runtime path changes. +--- When called from a fast event, the cached value will be returned. +--- @return string[] rtp, boolean updated +---@private +function Loader.get_rtp() + local start = uv.hrtime() + if vim.in_fast_event() then + Loader.track('get_rtp', start) + return (Loader._rtp or {}), false + end + local updated = false + local key = vim.go.rtp + if key ~= Loader._rtp_key then + Loader._rtp = {} + for _, path in ipairs(vim.api.nvim_get_runtime_file('', true)) do + path = Loader.normalize(path) + -- skip after directories + if + path:sub(-6, -1) ~= '/after' + and not (Loader._indexed[path] and vim.tbl_isempty(Loader._indexed[path])) + then + Loader._rtp[#Loader._rtp + 1] = path + end + end + updated = true + Loader._rtp_key = key + end + Loader.track('get_rtp', start) + return Loader._rtp, updated +end + +--- Returns the cache file name +---@param name string can be a module name, or a file name +---@return string file_name +---@private +function Loader.cache_file(name) + local ret = M.path .. '/' .. name:gsub('[/\\:]', '%%') + return ret:sub(-4) == '.lua' and (ret .. 'c') or (ret .. '.luac') +end + +--- Saves the cache entry for a given module or file +---@param name string module name or filename +---@param entry CacheEntry +---@private +function Loader.write(name, entry) + local cname = Loader.cache_file(name) + local f = assert(uv.fs_open(cname, 'w', 438)) + local header = { + Loader.VERSION, + entry.hash.size, + entry.hash.mtime.sec, + entry.hash.mtime.nsec, + } + uv.fs_write(f, table.concat(header, ',') .. '\0') + uv.fs_write(f, entry.chunk) + uv.fs_close(f) +end + +--- Loads the cache entry for a given module or file +---@param name string module name or filename +---@return CacheEntry? +---@private +function Loader.read(name) + local start = uv.hrtime() + local cname = Loader.cache_file(name) + local f = uv.fs_open(cname, 'r', 438) + if f then + local hash = uv.fs_fstat(f) --[[@as CacheHash]] + local data = uv.fs_read(f, hash.size, 0) --[[@as string]] + uv.fs_close(f) + + local zero = data:find('\0', 1, true) + if not zero then + return + end + + ---@type integer[]|{[0]:integer} + local header = vim.split(data:sub(1, zero - 1), ',') + if tonumber(header[1]) ~= Loader.VERSION then + return + end + Loader.track('read', start) + return { + hash = { + size = tonumber(header[2]), + mtime = { sec = tonumber(header[3]), nsec = tonumber(header[4]) }, + }, + chunk = data:sub(zero + 1), + } + end + Loader.track('read', start) +end + +--- The `package.loaders` loader for lua files using the cache. +---@param modname string module name +---@return string|function +---@private +function Loader.loader(modname) + local start = uv.hrtime() + local ret = M.find(modname)[1] + if ret then + local chunk, err = Loader.load(ret.modpath, { hash = ret.stat }) + Loader.track('loader', start) + return chunk or error(err) + end + Loader.track('loader', start) + return '\ncache_loader: module ' .. modname .. ' not found' +end + +--- The `package.loaders` loader for libs +---@param modname string module name +---@return string|function +---@private +function Loader.loader_lib(modname) + local start = uv.hrtime() + local sysname = uv.os_uname().sysname:lower() or '' + local is_win = sysname:find('win', 1, true) and not sysname:find('darwin', 1, true) + local ret = M.find(modname, { patterns = is_win and { '.dll' } or { '.so' } })[1] + ---@type function?, string? + if ret 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 = modname:find('-', 1, true) + local funcname = dash and modname:sub(dash + 1) or modname + local chunk, err = package.loadlib(ret.modpath, 'luaopen_' .. funcname:gsub('%.', '_')) + Loader.track('loader_lib', start) + return chunk or error(err) + end + Loader.track('loader_lib', start) + return '\ncache_loader_lib: module ' .. modname .. ' not found' +end + +--- `loadfile` using the cache +---@param filename? string +---@param mode? "b"|"t"|"bt" +---@param env? table +---@param hash? CacheHash +---@return function?, string? error_message +---@private +-- luacheck: ignore 312 +function Loader.loadfile(filename, mode, env, hash) + local start = uv.hrtime() + filename = Loader.normalize(filename) + mode = nil -- ignore mode, since we byte-compile the lua source files + local chunk, err = Loader.load(filename, { mode = mode, env = env, hash = hash }) + Loader.track('loadfile', start) + return chunk, err +end + +--- Checks whether two cache hashes are the same based on: +--- * file size +--- * mtime in seconds +--- * mtime in nanoseconds +---@param h1 CacheHash +---@param h2 CacheHash +---@private +function Loader.eq(h1, h2) + return h1 + and h2 + and h1.size == h2.size + and h1.mtime.sec == h2.mtime.sec + and h1.mtime.nsec == h2.mtime.nsec +end + +--- Loads the given module path using the cache +---@param modpath string +---@param opts? {hash?: CacheHash, mode?: "b"|"t"|"bt", env?:table} (table|nil) Options for loading the module: +--- - hash: (table) the hash of the file to load if it is already known. (defaults to `vim.loop.fs_stat({modpath})`) +--- - mode: (string) the mode to load the module with. "b"|"t"|"bt" (defaults to `nil`) +--- - env: (table) the environment to load the module in. (defaults to `nil`) +---@see |luaL_loadfile()| +---@return function?, string? error_message +---@private +function Loader.load(modpath, opts) + local start = uv.hrtime() + + opts = opts or {} + local hash = opts.hash or uv.fs_stat(modpath) + ---@type function?, string? + local chunk, err + + if not hash then + -- trigger correct error + chunk, err = Loader._loadfile(modpath, opts.mode, opts.env) + Loader.track('load', start) + return chunk, err + end + + local entry = Loader.read(modpath) + if entry and Loader.eq(entry.hash, hash) then + -- found in cache and up to date + chunk, err = load(entry.chunk --[[@as string]], '@' .. modpath, opts.mode, opts.env) + if not (err and err:find('cannot load incompatible bytecode', 1, true)) then + Loader.track('load', start) + return chunk, err + end + end + entry = { hash = hash, modpath = modpath } + + chunk, err = Loader._loadfile(modpath, opts.mode, opts.env) + if chunk then + entry.chunk = string.dump(chunk) + Loader.write(modpath, entry) + end + Loader.track('load', start) + return chunk, err +end + +--- Finds lua modules for the given module name. +---@param modname string Module name, or `"*"` to find the top-level modules instead +---@param opts? ModuleFindOpts (table|nil) Options for finding a module: +--- - rtp: (boolean) Search for modname in the runtime path (defaults to `true`) +--- - paths: (string[]) Extra paths to search for modname (defaults to `{}`) +--- - patterns: (string[]) List of patterns to use when searching for modules. +--- A pattern is a string added to the basename of the Lua module being searched. +--- (defaults to `{"/init.lua", ".lua"}`) +--- - all: (boolean) Return all matches instead of just the first one (defaults to `false`) +---@return ModuleInfo[] (list) A list of results with the following properties: +--- - modpath: (string) the path to the module +--- - modname: (string) the name of the module +--- - stat: (table|nil) the fs_stat of the module path. Won't be returned for `modname="*"` +function M.find(modname, opts) + local start = uv.hrtime() + opts = opts or {} + + modname = modname:gsub('/', '.') + local basename = modname:gsub('%.', '/') + local idx = modname:find('.', 1, true) + + -- HACK: fix incorrect require statements. Really not a fan of keeping this, + -- but apparently the regular lua loader also allows this + if idx == 1 then + modname = modname:gsub('^%.+', '') + basename = modname:gsub('%.', '/') + idx = modname:find('.', 1, true) + end + + -- get the top-level module name + local topmod = idx and modname:sub(1, idx - 1) or modname + + -- OPTIM: search for a directory first when topmod == modname + local patterns = opts.patterns + or (topmod == modname and { '/init.lua', '.lua' } or { '.lua', '/init.lua' }) + for p, pattern in ipairs(patterns) do + patterns[p] = '/lua/' .. basename .. pattern + end + + ---@type ModuleInfo[] + local results = {} + + -- Only continue if we haven't found anything yet or we want to find all + ---@private + local function continue() + return #results == 0 or opts.all + end + + -- Checks if the given paths contain the top-level module. + -- If so, it tries to find the module path for the given module name. + ---@param paths string[] + ---@private + local function _find(paths) + for _, path in ipairs(paths) do + if topmod == '*' then + for _, r in pairs(Loader.lsmod(path)) do + results[#results + 1] = r + if not continue() then + return + end + end + elseif Loader.lsmod(path)[topmod] then + for _, pattern in ipairs(patterns) do + local modpath = path .. pattern + Loader._stats.find.stat = (Loader._stats.find.stat or 0) + 1 + local hash = uv.fs_stat(modpath) + if hash then + results[#results + 1] = { modpath = modpath, stat = hash, modname = modname } + if not continue() then + return + end + end + end + end + end + end + + -- always check the rtp first + if opts.rtp ~= false then + _find(Loader._rtp or {}) + if continue() then + local rtp, updated = Loader.get_rtp() + if updated then + _find(rtp) + end + end + end + + -- check any additional paths + if continue() and opts.paths then + _find(opts.paths) + end + + Loader.track('find', start) + if #results == 0 then + -- module not found + Loader._stats.find.not_found = Loader._stats.find.not_found + 1 + end + + return results +end + +--- Resets the topmods cache for the path, or all the paths +--- if path is nil. +---@param path string? path to reset +function M.reset(path) + if path then + Loader._indexed[Loader.normalize(path)] = nil + else + Loader._indexed = {} + end +end + +--- Enables the experimental Lua module loader: +--- * overrides loadfile +--- * adds the lua loader using the byte-compilation cache +--- * adds the libs loader +--- * removes the default Neovim loader +function M.enable() + if M.enabled then + return + end + M.enabled = true + vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p') + _G.loadfile = Loader.loadfile + -- add lua loader + table.insert(package.loaders, 2, Loader.loader) + -- add libs loader + table.insert(package.loaders, 3, Loader.loader_lib) + -- remove Neovim loader + for l, loader in ipairs(package.loaders) do + if loader == vim._load_package then + table.remove(package.loaders, l) + break + end + end + + -- this will reset the top-mods in case someone adds a new + -- top-level lua module to a path already on the rtp + vim.api.nvim_create_autocmd('BufWritePost', { + group = vim.api.nvim_create_augroup('cache_topmods_reset', { clear = true }), + callback = function(event) + local bufname = event.match ---@type string + local idx = bufname:find('/lua/', 1, true) + if idx then + M.reset(bufname:sub(1, idx - 1)) + end + end, + }) +end + +--- Disables the experimental Lua module loader: +--- * removes the loaders +--- * adds the default Neovim loader +function M.disable() + if not M.enabled then + return + end + M.enabled = false + _G.loadfile = Loader._loadfile + ---@diagnostic disable-next-line: no-unknown + for l, loader in ipairs(package.loaders) do + if loader == Loader.loader or loader == Loader.loader_lib then + table.remove(package.loaders, l) + end + end + table.insert(package.loaders, 2, vim._load_package) + vim.api.nvim_del_augroup_by_name('cache_topmods_reset') +end + +--- Return the top-level `/lua/*` modules for this path +---@param path string path to check for top-level lua modules +---@private +function Loader.lsmod(path) + if not Loader._indexed[path] then + local start = uv.hrtime() + Loader._indexed[path] = {} + local handle = vim.loop.fs_scandir(path .. '/lua') + while handle do + local name, t = vim.loop.fs_scandir_next(handle) + if not name then + break + end + local modpath = path .. '/lua/' .. name + -- HACK: type is not always returned due to a bug in luv + t = t or uv.fs_stat(modpath).type + ---@type string + local topname + local ext = name:sub(-4) + if ext == '.lua' or ext == '.dll' then + topname = name:sub(1, -5) + elseif name:sub(-3) == '.so' then + topname = name:sub(1, -4) + elseif t == 'link' or t == 'directory' then + topname = name + end + if topname then + Loader._indexed[path][topname] = { modpath = modpath, modname = topname } + Loader._topmods[topname] = Loader._topmods[topname] or {} + if not vim.tbl_contains(Loader._topmods[topname], path) then + table.insert(Loader._topmods[topname], path) + end + end + end + Loader.track('lsmod', start) + end + return Loader._indexed[path] +end + +--- Debug function that wrapps all loaders and tracks stats +---@private +function M._profile_loaders() + for l, loader in pairs(package.loaders) do + local loc = debug.getinfo(loader, 'Sn').source:sub(2) + package.loaders[l] = function(modname) + local start = vim.loop.hrtime() + local ret = loader(modname) + Loader.track('loader ' .. l .. ': ' .. loc, start) + Loader.track('loader_all', start) + return ret + end + end +end + +--- Prints all cache stats +---@param opts? {print?:boolean} +---@return LoaderStats +---@private +function M._inspect(opts) + if opts and opts.print then + ---@private + local function ms(nsec) + return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. 'ms' + end + local chunks = {} ---@type string[][] + ---@type string[] + local stats = vim.tbl_keys(Loader._stats) + table.sort(stats) + for _, stat in ipairs(stats) do + vim.list_extend(chunks, { + { '\n' .. stat .. '\n', 'Title' }, + { '* total: ' }, + { tostring(Loader._stats[stat].total) .. '\n', 'Number' }, + { '* time: ' }, + { ms(Loader._stats[stat].time) .. '\n', 'Bold' }, + { '* avg time: ' }, + { ms(Loader._stats[stat].time / Loader._stats[stat].total) .. '\n', 'Bold' }, + }) + for k, v in pairs(Loader._stats[stat]) do + if not vim.tbl_contains({ 'time', 'total' }, k) then + chunks[#chunks + 1] = { '* ' .. k .. ':' .. string.rep(' ', 9 - #k) } + chunks[#chunks + 1] = { tostring(v) .. '\n', 'Number' } + end + end + end + vim.api.nvim_echo(chunks, true, {}) + end + return Loader._stats +end + +return M -- cgit From 3c82ce0d625184a90e6b2dda96e38fcb44f901d2 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Mar 2023 12:46:24 +0100 Subject: refactor(loader): use vim.fs --- runtime/lua/vim/loader.lua | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 41d5579664..bafe766a5d 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -450,12 +450,7 @@ function Loader.lsmod(path) if not Loader._indexed[path] then local start = uv.hrtime() Loader._indexed[path] = {} - local handle = vim.loop.fs_scandir(path .. '/lua') - while handle do - local name, t = vim.loop.fs_scandir_next(handle) - if not name then - break - end + for name, t in vim.fs.dir(path .. '/lua') do local modpath = path .. '/lua/' .. name -- HACK: type is not always returned due to a bug in luv t = t or uv.fs_stat(modpath).type -- cgit From fab8dab6b6273767e2b31e4282cee5a11349ac8f Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Mar 2023 12:47:06 +0100 Subject: refactor(loader): remove BufWritePost autocmd --- runtime/lua/vim/loader.lua | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index bafe766a5d..3dc1bd9574 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -409,19 +409,6 @@ function M.enable() break end end - - -- this will reset the top-mods in case someone adds a new - -- top-level lua module to a path already on the rtp - vim.api.nvim_create_autocmd('BufWritePost', { - group = vim.api.nvim_create_augroup('cache_topmods_reset', { clear = true }), - callback = function(event) - local bufname = event.match ---@type string - local idx = bufname:find('/lua/', 1, true) - if idx then - M.reset(bufname:sub(1, idx - 1)) - end - end, - }) end --- Disables the experimental Lua module loader: @@ -440,7 +427,6 @@ function M.disable() end end table.insert(package.loaders, 2, vim._load_package) - vim.api.nvim_del_augroup_by_name('cache_topmods_reset') end --- Return the top-level `/lua/*` modules for this path -- cgit From ffd8cd1a96a96b09351a5ffde1068c3595443128 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Mar 2023 12:49:38 +0100 Subject: refactor(loader): add typing for package.loaders --- runtime/lua/vim/loader.lua | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 3dc1bd9574..19df7e4383 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -1,5 +1,8 @@ local uv = vim.loop +--- @type (fun(modename: string): fun()|string)[] +local loaders = package.loaders + local M = {} ---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number} @@ -399,13 +402,13 @@ function M.enable() vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p') _G.loadfile = Loader.loadfile -- add lua loader - table.insert(package.loaders, 2, Loader.loader) + table.insert(loaders, 2, Loader.loader) -- add libs loader - table.insert(package.loaders, 3, Loader.loader_lib) + table.insert(loaders, 3, Loader.loader_lib) -- remove Neovim loader - for l, loader in ipairs(package.loaders) do + for l, loader in ipairs(loaders) do if loader == vim._load_package then - table.remove(package.loaders, l) + table.remove(loaders, l) break end end @@ -420,13 +423,12 @@ function M.disable() end M.enabled = false _G.loadfile = Loader._loadfile - ---@diagnostic disable-next-line: no-unknown - for l, loader in ipairs(package.loaders) do + for l, loader in ipairs(loaders) do if loader == Loader.loader or loader == Loader.loader_lib then - table.remove(package.loaders, l) + table.remove(loaders, l) end end - table.insert(package.loaders, 2, vim._load_package) + table.insert(loaders, 2, vim._load_package) end --- Return the top-level `/lua/*` modules for this path @@ -466,9 +468,9 @@ end --- Debug function that wrapps all loaders and tracks stats ---@private function M._profile_loaders() - for l, loader in pairs(package.loaders) do + for l, loader in pairs(loaders) do local loc = debug.getinfo(loader, 'Sn').source:sub(2) - package.loaders[l] = function(modname) + loaders[l] = function(modname) local start = vim.loop.hrtime() local ret = loader(modname) Loader.track('loader ' .. l .. ': ' .. loc, start) -- cgit From 25fa051fa157ca4bce712e61602447a4222581ca Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Mar 2023 13:01:48 +0100 Subject: feat(vim.fs): improve normalize - Add options argument with an option to expand env vars - Resolve '//' -> '/' - Use in vim.loader --- runtime/lua/vim/loader.lua | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 19df7e4383..eccf95e2f2 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -49,19 +49,9 @@ function Loader.track(stat, start) Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start end ---- slightly faster/different version than vim.fs.normalize ---- we also need to have it here, since the loader will load vim.fs ---@private -function Loader.normalize(path) - if path:sub(1, 1) == '~' then - local home = vim.loop.os_homedir() or '~' - if home:sub(-1) == '\\' or home:sub(-1) == '/' then - home = home:sub(1, -2) - end - path = home .. path:sub(2) - end - path = path:gsub('\\', '/'):gsub('/+', '/') - return path:sub(-1) == '/' and path:sub(1, -2) or path +local function normalize(path) + return vim.fs.normalize(path, { expand_env = false }) end --- Gets the rtp excluding after directories. @@ -80,7 +70,7 @@ function Loader.get_rtp() if key ~= Loader._rtp_key then Loader._rtp = {} for _, path in ipairs(vim.api.nvim_get_runtime_file('', true)) do - path = Loader.normalize(path) + path = normalize(path) -- skip after directories if path:sub(-6, -1) ~= '/after' @@ -210,7 +200,7 @@ end -- luacheck: ignore 312 function Loader.loadfile(filename, mode, env, hash) local start = uv.hrtime() - filename = Loader.normalize(filename) + filename = normalize(filename) mode = nil -- ignore mode, since we byte-compile the lua source files local chunk, err = Loader.load(filename, { mode = mode, env = env, hash = hash }) Loader.track('loadfile', start) @@ -383,7 +373,7 @@ end ---@param path string? path to reset function M.reset(path) if path then - Loader._indexed[Loader.normalize(path)] = nil + Loader._indexed[normalize(path)] = nil else Loader._indexed = {} end -- cgit From fd70a9dca199aae1eb7714608d63e8b2e63fb293 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Mar 2023 13:23:34 +0100 Subject: refactor(loader): simplify tracking logic --- runtime/lua/vim/loader.lua | 74 +++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 44 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index eccf95e2f2..7b6b9cb563 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -41,14 +41,6 @@ local Loader = { }, } ---- Tracks the time spent in a function ----@private -function Loader.track(stat, start) - Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 } - Loader._stats[stat].total = Loader._stats[stat].total + 1 - Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start -end - ---@private local function normalize(path) return vim.fs.normalize(path, { expand_env = false }) @@ -60,9 +52,7 @@ end --- @return string[] rtp, boolean updated ---@private function Loader.get_rtp() - local start = uv.hrtime() if vim.in_fast_event() then - Loader.track('get_rtp', start) return (Loader._rtp or {}), false end local updated = false @@ -82,7 +72,6 @@ function Loader.get_rtp() updated = true Loader._rtp_key = key end - Loader.track('get_rtp', start) return Loader._rtp, updated end @@ -118,7 +107,6 @@ end ---@return CacheEntry? ---@private function Loader.read(name) - local start = uv.hrtime() local cname = Loader.cache_file(name) local f = uv.fs_open(cname, 'r', 438) if f then @@ -136,7 +124,6 @@ function Loader.read(name) if tonumber(header[1]) ~= Loader.VERSION then return end - Loader.track('read', start) return { hash = { size = tonumber(header[2]), @@ -145,7 +132,6 @@ function Loader.read(name) chunk = data:sub(zero + 1), } end - Loader.track('read', start) end --- The `package.loaders` loader for lua files using the cache. @@ -153,14 +139,11 @@ end ---@return string|function ---@private function Loader.loader(modname) - local start = uv.hrtime() local ret = M.find(modname)[1] if ret then local chunk, err = Loader.load(ret.modpath, { hash = ret.stat }) - Loader.track('loader', start) return chunk or error(err) end - Loader.track('loader', start) return '\ncache_loader: module ' .. modname .. ' not found' end @@ -169,7 +152,6 @@ end ---@return string|function ---@private function Loader.loader_lib(modname) - local start = uv.hrtime() local sysname = uv.os_uname().sysname:lower() or '' local is_win = sysname:find('win', 1, true) and not sysname:find('darwin', 1, true) local ret = M.find(modname, { patterns = is_win and { '.dll' } or { '.so' } })[1] @@ -183,10 +165,8 @@ function Loader.loader_lib(modname) local dash = modname:find('-', 1, true) local funcname = dash and modname:sub(dash + 1) or modname local chunk, err = package.loadlib(ret.modpath, 'luaopen_' .. funcname:gsub('%.', '_')) - Loader.track('loader_lib', start) return chunk or error(err) end - Loader.track('loader_lib', start) return '\ncache_loader_lib: module ' .. modname .. ' not found' end @@ -199,12 +179,9 @@ end ---@private -- luacheck: ignore 312 function Loader.loadfile(filename, mode, env, hash) - local start = uv.hrtime() - filename = normalize(filename) - mode = nil -- ignore mode, since we byte-compile the lua source files - local chunk, err = Loader.load(filename, { mode = mode, env = env, hash = hash }) - Loader.track('loadfile', start) - return chunk, err + -- ignore mode, since we byte-compile the lua source files + mode = nil + return Loader.load(normalize(filename), { mode = mode, env = env, hash = hash }) end --- Checks whether two cache hashes are the same based on: @@ -232,8 +209,6 @@ end ---@return function?, string? error_message ---@private function Loader.load(modpath, opts) - local start = uv.hrtime() - opts = opts or {} local hash = opts.hash or uv.fs_stat(modpath) ---@type function?, string? @@ -241,9 +216,7 @@ function Loader.load(modpath, opts) if not hash then -- trigger correct error - chunk, err = Loader._loadfile(modpath, opts.mode, opts.env) - Loader.track('load', start) - return chunk, err + return Loader._loadfile(modpath, opts.mode, opts.env) end local entry = Loader.read(modpath) @@ -251,7 +224,6 @@ function Loader.load(modpath, opts) -- found in cache and up to date chunk, err = load(entry.chunk --[[@as string]], '@' .. modpath, opts.mode, opts.env) if not (err and err:find('cannot load incompatible bytecode', 1, true)) then - Loader.track('load', start) return chunk, err end end @@ -262,7 +234,6 @@ function Loader.load(modpath, opts) entry.chunk = string.dump(chunk) Loader.write(modpath, entry) end - Loader.track('load', start) return chunk, err end @@ -280,7 +251,6 @@ end --- - modname: (string) the name of the module --- - stat: (table|nil) the fs_stat of the module path. Won't be returned for `modname="*"` function M.find(modname, opts) - local start = uv.hrtime() opts = opts or {} modname = modname:gsub('/', '.') @@ -359,7 +329,6 @@ function M.find(modname, opts) _find(opts.paths) end - Loader.track('find', start) if #results == 0 then -- module not found Loader._stats.find.not_found = Loader._stats.find.not_found + 1 @@ -426,7 +395,6 @@ end ---@private function Loader.lsmod(path) if not Loader._indexed[path] then - local start = uv.hrtime() Loader._indexed[path] = {} for name, t in vim.fs.dir(path .. '/lua') do local modpath = path .. '/lua/' .. name @@ -450,23 +418,41 @@ function Loader.lsmod(path) end end end - Loader.track('lsmod', start) end return Loader._indexed[path] end +--- Tracks the time spent in a function +--- @generic F: function +--- @param f F +--- @return F +--- @private +function Loader.track(stat, f) + return function(...) + local start = vim.loop.hrtime() + local r = { f(...) } + Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 } + Loader._stats[stat].total = Loader._stats[stat].total + 1 + Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start + return unpack(r, 1, table.maxn(r)) + end +end + +Loader.get_rtp = Loader.track('get_rtp', Loader.get_rtp) +Loader.read = Loader.track('read', Loader.read) +Loader.loader = Loader.track('loader', Loader.loader) +Loader.loader_lib = Loader.track('loader_lib', Loader.loader_lib) +Loader.loadfile = Loader.track('loadfile', Loader.loadfile) +Loader.load = Loader.track('load', Loader.load) +M.find = Loader.track('find', M.find) +Loader.lsmod = Loader.track('lsmod', Loader.lsmod) + --- Debug function that wrapps all loaders and tracks stats ---@private function M._profile_loaders() for l, loader in pairs(loaders) do local loc = debug.getinfo(loader, 'Sn').source:sub(2) - loaders[l] = function(modname) - local start = vim.loop.hrtime() - local ret = loader(modname) - Loader.track('loader ' .. l .. ': ' .. loc, start) - Loader.track('loader_all', start) - return ret - end + loaders[l] = Loader.track('loader ' .. l .. ': ' .. loc, loader) end end -- cgit From 4cff3aceeac2027d82080649ff1c8be9ffa87b90 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 31 Mar 2023 09:43:13 +0100 Subject: fix(loader): disable profiling by default --- runtime/lua/vim/loader.lua | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 7b6b9cb563..7ac7f44338 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -438,21 +438,27 @@ function Loader.track(stat, f) end end -Loader.get_rtp = Loader.track('get_rtp', Loader.get_rtp) -Loader.read = Loader.track('read', Loader.read) -Loader.loader = Loader.track('loader', Loader.loader) -Loader.loader_lib = Loader.track('loader_lib', Loader.loader_lib) -Loader.loadfile = Loader.track('loadfile', Loader.loadfile) -Loader.load = Loader.track('load', Loader.load) -M.find = Loader.track('find', M.find) -Loader.lsmod = Loader.track('lsmod', Loader.lsmod) +---@class ProfileOpts +---@field loaders? boolean Add profiling to the loaders --- Debug function that wrapps all loaders and tracks stats ---@private -function M._profile_loaders() - for l, loader in pairs(loaders) do - local loc = debug.getinfo(loader, 'Sn').source:sub(2) - loaders[l] = Loader.track('loader ' .. l .. ': ' .. loc, loader) +---@param opts ProfileOpts? +function M._profile(opts) + Loader.get_rtp = Loader.track('get_rtp', Loader.get_rtp) + Loader.read = Loader.track('read', Loader.read) + Loader.loader = Loader.track('loader', Loader.loader) + Loader.loader_lib = Loader.track('loader_lib', Loader.loader_lib) + Loader.loadfile = Loader.track('loadfile', Loader.loadfile) + Loader.load = Loader.track('load', Loader.load) + M.find = Loader.track('find', M.find) + Loader.lsmod = Loader.track('lsmod', Loader.lsmod) + + if opts and opts.loaders then + for l, loader in pairs(loaders) do + local loc = debug.getinfo(loader, 'Sn').source:sub(2) + loaders[l] = Loader.track('loader ' .. l .. ': ' .. loc, loader) + end end end -- cgit From 83bfd94d1df5eecb8e4069a227c7d24598636d63 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 31 Mar 2023 13:05:22 +0100 Subject: refactor(loader): cache hash information Whenever we run fs_stat() on a path, save this information in the loader so it can be re-used. - Loader.loadfile: Remove arguments `hash` as it is no longer needed. - Loader.loader: Use _G.loadstring instead of Loader.load This allows plugins to wrap loadstring to inspection and profiling - factor out read file logic --- runtime/lua/vim/loader.lua | 62 +++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 17 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 7ac7f44338..201de18497 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -5,7 +5,7 @@ local loaders = package.loaders local M = {} ----@alias CacheHash {mtime: {sec:number, nsec:number}, size:number} +---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number, type: string} ---@alias CacheEntry {hash:CacheHash, chunk:string} ---@class ModuleFindOpts @@ -32,6 +32,8 @@ local Loader = { VERSION = 3, ---@type table> _indexed = {}, + ---@type table + _hashes = {}, ---@type table _topmods = {}, _loadfile = loadfile, @@ -41,6 +43,18 @@ local Loader = { }, } +--- @param path string +--- @return uv.fs_stat.result +--- @private +function Loader.get_hash(path) + if not Loader._hashes[path] then + -- Note we must never save a stat for a non-existent path. + -- For non-existent paths fs_stat() will return nil. + Loader._hashes[path] = uv.fs_stat(path) + end + return Loader._hashes[path] +end + ---@private local function normalize(path) return vim.fs.normalize(path, { expand_env = false }) @@ -102,18 +116,28 @@ function Loader.write(name, entry) uv.fs_close(f) end +--- @param path string +--- @param mode integer +--- @return string? data +--- @private +local function readfile(path, mode) + local f = uv.fs_open(path, 'r', mode) + if f then + local hash = assert(uv.fs_fstat(f)) + local data = uv.fs_read(f, hash.size, 0) --[[@as string?]] + uv.fs_close(f) + return data + end +end + --- Loads the cache entry for a given module or file ---@param name string module name or filename ---@return CacheEntry? ---@private function Loader.read(name) local cname = Loader.cache_file(name) - local f = uv.fs_open(cname, 'r', 438) - if f then - local hash = uv.fs_fstat(f) --[[@as CacheHash]] - local data = uv.fs_read(f, hash.size, 0) --[[@as string]] - uv.fs_close(f) - + local data = readfile(cname, 438) + if data then local zero = data:find('\0', 1, true) if not zero then return @@ -141,7 +165,9 @@ end function Loader.loader(modname) local ret = M.find(modname)[1] if ret then - local chunk, err = Loader.load(ret.modpath, { hash = ret.stat }) + -- Make sure to call the global loadfile so we respect any augmentations done elsewhere. + -- E.g. profiling + local chunk, err = loadfile(ret.modpath) return chunk or error(err) end return '\ncache_loader: module ' .. modname .. ' not found' @@ -171,17 +197,17 @@ function Loader.loader_lib(modname) end --- `loadfile` using the cache +--- Note this has the mode and env arguments which is supported by LuaJIT and is 5.1 compatible. ---@param filename? string ---@param mode? "b"|"t"|"bt" ---@param env? table ----@param hash? CacheHash ---@return function?, string? error_message ---@private -- luacheck: ignore 312 -function Loader.loadfile(filename, mode, env, hash) +function Loader.loadfile(filename, mode, env) -- ignore mode, since we byte-compile the lua source files mode = nil - return Loader.load(normalize(filename), { mode = mode, env = env, hash = hash }) + return Loader.load(normalize(filename), { mode = mode, env = env }) end --- Checks whether two cache hashes are the same based on: @@ -201,8 +227,7 @@ end --- Loads the given module path using the cache ---@param modpath string ----@param opts? {hash?: CacheHash, mode?: "b"|"t"|"bt", env?:table} (table|nil) Options for loading the module: ---- - hash: (table) the hash of the file to load if it is already known. (defaults to `vim.loop.fs_stat({modpath})`) +---@param opts? {mode?: "b"|"t"|"bt", env?:table} (table|nil) Options for loading the module: --- - mode: (string) the mode to load the module with. "b"|"t"|"bt" (defaults to `nil`) --- - env: (table) the environment to load the module in. (defaults to `nil`) ---@see |luaL_loadfile()| @@ -210,7 +235,7 @@ end ---@private function Loader.load(modpath, opts) opts = opts or {} - local hash = opts.hash or uv.fs_stat(modpath) + local hash = Loader.get_hash(modpath) ---@type function?, string? local chunk, err @@ -301,7 +326,7 @@ function M.find(modname, opts) for _, pattern in ipairs(patterns) do local modpath = path .. pattern Loader._stats.find.stat = (Loader._stats.find.stat or 0) + 1 - local hash = uv.fs_stat(modpath) + local hash = Loader.get_hash(modpath) if hash then results[#results + 1] = { modpath = modpath, stat = hash, modname = modname } if not continue() then @@ -337,7 +362,7 @@ function M.find(modname, opts) return results end ---- Resets the topmods cache for the path, or all the paths +--- Resets the cache for the path, or all the paths --- if path is nil. ---@param path string? path to reset function M.reset(path) @@ -346,6 +371,9 @@ function M.reset(path) else Loader._indexed = {} end + + -- Path could be a directory so just clear all the hashes. + Loader._hashes = {} end --- Enables the experimental Lua module loader: @@ -399,7 +427,7 @@ function Loader.lsmod(path) for name, t in vim.fs.dir(path .. '/lua') do local modpath = path .. '/lua/' .. name -- HACK: type is not always returned due to a bug in luv - t = t or uv.fs_stat(modpath).type + t = t or Loader.get_hash(modpath).type ---@type string local topname local ext = name:sub(-4) -- cgit From a5c572bd446a89be2dccb2f7479ff1b017074640 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:07:33 +0200 Subject: docs: fix typos Co-authored-by: Gregory Anders Co-authored-by: Raphael Co-authored-by: C.D. MacEachern Co-authored-by: himanoa --- runtime/lua/vim/loader.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 201de18497..38b1e9fc0f 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -11,7 +11,7 @@ local M = {} ---@class ModuleFindOpts ---@field all? boolean Search for all matches (defaults to `false`) ---@field rtp? boolean Search for modname in the runtime path (defaults to `true`) ----@field patterns? string[] Paterns to use (defaults to `{"/init.lua", ".lua"}`) +---@field patterns? string[] Patterns to use (defaults to `{"/init.lua", ".lua"}`) ---@field paths? string[] Extra paths to search for modname ---@class ModuleInfo @@ -469,7 +469,7 @@ end ---@class ProfileOpts ---@field loaders? boolean Add profiling to the loaders ---- Debug function that wrapps all loaders and tracks stats +--- Debug function that wraps all loaders and tracks stats ---@private ---@param opts ProfileOpts? function M._profile(opts) -- cgit From 66c66d8db8ab5cb6d0c6d85d64556d7cf20b04fa Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 13 Apr 2023 17:34:47 +0100 Subject: fix(loader): reset hashes when running the loader --- runtime/lua/vim/loader.lua | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 38b1e9fc0f..7f953adc21 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -5,7 +5,7 @@ local loaders = package.loaders local M = {} ----@alias CacheHash {mtime: {sec:number, nsec:number}, size:number, type: string} +---@alias CacheHash {mtime: {nsec: integer, sec: integer}, size: integer, type?: uv.aliases.fs_stat_types} ---@alias CacheEntry {hash:CacheHash, chunk:string} ---@class ModuleFindOpts @@ -28,12 +28,11 @@ M.enabled = false ---@field _rtp string[] ---@field _rtp_pure string[] ---@field _rtp_key string +---@field _hashes? table local Loader = { VERSION = 3, ---@type table> _indexed = {}, - ---@type table - _hashes = {}, ---@type table _topmods = {}, _loadfile = loadfile, @@ -44,9 +43,13 @@ local Loader = { } --- @param path string ---- @return uv.fs_stat.result +--- @return CacheHash --- @private function Loader.get_hash(path) + if not Loader._hashes then + return uv.fs_stat(path) --[[@as CacheHash]] + end + if not Loader._hashes[path] then -- Note we must never save a stat for a non-existent path. -- For non-existent paths fs_stat() will return nil. @@ -163,13 +166,16 @@ end ---@return string|function ---@private function Loader.loader(modname) + Loader._hashes = {} local ret = M.find(modname)[1] if ret then -- Make sure to call the global loadfile so we respect any augmentations done elsewhere. -- E.g. profiling local chunk, err = loadfile(ret.modpath) + Loader._hashes = nil return chunk or error(err) end + Loader._hashes = nil return '\ncache_loader: module ' .. modname .. ' not found' end @@ -373,7 +379,9 @@ function M.reset(path) end -- Path could be a directory so just clear all the hashes. - Loader._hashes = {} + if Loader._hashes then + Loader._hashes = {} + end end --- Enables the experimental Lua module loader: -- cgit From 4d04feb6629cb049cb2a13ba35f0c8d3c6b67ff4 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 14 Apr 2023 10:39:57 +0200 Subject: feat(lua): vim.tbl_contains supports general tables and predicates (#23040) * feat(lua): vim.tbl_contains supports general tables and predicates Problem: `vim.tbl_contains` only works for list-like tables (integer keys without gaps) and primitive values (in particular, not for nested tables). Solution: Rename `vim.tbl_contains` to `vim.list_contains` and add new `vim.tbl_contains` that works for general tables and optionally allows `value` to be a predicate function that is checked for every key. --- runtime/lua/vim/loader.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 7f953adc21..66627fe4e7 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -449,7 +449,7 @@ function Loader.lsmod(path) if topname then Loader._indexed[path][topname] = { modpath = modpath, modname = topname } Loader._topmods[topname] = Loader._topmods[topname] or {} - if not vim.tbl_contains(Loader._topmods[topname], path) then + if not vim.list_contains(Loader._topmods[topname], path) then table.insert(Loader._topmods[topname], path) end end @@ -523,7 +523,7 @@ function M._inspect(opts) { ms(Loader._stats[stat].time / Loader._stats[stat].total) .. '\n', 'Bold' }, }) for k, v in pairs(Loader._stats[stat]) do - if not vim.tbl_contains({ 'time', 'total' }, k) then + if not vim.list_contains({ 'time', 'total' }, k) then chunks[#chunks + 1] = { '* ' .. k .. ':' .. string.rep(' ', 9 - #k) } chunks[#chunks + 1] = { tostring(v) .. '\n', 'Number' } end -- cgit From 2db719f6c2b677fcbc197b02fe52764a851523b2 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 3 Jun 2023 11:06:00 +0100 Subject: feat(lua): rename vim.loop -> vim.uv (#22846) --- runtime/lua/vim/loader.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 66627fe4e7..9024bdb231 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -1,4 +1,4 @@ -local uv = vim.loop +local uv = vim.uv --- @type (fun(modename: string): fun()|string)[] local loaders = package.loaders @@ -465,7 +465,7 @@ end --- @private function Loader.track(stat, f) return function(...) - local start = vim.loop.hrtime() + local start = vim.uv.hrtime() local r = { f(...) } Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 } Loader._stats[stat].total = Loader._stats[stat].total + 1 -- cgit From 4e6356559c8cd44dbcaa765d1f39e176064526ec Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 22 Jun 2023 03:44:51 -0700 Subject: test: spellcheck :help (vimdoc) files #24109 Enforce consistent terminology (defined in `gen_help_html.lua:spell_dict`) for common misspellings. This does not spellcheck English in general (perhaps a future TODO, though it may be noisy). --- runtime/lua/vim/loader.lua | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 9024bdb231..f340dc85b5 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -161,7 +161,7 @@ function Loader.read(name) end end ---- The `package.loaders` loader for lua files using the cache. +--- The `package.loaders` loader for Lua files using the cache. ---@param modname string module name ---@return string|function ---@private @@ -211,7 +211,7 @@ end ---@private -- luacheck: ignore 312 function Loader.loadfile(filename, mode, env) - -- ignore mode, since we byte-compile the lua source files + -- ignore mode, since we byte-compile the Lua source files mode = nil return Loader.load(normalize(filename), { mode = mode, env = env }) end @@ -268,7 +268,7 @@ function Loader.load(modpath, opts) return chunk, err end ---- Finds lua modules for the given module name. +--- Finds Lua modules for the given module name. ---@param modname string Module name, or `"*"` to find the top-level modules instead ---@param opts? ModuleFindOpts (table|nil) Options for finding a module: --- - rtp: (boolean) Search for modname in the runtime path (defaults to `true`) @@ -289,7 +289,7 @@ function M.find(modname, opts) local idx = modname:find('.', 1, true) -- HACK: fix incorrect require statements. Really not a fan of keeping this, - -- but apparently the regular lua loader also allows this + -- but apparently the regular Lua loader also allows this if idx == 1 then modname = modname:gsub('^%.+', '') basename = modname:gsub('%.', '/') @@ -386,9 +386,9 @@ end --- Enables the experimental Lua module loader: --- * overrides loadfile ---- * adds the lua loader using the byte-compilation cache +--- * adds the Lua loader using the byte-compilation cache --- * adds the libs loader ---- * removes the default Neovim loader +--- * removes the default Nvim loader function M.enable() if M.enabled then return @@ -396,11 +396,11 @@ function M.enable() M.enabled = true vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p') _G.loadfile = Loader.loadfile - -- add lua loader + -- add Lua loader table.insert(loaders, 2, Loader.loader) -- add libs loader table.insert(loaders, 3, Loader.loader_lib) - -- remove Neovim loader + -- remove Nvim loader for l, loader in ipairs(loaders) do if loader == vim._load_package then table.remove(loaders, l) @@ -411,7 +411,7 @@ end --- Disables the experimental Lua module loader: --- * removes the loaders ---- * adds the default Neovim loader +--- * adds the default Nvim loader function M.disable() if not M.enabled then return @@ -426,8 +426,8 @@ function M.disable() table.insert(loaders, 2, vim._load_package) end ---- Return the top-level `/lua/*` modules for this path ----@param path string path to check for top-level lua modules +--- Return the top-level \`/lua/*` modules for this path +---@param path string path to check for top-level Lua modules ---@private function Loader.lsmod(path) if not Loader._indexed[path] then -- cgit From be74807eef13ff8c90d55cf8b22b01d6d33b1641 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 18 Jul 2023 15:42:30 +0100 Subject: docs(lua): more improvements (#24387) * docs(lua): teach lua2dox how to table * docs(lua): teach gen_vimdoc.py about local functions No more need to mark local functions with @private * docs(lua): mention @nodoc and @meta in dev-lua-doc * fixup! Co-authored-by: Justin M. Keyes --------- Co-authored-by: Justin M. Keyes --- runtime/lua/vim/loader.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index f340dc85b5..4f4722b0c3 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -21,7 +21,10 @@ local M = {} ---@alias LoaderStats table +---@nodoc M.path = vim.fn.stdpath('cache') .. '/luac' + +---@nodoc M.enabled = false ---@class Loader @@ -58,7 +61,6 @@ function Loader.get_hash(path) return Loader._hashes[path] end ----@private local function normalize(path) return vim.fs.normalize(path, { expand_env = false }) end @@ -122,7 +124,6 @@ end --- @param path string --- @param mode integer --- @return string? data ---- @private local function readfile(path, mode) local f = uv.fs_open(path, 'r', mode) if f then @@ -310,7 +311,6 @@ function M.find(modname, opts) local results = {} -- Only continue if we haven't found anything yet or we want to find all - ---@private local function continue() return #results == 0 or opts.all end @@ -318,7 +318,6 @@ function M.find(modname, opts) -- Checks if the given paths contain the top-level module. -- If so, it tries to find the module path for the given module name. ---@param paths string[] - ---@private local function _find(paths) for _, path in ipairs(paths) do if topmod == '*' then @@ -504,7 +503,6 @@ end ---@private function M._inspect(opts) if opts and opts.print then - ---@private local function ms(nsec) return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. 'ms' end -- cgit From 0804034c07ad5883bc653d054e549a87d429a8b7 Mon Sep 17 00:00:00 2001 From: Tyler Miller Date: Tue, 1 Aug 2023 08:28:28 -0700 Subject: fix(loader): cache path ambiguity #24491 Problem: cache paths are derived by replacing each reserved/filesystem- path-sensitive char with a `%` char in the original path. With this method, two different files at two different paths (each containing `%` chars) can erroneously resolve to the very same cache path in certain edge-cases. Solution: derive cache paths by url-encoding the original (path) instead using `vim.uri_encode()` with `"rfc2396"`. Increment `Loader.VERSION` to denote this change. --- runtime/lua/vim/loader.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 4f4722b0c3..e08ccba701 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -1,4 +1,5 @@ local uv = vim.uv +local uri_encode = vim.uri_encode --- @type (fun(modename: string): fun()|string)[] local loaders = package.loaders @@ -33,7 +34,7 @@ M.enabled = false ---@field _rtp_key string ---@field _hashes? table local Loader = { - VERSION = 3, + VERSION = 4, ---@type table> _indexed = {}, ---@type table @@ -99,7 +100,7 @@ end ---@return string file_name ---@private function Loader.cache_file(name) - local ret = M.path .. '/' .. name:gsub('[/\\:]', '%%') + local ret = ('%s/%s'):format(M.path, uri_encode(name, 'rfc2396')) return ret:sub(-4) == '.lua' and (ret .. 'c') or (ret .. '.luac') end -- cgit From c43c745a14dced87a23227d7be4f1c33d4455193 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 9 Aug 2023 11:06:13 +0200 Subject: fix(lua): improve annotations for stricter luals diagnostics (#24609) Problem: luals returns stricter diagnostics with bundled luarc.json Solution: Improve some function and type annotations: * use recognized uv.* types * disable diagnostic for global `vim` in shared.lua * docs: don't start comment lines with taglink (otherwise LuaLS will interpret it as a type) * add type alias for lpeg pattern * fix return annotation for `vim.secure.trust` * rename local Range object in vim.version (shadows `Range` in vim.treesitter) * fix some "missing fields" warnings * add missing required fields for test functions in eval.lua * rename lsp meta files for consistency --- runtime/lua/vim/loader.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/loader.lua') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index e08ccba701..ee01111337 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -18,7 +18,7 @@ local M = {} ---@class ModuleInfo ---@field modpath string Path of the module ---@field modname string Name of the module ----@field stat? uv_fs_t File stat of the module path +---@field stat? uv.uv_fs_t File stat of the module path ---@alias LoaderStats table -- cgit