nvim/lua/lazy.lua
xfy beb75d3a17 fix(lazy): 按键触发加载失败时不执行后续 action
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 18:08:48 +08:00

157 lines
5.8 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =============================================================================
-- 自定义懒加载框架 (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 - 初始化函数
--
-- 原理:
-- 创建一个一次性的 autocmdonce = true
-- 当指定事件首次触发时,调用 M.load(name, fn) 完成初始化。
-- 由于 once = trueautocmd 在触发后自动销毁,不会重复执行。
--
-- 典型用法:
-- 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