diff --git a/lua/lazy/core/cache.lua b/lua/lazy/core/cache.lua index 374429f..a5af080 100644 --- a/lua/lazy/core/cache.lua +++ b/lua/lazy/core/cache.lua @@ -12,6 +12,7 @@ local used = {} ---@type table local cache = {} +---@return string? function M.get(key) if cache[key] then used[key] = true diff --git a/lua/lazy/core/config.lua b/lua/lazy/core/config.lua index 3f7bb9d..084105e 100644 --- a/lua/lazy/core/config.lua +++ b/lua/lazy/core/config.lua @@ -8,6 +8,7 @@ M.defaults = { plugins = "config.plugins", plugins_local = { path = vim.fn.expand("~/projects"), + ---@type string[] patterns = {}, }, package_path = vim.fn.stdpath("data") .. "/site/pack/lazy", @@ -27,15 +28,27 @@ M.defaults = { M.ns = vim.api.nvim_create_namespace("lazy") +M.paths = { + ---@type string + main = nil, + ---@type string + plugins = nil, +} + ---@type table M.plugins = {} +---@type LazyPlugin[] +M.to_clean = {} + ---@type LazyConfig M.options = {} ---@param opts? LazyConfig function M.setup(opts) M.options = vim.tbl_deep_extend("force", M.defaults, opts or {}) + M.paths.plugins = vim.fn.stdpath("config") .. "/lua/" .. M.options.plugins:gsub("%.", "/") + M.paths.main = M.paths.plugins .. (vim.loop.fs_stat(M.paths.plugins .. ".lua") and ".lua" or "/init.lua") -- vim.fn.mkdir(M.options.package_path, "p") diff --git a/lua/lazy/core/loader.lua b/lua/lazy/core/loader.lua index d87c232..19559d7 100644 --- a/lua/lazy/core/loader.lua +++ b/lua/lazy/core/loader.lua @@ -20,11 +20,12 @@ M.loading = {} ---@param plugin LazyPlugin function M.add(plugin) - if plugin.init or (plugin.opt == false and plugin.config) then + if plugin.init or (plugin.opt == false) then table.insert(M.loaders.init, plugin.name) end for _, loader_type in ipairs(M.types) do + ---@type (string|string[])? local loaders = plugin[loader_type] if plugin[loader_type] then loaders = type(loaders) == "table" and loaders or { loaders } @@ -198,7 +199,8 @@ end ---@param plugins string|LazyPlugin|string[]|LazyPlugin[] ---@param reason {[string]:string} -function M.load(plugins, reason) +---@param opts? {load_start: boolean} +function M.load(plugins, reason, opts) if type(plugins) == "string" or plugins.name then ---@diagnostic disable-next-line: assign-type-mismatch plugins = { plugins } @@ -211,6 +213,7 @@ function M.load(plugins, reason) end if not plugin.loaded then + ---@diagnostic disable-next-line: assign-type-mismatch plugin.loaded = {} for k, v in pairs(reason) do plugin.loaded[k] = v @@ -222,7 +225,7 @@ function M.load(plugins, reason) table.insert(M.loading, plugin) Util.track(plugin.name) - M.packadd(plugin) + M.packadd(plugin, opts and opts.load_start) if plugin.requires then M.load(plugin.requires, {}) @@ -242,11 +245,11 @@ function M.load(plugins, reason) end ---@param plugin LazyPlugin -function M.packadd(plugin) +function M.packadd(plugin, load_start) if plugin.opt then vim.cmd.packadd(plugin.pack) M.source_plugin_files(plugin, true) - else + elseif load_start then vim.opt.runtimepath:append(plugin.dir) M.source_plugin_files(plugin) M.source_plugin_files(plugin, true) @@ -256,16 +259,12 @@ end ---@param plugin LazyPlugin ---@param after? boolean function M.source_plugin_files(plugin, after) - local pattern = (after and "/after" or "") .. ("/plugin/" .. "**/*.\\(vim\\|lua\\)") - - local _, entries = pcall(vim.fn.glob, plugin.dir .. "/" .. pattern, false, true) - - if entries then - ---@cast entries string[] - for _, file in ipairs(entries) do - vim.cmd("silent source " .. file) + Util.walk(plugin.dir .. (after and "/after" or "") .. "/plugin", function(path, _, t) + local ext = path:sub(-3) + if t == "file" and (ext == "lua" or ext == "vim") then + vim.cmd("silent source " .. path) end - end + end) end return M diff --git a/lua/lazy/core/module.lua b/lua/lazy/core/module.lua index 2238238..599c512 100644 --- a/lua/lazy/core/module.lua +++ b/lua/lazy/core/module.lua @@ -2,132 +2,72 @@ local Cache = require("lazy.core.cache") local M = {} ----@type table -M.modules = {} +---@type table +M.hashes = {} -function M.add(modname, file) - if not M.modules[modname] then - M.modules[modname] = { file = file } - end +function M.is_dirty(modname, modpath) + return not (Cache.get(modname) and M.hashes[modname] and M.hashes[modname] == Cache.hash(modpath)) end ---@param modname string -function M.load(modname) - if type(package.loaded[modname]) == "table" then +---@param modpath string +---@return table +function M.load(modname, modpath) + local err + ---@type (string|fun())? + local chunk = Cache.get(modname) + + if chunk then + local hash = Cache.hash(modpath) + if hash ~= M.hashes[modname] then + M.hashes[modname] = hash + chunk = nil + end + end + + if chunk then + chunk, err = loadstring(chunk --[[@as string]], "@" .. modpath) + else + vim.schedule(function() + vim.notify("loadfile(" .. modname .. ")") + end) + chunk, err = loadfile(modpath) + if chunk then + Cache.set(modname, string.dump(chunk)) + M.hashes[modname] = M.hashes[modname] or Cache.hash(modpath) + end + end + + if chunk then + ---@diagnostic disable-next-line: no-unknown + package.loaded[modname] = chunk() return package.loaded[modname] + else + error(err) end - - local info = M.modules[modname] - if info then - local err - ---@type string|fun()|nil - local chunk = Cache.get(modname) - - if not chunk then - vim.schedule(function() - vim.notify("loading " .. modname) - end) - chunk, err = loadfile(info.file) - if chunk then - Cache.set(modname, string.dump(chunk)) - info.hash = info.hash or Cache.hash(info.file) - end - end - - if type(chunk) == "string" then - chunk, err = loadstring(chunk --[[@as string]], "@" .. info.file) - end - - if not chunk then - error(err) - end - - ---@type table - local mod = chunk() - package.loaded[modname] = mod - return mod - end -end - -local function _add_module(dir, modname) - local d = vim.loop.fs_opendir(dir, nil, 100) - if d then - ---@type {name: string, type: "file"|"directory"|"link"}[] - local entries = vim.loop.fs_readdir(d) - while entries do - for _, entry in ipairs(entries) do - local path = dir .. "/" .. entry.name - if entry.type == "directory" then - _add_module(path, modname and (modname .. "." .. entry.name) or entry.name) - else - local childname = entry.name:match("^(.*)%.lua$") - if childname then - local child = entry.name == "init.lua" and modname or modname and (modname .. "." .. childname) or childname - if child then - M.add(child, path) - end - end - end - end - entries = vim.loop.fs_readdir(d) - end - vim.loop.fs_closedir(d) - end -end - -function M.add_module(path) - if path:find("/lua/?$") then - return _add_module(path) - end - ---@type string - local modname = path:match("/lua/(.*)/?") - assert(modname) - modname = modname:gsub("/", ".") - if vim.loop.fs_stat(path .. ".lua") then - M.add(modname, path .. ".lua") - end - _add_module(path, modname) end function M.setup() -- load cache local value = Cache.get("cache.modules") if value then - M.modules = vim.json.decode(value) - for k, v in pairs(M.modules) do - if Cache.hash(v.file) ~= v.hash then - Cache.del(k) - M.changed = true - M.modules[k] = nil - end - end + M.hashes = vim.json.decode(value) end -- preload core modules local root = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":p:h:h") for _, name in ipairs({ "util", "config", "loader", "state" }) do local modname = "lazy.core." .. name - M.add(modname, root .. "/core/" .. name:gsub("%.", "/") .. ".lua") - end - - table.insert(package.loaders, 2, function(modname) - if M.modules[modname] then - return function() - return M.load(modname) - end + ---@diagnostic disable-next-line: no-unknown + package.preload[modname] = function() + return M.load(modname, root .. "/core/" .. name:gsub("%.", "/") .. ".lua") end - end) + end return M end function M.save() - local value = {} - for k, v in pairs(M.modules) do - if v.hash then - value[k] = v - end - end - Cache.set("cache.modules", vim.json.encode(value)) + Cache.set("cache.modules", vim.json.encode(M.hashes)) end return M diff --git a/lua/lazy/core/state.lua b/lua/lazy/core/state.lua index 36ab151..84b76d1 100644 --- a/lua/lazy/core/state.lua +++ b/lua/lazy/core/state.lua @@ -3,9 +3,43 @@ local Module = require("lazy.core.module") local M = {} -M.functions = { "init", "config", "run" } M.dirty = true +function M.update_state(check_clean) + local Util = require("lazy.core.util") + local Config = require("lazy.core.config") + ---@type table<"opt"|"start", table> + local installed = { opt = {}, start = {} } + for opt, packs in pairs(installed) do + Util.scandir(Config.options.package_path .. "/" .. opt, function(_, name, type) + if type == "directory" or type == "link" then + packs[name] = true + end + end) + end + + for _, plugin in pairs(Config.plugins) do + local opt = plugin.opt and "opt" or "start" + plugin.installed = installed[opt][plugin.pack] == true + installed[opt][plugin.pack] = nil + end + + if check_clean then + Config.to_clean = {} + for opt, packs in pairs(installed) do + for pack in pairs(packs) do + table.insert(Config.to_clean, { + name = pack, + pack = pack, + dir = Config.options.package_path .. "/" .. opt .. "/" .. pack, + opt = opt == "opt", + installed = true, + }) + end + end + end +end + function M.save() if not M.dirty then return @@ -14,29 +48,30 @@ function M.save() ---@class LazyState local state = { - ---@type LazyPlugin[] + ---@type CachedPlugin[] plugins = {}, loaders = require("lazy.core.loader").loaders, config = Config.options, } + ---@alias CachedPlugin LazyPlugin | {_funcs: table} local skip = { installed = true, loaded = true, tasks = true, dirty = true, dir = true } local funcount = 0 for _, plugin in pairs(Config.plugins) do - ---@type LazyPlugin | {_chunks: string[] | table} + ---@type CachedPlugin local save = {} table.insert(state.plugins, save) + ---@diagnostic disable-next-line: no-unknown for k, v in pairs(plugin) do if type(v) == "function" then - if vim.tbl_contains(M.functions, k) then - if plugin.modname then - save[k] = true - else - funcount = funcount + 1 - Cache.set("cache.state.fun." .. funcount, string.dump(v)) - save[k] = funcount - end + save._funcs = save._funcs or {} + if plugin.modname then + save._funcs[k] = true + else + funcount = funcount + 1 + Cache.set("cache.state.fun." .. funcount, string.dump(v)) + save._funcs[k] = funcount end elseif not skip[k] then save[k] = v @@ -46,16 +81,6 @@ function M.save() Cache.set("cache.state", vim.json.encode(state)) end -local function load_plugin(plugin, fun, ...) - local mod = Module.load(plugin.modname) - for k, v in pairs(mod) do - if type(v) == "function" then - plugin[k] = v - end - end - return mod[fun](...) -end - function M.load() ---@type boolean, LazyState local ok, state = pcall(vim.json.decode, Cache.get("cache.state")) @@ -64,7 +89,6 @@ function M.load() return false end - local Util = require("lazy.core.util") local Config = require("lazy.core.config") if not vim.deep_equal(Config.options, state.config) then @@ -72,50 +96,45 @@ function M.load() return false end - -- Check for installed plugins - ---@type table<"opt"|"start", table> - local installed = { opt = {}, start = {} } - for opt, packs in pairs(installed) do - for _, entry in ipairs(Util.scandir(Config.options.package_path .. "/" .. opt)) do - if entry.type == "directory" or entry.type == "link" then - packs[entry.name] = true - end - end + if Module.is_dirty(Config.options.plugins, Config.paths.main) then + return false end -- plugins for _, plugin in ipairs(state.plugins) do - ---@cast plugin LazyPlugin|{_chunks:table} Config.plugins[plugin.name] = plugin plugin.loaded = nil plugin.dir = Config.options.package_path .. "/" .. (plugin.opt and "opt" or "start") .. "/" .. plugin.pack - plugin.installed = installed[plugin.opt and "opt" or "start"][plugin.pack] if plugin.modname then - -- mark module as used - assert(Cache.get(plugin.modname)) - for _, fun in ipairs(M.functions) do - if plugin[fun] == true then - plugin[fun] = function(...) - return load_plugin(plugin, fun, ...) + if Module.is_dirty(plugin.modname, plugin.modpath) then + return false + end + for fun in pairs(plugin._funcs or {}) do + ---@diagnostic disable-next-line: assign-type-mismatch + plugin[fun] = function(...) + local mod = Module.load(plugin.modname, plugin.modpath) + for k in pairs(plugin._funcs) do + plugin[k] = mod[k] end + return plugin[fun](...) end end - else - for _, fun in ipairs(M.functions) do - if type(plugin[fun]) == "number" then - local chunk = assert(Cache.get("cache.state.fun." .. plugin[fun])) - plugin[fun] = function(...) - plugin[fun] = loadstring(chunk) - return plugin[fun](...) - end + elseif plugin._funcs then + for fun, id in pairs(plugin._funcs) do + local chunk = assert(Cache.get("cache.state.fun." .. id)) + ---@diagnostic disable-next-line: assign-type-mismatch + plugin[fun] = function(...) + ---@diagnostic disable-next-line: assign-type-mismatch + plugin[fun] = loadstring(chunk) + return plugin[fun](...) end end end end + M.update_state() -- loaders - local Loader = require("lazy.core.loader") - Loader.loaders = state.loaders + require("lazy.core.loader").loaders = state.loaders M.dirty = false diff --git a/lua/lazy/init.lua b/lua/lazy/init.lua index db147a7..2b18fbc 100644 --- a/lua/lazy/init.lua +++ b/lua/lazy/init.lua @@ -16,7 +16,8 @@ function M.setup(opts) require("lazy.core.cache").setup() local module_start = vim.loop.hrtime() - local Module = require("lazy.core.module").setup() + + require("lazy.core.module").setup() local require_start = vim.loop.hrtime() local Util = require("lazy.core.util") @@ -35,36 +36,27 @@ function M.setup(opts) Util.track() Util.track("state") - if Module.changed or not State.load() then - -- rebuild state + if not State.load() then local Plugin = require("lazy.plugin") - Module.add_module(vim.fn.stdpath("config") .. "/lua/" .. Config.options.plugins:gsub("%.", "/")) - -- Module.add_module(vim.fn.stdpath("config") .. "/lua") - -- Module.add_module(Config.options.package_path .. "/start/tokyonight.nvim/lua") - -- Module.add_module(Config.options.package_path .. "/opt/nvim-cmp/lua") - -- Module.add_module(Config.options.package_path .. "/opt/cmp-buffer/lua") vim.schedule(function() Util.info("Reloading...") end) - Util.track("normalize") - Plugin.normalize(require(Config.options.plugins)) - if not Config.plugins.lazy then - Plugin.plugin({ - "folke/lazy.nvim", - opt = false, - }) - end - Util.track() - - Util.track("process") - Plugin.process() + Util.track("reload") + Plugin.reload() Util.track() + -- if not Config.plugins.lazy then + -- Plugin.plugin({ + -- "folke/lazy.nvim", + -- opt = false, + -- }) + -- end end Util.track() Util.track("install") for _, plugin in pairs(Config.plugins) do if not plugin.installed then + vim.cmd("do User LazyInstallPre") require("lazy.manager").install({ wait = true, }) @@ -79,7 +71,11 @@ function M.setup(opts) Util.track() -- end setup + local lazy_delta = vim.loop.hrtime() - cache_start + Loader.init_plugins() + + Config.plugins.lazy.loaded.time = lazy_delta done = true vim.cmd("do User LazyDone") diff --git a/lua/lazy/manager.lua b/lua/lazy/manager.lua index 291ec0b..029aed4 100644 --- a/lua/lazy/manager.lua +++ b/lua/lazy/manager.lua @@ -1,13 +1,10 @@ local Config = require("lazy.core.config") local Task = require("lazy.task") local Runner = require("lazy.runner") -local Util = require("lazy.core.util") +local State = require("lazy.core.state") local M = {} ----@type table -M.to_clean = {} - ---@alias ManagerOpts {wait?: boolean, plugins?: LazyPlugin[], clear?: boolean, show?: boolean} ---@param operation TaskType @@ -115,38 +112,11 @@ end ---@param opts? ManagerOpts function M.clean(opts) opts = opts or {} - M.check_clean() - opts.plugins = vim.tbl_values(M.to_clean) + State.update_state(true) + opts.plugins = vim.tbl_values(Config.to_clean) M.run("clean", opts) end -function M.check_clean() - ---@type table - local packs = {} - for _, plugin in pairs(Config.plugins) do - packs[plugin.pack] = plugin.opt - end - - for _, opt in ipairs({ "opt", "start" }) do - local site = Config.options.package_path .. "/" .. opt - if Util.file_exists(site) then - for _, pack in ipairs(Util.scandir(site)) do - if packs[pack.name] ~= (opt == "opt") then - ---@type LazyPlugin - local plugin = { - name = pack.name, - pack = pack.name, - dir = site .. "/" .. pack.name, - opt = opt == "opt", - installed = true, - } - M.to_clean[plugin.dir] = plugin - end - end - end - end -end - function M.clear() for _, plugin in pairs(Config.plugins) do -- clear updated status @@ -159,7 +129,6 @@ function M.clear() end, plugin.tasks) end end - M.to_clean = {} vim.cmd([[do User LazyRender]]) end diff --git a/lua/lazy/plugin.lua b/lua/lazy/plugin.lua index fdeeb30..6d58bf2 100644 --- a/lua/lazy/plugin.lua +++ b/lua/lazy/plugin.lua @@ -1,15 +1,19 @@ local Config = require("lazy.core.config") -local Util = require("lazy.core.util") +local Util = require("lazy.util") local Module = require("lazy.core.module") +local State = require("lazy.core.state") local M = {} +M.funcs = { run = "run", init = "init", config = "config" } + ---@class LazyPlugin ---@field [1] string ---@field name string display name and name used for plugin config files ---@field pack string package name ---@field uri string ---@field modname? string +---@field modpath? string ---@field branch? string ---@field dir string ---@field opt? boolean @@ -72,26 +76,52 @@ function M.process_local(plugin) end end ----@param plugin LazyPlugin -function M.process_config(plugin) - local name = plugin.name - local modname = Config.options.plugins .. "." .. name - - local spec = Module.load(modname) - if spec then - -- add to loaded modules - if spec.requires then - spec.requires = M.normalize(spec.requires) +function M.process_config() + Util.lsmod(Config.paths.plugins, function(name, modpath) + local plugin = Config.plugins[name] + if plugin then + local modname = Config.options.plugins .. "." .. name + local ok, spec = pcall(Module.load, modname, modpath) + if ok and spec then + ---@diagnostic disable-next-line: no-unknown + for k, v in pairs(spec) do + if k == "requires" then + plugin.requires = M.normalize(v) + elseif type(v) ~= "function" or M.funcs[k] then + ---@diagnostic disable-next-line: no-unknown + plugin[k] = v + end + end + plugin.modname = modname + plugin.modpath = modpath + M.plugin(plugin) + else + Util.error("Failed to load " .. modname .. "\n" .. spec) + end end + end) +end - ---@diagnostic disable-next-line: no-unknown - for k, v in pairs(spec) do - ---@diagnostic disable-next-line: no-unknown - plugin[k] = v - end - plugin.modname = modname - M.plugin(plugin) +function M.reload() + Config.plugins = {} + M.normalize(assert(Module.load(Config.options.plugins, Config.paths.main))) + + if not Config.plugins.lazy then + M.plugin({ + "folke/lazy.nvim", + opt = false, + }) end + + M.process_config() + for _, plugin in pairs(Config.plugins) do + if plugin.opt == nil then + plugin.opt = Config.options.opt + end + plugin.dir = Config.options.package_path .. "/" .. (plugin.opt and "opt" or "start") .. "/" .. plugin.pack + M.process_local(plugin) + end + State.update_state() end ---@param spec table @@ -108,9 +138,7 @@ function M.normalize(spec, results) else ---@cast spec LazyPlugin spec = M.plugin(spec) - if spec.requires then - -- TODO: fix multiple requires in different packages spec.requires = M.normalize(spec.requires) end table.insert(results, spec.name) @@ -118,19 +146,6 @@ function M.normalize(spec, results) return results end -function M.process() - for _, plugin in pairs(Config.plugins) do - M.process_config(plugin) - end - - for _, plugin in pairs(Config.plugins) do - if plugin.opt == nil then - plugin.opt = Config.options.opt - end - plugin.dir = Config.options.package_path .. "/" .. (plugin.opt and "opt" or "start") .. "/" .. plugin.pack - plugin.installed = Util.file_exists(plugin.dir) - M.process_local(plugin) - end -end +-- profile(M.rebuild, 1000, true) return M diff --git a/lua/lazy/task.lua b/lua/lazy/task.lua index 79a9b2a..7d9dbb5 100644 --- a/lua/lazy/task.lua +++ b/lua/lazy/task.lua @@ -1,6 +1,6 @@ local Process = require("lazy.process") local Loader = require("lazy.core.loader") -local Util = require("lazy.core.util") +local Util = require("lazy.util") ---@class LazyTask ---@field plugin LazyPlugin @@ -45,23 +45,18 @@ function Task:_done() end function Task:clean() - local function rm(path) - for _, entry in ipairs(Util.scandir(path)) do - if entry.type == "directory" then - rm(entry.path) - else - vim.loop.fs_unlink(entry.path) - end - end - vim.loop.fs_rmdir(path) - end - local dir = self.plugin.dir:gsub("/+$", "") - local stat = vim.loop.fs_lstat(dir) if stat.type == "directory" then - rm(dir) + Util.walk(dir, function(path, _, type) + if type == "directory" then + vim.loop.fs_rmdir(path) + else + vim.loop.fs_unlink(path) + end + end) + vim.loop.fs_rmdir(dir) else vim.loop.fs_unlink(dir) end @@ -108,17 +103,17 @@ function Task:install() end function Task:run() - Loader.load(self.plugin, { task = "run" }) + Loader.load(self.plugin, { task = "run" }, { load_start = true }) local run = self.plugin.run if run then if type(run) == "string" and run:sub(1, 1) == ":" then - vim.cmd(run:sub(2)) + local cmd = vim.api.nvim_parse_cmd(run:sub(2), {}) + self.output = vim.api.nvim_cmd(cmd, { output = true }) elseif type(run) == "function" then run() else local args = vim.split(run, "%s+") - return self:spawn(table.remove(args, 1), { args = args, cwd = self.plugin.dir, diff --git a/lua/lazy/view/commands.lua b/lua/lazy/view/commands.lua index cea66b9..2d476fb 100644 --- a/lua/lazy/view/commands.lua +++ b/lua/lazy/view/commands.lua @@ -1,6 +1,6 @@ local View = require("lazy.view") local Manager = require("lazy.manager") -local Util = require("lazy.core.util") +local Util = require("lazy.util") local M = {} diff --git a/lua/lazy/view/init.lua b/lua/lazy/view/init.lua index 4f8c93e..93c8b7a 100644 --- a/lua/lazy/view/init.lua +++ b/lua/lazy/view/init.lua @@ -1,4 +1,4 @@ -local Util = require("lazy.core.util") +local Util = require("lazy.util") local Render = require("lazy.view.render") local M = {} @@ -54,11 +54,6 @@ function M.show() end end - vim.keymap.set("n", "", close, { - nowait = true, - buffer = buf, - }) - vim.keymap.set("n", "q", close, { nowait = true, buffer = buf,