157 lines
5.8 KiB
Lua
157 lines
5.8 KiB
Lua
-- =============================================================================
|
||
-- 自定义懒加载框架 (lua/lazy.lua)
|
||
-- =============================================================================
|
||
-- 本模块与 lazy.nvim 插件管理器完全无关,
|
||
-- 是一个约 35 行的轻量级懒加载原语封装,用于延迟加载重型插件模块。
|
||
--
|
||
-- 核心设计:
|
||
-- - 通过 _loaded 表跟踪已加载模块,避免重复初始化
|
||
-- - on_event:通过 autocmd 在特定事件(如 InsertEnter、BufReadPost)触发加载
|
||
-- - on_keys:通过键位映射触发加载,首次按键时初始化插件
|
||
-- - load:直接加载,用于手动触发或内部调用
|
||
-- =============================================================================
|
||
|
||
local M = {}
|
||
|
||
-- 已加载模块的标记表。
|
||
-- 键为模块标识名(如 "treesitter", "lsp", "completion"),
|
||
-- 值为 true 表示该模块已完成初始化。
|
||
-- 此表用于防止重复加载和重复执行 setup()。
|
||
M._loaded = {}
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 直接加载模块
|
||
-- ---------------------------------------------------------------------------
|
||
-- 参数:
|
||
-- name - 模块标识名(自定义字符串,用于 _loaded 去重)
|
||
-- fn - 可选的初始化函数,在首次加载时执行
|
||
--
|
||
-- 行为:
|
||
-- 1. 检查 _loaded[name] 是否为真,是则直接返回(幂等)
|
||
-- 2. 如果有 fn,执行 fn() 完成插件 setup
|
||
-- 3. 仅在 fn() 成功执行后,标记为已加载
|
||
--
|
||
-- 使用场景:
|
||
-- 在按键回调中手动触发加载,或在 on_event 回调中延迟初始化。
|
||
M.load = function(name, fn)
|
||
if M._loaded[name] then
|
||
return true
|
||
end
|
||
if fn then
|
||
local ok, err = pcall(fn)
|
||
if not ok then
|
||
vim.notify("[lazy] 加载 " .. name .. " 失败: " .. tostring(err), vim.log.levels.ERROR)
|
||
return false
|
||
end
|
||
end
|
||
M._loaded[name] = true
|
||
return true
|
||
end
|
||
|
||
-- 仅标记模块为已加载,不执行 setup(用于直接加载的插件)。
|
||
M.track = function(name)
|
||
if M._loaded[name] then
|
||
return true
|
||
end
|
||
M._loaded[name] = true
|
||
return true
|
||
end
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 事件触发懒加载
|
||
-- ---------------------------------------------------------------------------
|
||
-- 参数:
|
||
-- name - 模块标识名
|
||
-- event - Neovim autocmd 事件名(如 "InsertEnter", "BufReadPost", "VimEnter")
|
||
-- pattern - autocmd 匹配模式,默认为 "*"
|
||
-- fn - 初始化函数
|
||
--
|
||
-- 原理:
|
||
-- 创建一个一次性的 autocmd(once = true),
|
||
-- 当指定事件首次触发时,调用 M.load(name, fn) 完成初始化。
|
||
-- 由于 once = true,autocmd 在触发后自动销毁,不会重复执行。
|
||
--
|
||
-- 典型用法:
|
||
-- lazy.on_event("completion", "InsertEnter", "*", function()
|
||
-- require("mini.completion").setup({ ... })
|
||
-- end)
|
||
M.on_event = function(name, event, pattern, fn)
|
||
vim.api.nvim_create_autocmd(event, {
|
||
pattern = pattern or "*",
|
||
once = true,
|
||
callback = function()
|
||
M.load(name, fn)
|
||
end,
|
||
})
|
||
end
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 按键触发懒加载
|
||
-- ---------------------------------------------------------------------------
|
||
-- 参数:
|
||
-- name - 模块标识名
|
||
-- keys - 键位序列(如 "<leader>ff", "<leader>gg")
|
||
-- mode - 映射模式,默认为 "n"(normal)
|
||
-- fn - 初始化函数,在首次按键时执行
|
||
-- action - 可选的额外动作函数,在 fn 之后执行
|
||
-- opts - 键位映射选项(desc, buffer 等)
|
||
--
|
||
-- 原理:
|
||
-- 创建一个键位映射,首次按下时:
|
||
-- 1. 调用 M.load(name, fn) 完成插件初始化
|
||
-- 2. 如果提供了 action,执行 action()
|
||
-- 3. 后续按键直接执行 action(因为 _loaded 已标记)
|
||
--
|
||
-- 注意:
|
||
-- 此设计使得首次按键稍慢(需要 setup),后续按键与直接映射无异。
|
||
-- 适合重型插件如 mini.pick、vim-fugitive。
|
||
M.on_keys = function(name, keys, mode, fn, action, opts)
|
||
mode = mode or "n"
|
||
opts = opts or {}
|
||
vim.keymap.set(mode, keys, function()
|
||
if not M.load(name, fn) then
|
||
return
|
||
end
|
||
if action then
|
||
action()
|
||
end
|
||
end, { desc = opts.desc, buffer = opts.buffer })
|
||
end
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 命令触发懒加载
|
||
-- ---------------------------------------------------------------------------
|
||
-- 参数:
|
||
-- name - 模块标识名
|
||
-- cmd - 命令名(如 "ExColors")
|
||
-- fn - 初始化函数,在首次执行命令时调用
|
||
-- opts - 用户命令选项(bang, nargs, desc 等)
|
||
--
|
||
-- 原理:
|
||
-- 创建一个同名的临时用户命令,首次执行时:
|
||
-- 1. 删除临时命令(避免与插件注册的命令冲突)
|
||
-- 2. 调用 M.load(name, fn) 完成插件初始化
|
||
-- 3. 插件初始化后通常会注册同名命令,覆盖临时命令
|
||
-- 4. 如果插件没有注册同名命令,则递归执行该命令
|
||
--
|
||
-- 注意:
|
||
-- 要求插件在初始化(setup)时会注册同名用户命令。
|
||
-- 如果插件不注册命令,可在 fn 中手动处理命令逻辑。
|
||
M.on_cmd = function(name, cmd, fn, opts)
|
||
opts = opts or {}
|
||
vim.api.nvim_create_user_command(cmd, function(cmd_opts)
|
||
vim.api.nvim_del_user_command(cmd)
|
||
M.load(name, fn)
|
||
-- 如果插件注册了新命令,这里需要重新执行
|
||
-- 由于 del + load 后命令可能已不存在(插件未注册)
|
||
-- 或已变成插件的命令,所以用 pcall 安全地尝试执行
|
||
local ok = pcall(vim.cmd, cmd .. (cmd_opts.bang and "!" or ""))
|
||
if not ok then
|
||
-- 插件未注册同名命令,命令已执行完毕
|
||
vim.notify("[" .. name .. "] 已加载", vim.log.levels.INFO)
|
||
end
|
||
end, { bang = opts.bang, nargs = opts.nargs, desc = opts.desc })
|
||
end
|
||
|
||
return M
|