mirror of
https://github.com/cap153/nvim.git
synced 2026-04-17 09:45:29 +08:00
318 lines
11 KiB
Lua
318 lines
11 KiB
Lua
-- ==============================================================
|
|
-- 插件花名册
|
|
-- ==============================================================
|
|
local specs = {
|
|
-- 公共依赖
|
|
'https://github.com/nvim-lua/plenary.nvim',
|
|
'https://github.com/williamboman/mason.nvim',
|
|
'https://github.com/mason-org/mason-registry',
|
|
'https://github.com/nvim-tree/nvim-web-devicons',
|
|
-- blinkcmp.lua 自动补全、代码片段
|
|
'https://github.com/saghen/blink.cmp',
|
|
'https://github.com/rafamadriz/friendly-snippets',
|
|
-- lspconfig.lua
|
|
'https://github.com/neovim/nvim-lspconfig',
|
|
'https://github.com/williamboman/mason-lspconfig.nvim',
|
|
'https://github.com/MysticalDevil/inlay-hints.nvim',
|
|
-- treesitter.lua 需要安装tree-sitter-cli工具包
|
|
'https://github.com/nvim-treesitter/nvim-treesitter',
|
|
-- indentblankline.lua 彩虹缩进
|
|
"https://github.com/lukas-reineke/indent-blankline.nvim",
|
|
-- conform.lua 格式化工具formatter
|
|
"https://github.com/stevearc/conform.nvim",
|
|
-- kommentary.lua 注释插件
|
|
"https://github.com/b3nj5m1n/kommentary",
|
|
-- noice.lua 取代消息、命令行和弹出菜单的 UI
|
|
"https://github.com/folke/noice.nvim",
|
|
"https://github.com/MunifTanjim/nui.nvim",
|
|
-- snacks.lua 图片预览、lazygit、lsp_references模糊查找
|
|
"https://github.com/folke/snacks.nvim",
|
|
-- tv.lua 模糊查找television
|
|
"https://github.com/alexpasmantier/tv.nvim",
|
|
-- coderunner.lua 运行代码
|
|
'https://github.com/CRAG666/code_runner.nvim',
|
|
-- peek.lua 预览markdown
|
|
'https://github.com/cap153/peek.nvim',
|
|
-- bufferline.lua 顶部状态栏
|
|
'https://github.com/akinsho/bufferline.nvim',
|
|
-- yazi.lua 文件管理器
|
|
'https://github.com/mikavilpas/yazi.nvim',
|
|
-- 查看可用键位
|
|
"https://github.com/folke/which-key.nvim",
|
|
}
|
|
-- 禁用插件:不会加载,不会下载(如果是新添加的),已在硬盘上不会被删除
|
|
local disabled = {
|
|
-- { src = 'https://github.com/saghen/blink.cmp', version = 'v1.10.2' }, -- 指定版本,暂时不想用,但想留着源码
|
|
}
|
|
|
|
-- ==============================================================
|
|
-- 快捷管理命令
|
|
-- ==============================================================
|
|
|
|
-- 获取所有已安装插件的名称列表(用于 Tab 补全)
|
|
local function get_plugin_names(arg_lead)
|
|
local installed = vim.pack.get(nil, { info = false })
|
|
local names = {}
|
|
for _, p in ipairs(installed) do
|
|
local name = p.spec.name
|
|
-- 只添加匹配开头字符串的插件
|
|
if name:lower():find(arg_lead:lower(), 1, true) == 1 then
|
|
table.insert(names, name)
|
|
end
|
|
end
|
|
-- 排序让补全列表更整洁
|
|
table.sort(names)
|
|
return names
|
|
end
|
|
|
|
-- :PackUpdate 命令更新插件,不带参数更新全部
|
|
vim.api.nvim_create_user_command("PackUpdate", function(opts)
|
|
local targets = #opts.fargs > 0 and opts.fargs or nil
|
|
if targets then
|
|
vim.notify("Checking updates for: " .. table.concat(targets, ", "), vim.log.levels.INFO)
|
|
else
|
|
vim.notify("Checking updates for all plugins...", vim.log.levels.INFO)
|
|
end
|
|
vim.pack.update(targets)
|
|
end, {
|
|
nargs = "*", -- 支持 0 到多个参数
|
|
complete = get_plugin_names, -- 绑定补全函数
|
|
desc = "Update specified or all plugins",
|
|
})
|
|
|
|
-- :PackStatus 命令查看插件当前状态和版本
|
|
vim.api.nvim_create_user_command("PackStatus", function(opts)
|
|
local targets = #opts.fargs > 0 and opts.fargs or nil
|
|
vim.pack.update(targets, { offline = true })
|
|
end, {
|
|
nargs = "*",
|
|
complete = get_plugin_names,
|
|
desc = "Check plugin status without downloading",
|
|
})
|
|
|
|
-- ==============================================================
|
|
-- 插件管理引擎 (PackUtils) (暴露给全局,供 configs/*.lua 调用)
|
|
-- ==============================================================
|
|
_G.PackUtils = {
|
|
is_building = {}, -- 记录各插件的构建状态,防止重复构建
|
|
is_initialized = {}, -- 统一在这里管理所有插件的初始化状态
|
|
disabled_plugins = {}, -- 专门记录被禁用的插件,供 load 拦截使用
|
|
}
|
|
|
|
-- [解析插件名]
|
|
function PackUtils.get_name(spec)
|
|
local url = type(spec) == "table" and spec.src or spec
|
|
return type(spec) == "table" and spec.name or url:match("([^/]+)$"):gsub("%.git$", "")
|
|
end
|
|
|
|
-- [同步清理] 自动删除孤儿,并注册禁用名单
|
|
function PackUtils.sync(active_specs, disabled_specs)
|
|
disabled_specs = disabled_specs or {}
|
|
local protected_names = {}
|
|
|
|
-- 将插件加入受保护名单
|
|
for _, spec in ipairs(active_specs) do
|
|
protected_names[PackUtils.get_name(spec)] = true
|
|
end
|
|
for _, spec in ipairs(disabled_specs) do
|
|
local name = PackUtils.get_name(spec)
|
|
protected_names[name] = true
|
|
PackUtils.disabled_plugins[name] = true -- 写入字典,供 load 拦截
|
|
end
|
|
|
|
-- 扫描磁盘
|
|
local pack_dir = vim.fn.stdpath("data") .. "/site/pack"
|
|
local installed_plugins = {}
|
|
local packages = vim.fn.expand(pack_dir .. "/*", false, true)
|
|
for _, pkg in ipairs(packages) do
|
|
for _, type_dir in ipairs({ "start", "opt" }) do
|
|
local path = pkg .. "/" .. type_dir
|
|
if vim.fn.isdirectory(path) == 1 then
|
|
local dirs = vim.fn.expand(path .. "/*", false, true)
|
|
for _, dir in ipairs(dirs) do
|
|
local name = dir:match("([^/]+)$")
|
|
if name ~= "README.md" and name ~= "doc" then
|
|
table.insert(installed_plugins, name)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- 找出既不在 active 也不在 disabled 里的孤儿
|
|
local to_delete = {}
|
|
for _, installed in ipairs(installed_plugins) do
|
|
if not protected_names[installed] then
|
|
table.insert(to_delete, installed)
|
|
end
|
|
end
|
|
|
|
if #to_delete > 0 then
|
|
vim.schedule(function()
|
|
vim.notify("🧹 Clean Up Orphaned Plugins: " .. table.concat(to_delete, ", "), vim.log.levels.INFO)
|
|
vim.pack.del(to_delete)
|
|
end)
|
|
end
|
|
end
|
|
|
|
-- [动态路径] 获取插件根目录
|
|
function PackUtils.get_root(name)
|
|
local paths = vim.api.nvim_get_runtime_file("pack/*/*/" .. name, true)
|
|
if #paths > 0 then return paths[1] end
|
|
local glob = vim.fn.globpath(vim.o.packpath, "pack/*/*/" .. name, 0, 1)
|
|
return glob[1] or nil
|
|
end
|
|
|
|
-- [构建执行] 执行编译任务
|
|
function PackUtils.run_build(name, build_cmd)
|
|
if PackUtils.disabled_plugins[name] then return end
|
|
if not build_cmd or PackUtils.is_building[name] then return end
|
|
local path = PackUtils.get_root(name)
|
|
if not path then return end
|
|
local stamp = path .. "/.build_done"
|
|
PackUtils.is_building[name] = true
|
|
|
|
-- 判断是否为 Neovim 内部命令 (以 : 开头)
|
|
local is_vim_cmd = false
|
|
local vim_cmd_str = ""
|
|
|
|
if type(build_cmd) == "string" and build_cmd:sub(1, 1) == ":" then
|
|
is_vim_cmd = true
|
|
vim_cmd_str = build_cmd:sub(2)
|
|
elseif type(build_cmd) == "table" and type(build_cmd[1]) == "string" and build_cmd[1]:sub(1, 1) == ":" then
|
|
is_vim_cmd = true
|
|
vim_cmd_str = table.concat(build_cmd, " "):sub(2)
|
|
end
|
|
|
|
if is_vim_cmd then
|
|
-- 在当前实例的空闲时执行 vim.cmd
|
|
vim.schedule(function()
|
|
vim.notify("⚙️ Running " .. name .. " setup command...", vim.log.levels.INFO)
|
|
-- 确保插件在当前实例已经被加载
|
|
pcall(vim.cmd.packadd, name)
|
|
-- 保护执行,防止命令错误导致编辑器崩溃
|
|
local ok, err = pcall(vim.cmd, vim_cmd_str)
|
|
PackUtils.is_building[name] = false
|
|
if ok then
|
|
local f = io.open(stamp, "w")
|
|
if f then f:close() end
|
|
vim.notify("✅ " .. name .. " setup success.", vim.log.levels.INFO)
|
|
else
|
|
vim.notify("❌ " .. name .. " setup failed: " .. tostring(err), vim.log.levels.ERROR)
|
|
end
|
|
end)
|
|
else
|
|
local final_cmd = {}
|
|
if type(build_cmd) == "string" then
|
|
for word in build_cmd:gmatch("%S+") do
|
|
table.insert(final_cmd, word)
|
|
end
|
|
else
|
|
final_cmd = build_cmd
|
|
end
|
|
vim.schedule(function() vim.notify("⚙️ Building " .. name .. " (Background)...", vim.log.levels.INFO) end)
|
|
vim.system(final_cmd, { cwd = path }, function(out)
|
|
PackUtils.is_building[name] = false
|
|
if out.code == 0 then
|
|
local f = io.open(stamp, "w")
|
|
if f then f:close() end
|
|
vim.schedule(function() vim.notify("✅ " .. name .. " build success.", vim.log.levels.INFO) end)
|
|
else
|
|
vim.schedule(function()
|
|
vim.notify("❌ " .. name .. " build failed: " .. (out.stderr or "Unknown Error"), vim.log.levels.ERROR)
|
|
end)
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
-- [监听器] 注册安装/更新监听
|
|
function PackUtils.setup_listener(name, build_cmd)
|
|
if PackUtils.disabled_plugins[name] then return end
|
|
if not build_cmd then return end
|
|
vim.api.nvim_create_autocmd('PackChanged', {
|
|
pattern = '*',
|
|
callback = function(ev)
|
|
if ev.data.spec.name == name and (ev.data.kind == "update" or ev.data.kind == "install") then
|
|
local stamp = ev.data.path .. "/.build_done"
|
|
os.remove(stamp) -- 自动删除.build_done文件触发构建
|
|
PackUtils.run_build(name, build_cmd)
|
|
end
|
|
end
|
|
})
|
|
end
|
|
|
|
-- [健康检查] 如果没标记且有构建命令,则触发构建
|
|
function PackUtils.check_health(name, build_cmd)
|
|
if PackUtils.disabled_plugins[name] then return end
|
|
if not build_cmd then return end
|
|
local path = PackUtils.get_root(name)
|
|
if path then
|
|
local stamp = path .. "/.build_done"
|
|
if vim.fn.filereadable(stamp) == 0 then
|
|
PackUtils.run_build(name, build_cmd)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 全方位防崩加载引擎
|
|
function PackUtils.load(P, config_fn)
|
|
if PackUtils.disabled_plugins[P.name] then return end
|
|
if PackUtils.is_initialized[P.name] then return end
|
|
PackUtils.check_health(P.name, P.build_cmd)
|
|
|
|
-- 强制将主插件挂载到 runtimepath
|
|
pcall(vim.cmd.packadd, P.name)
|
|
|
|
-- 保护依赖加载 (防止 dependencies 里的插件没下载)
|
|
if P.deps then
|
|
for _, dep in ipairs(P.deps) do
|
|
local dep_ok = pcall(vim.cmd.packadd, dep)
|
|
if not dep_ok then
|
|
vim.notify("Warning: " .. P.name .. " dependency [" .. dep .. "] missing", vim.log.levels.WARN)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 保护 require (防止插件文件夹还没下载完)
|
|
local req_ok, plugin = pcall(require, P.module)
|
|
-- 如果失败,说明插件还没下载好或者路径不对,优雅退出
|
|
if not req_ok then
|
|
-- 经过上面强制挂载后还是失败,且硬盘上确实有这个文件夹,那绝对是 module 填错了
|
|
if PackUtils.get_root(P.name) then
|
|
vim.notify("Error: Plugin [" .. P.name .. "] module not found", vim.log.levels.ERROR)
|
|
end
|
|
return
|
|
end
|
|
|
|
-- 保护 Setup 执行:使用 pcall 包裹传进来的匿名函数,防止 setup 里的参数写错导致崩溃
|
|
if config_fn then
|
|
local setup_ok, err = pcall(config_fn, plugin)
|
|
if not setup_ok then
|
|
vim.notify("Error: " .. P.name .. " setup failed: " .. tostring(err), vim.log.levels.ERROR)
|
|
return
|
|
end
|
|
end
|
|
|
|
-- 只有全部流程走通,才标记为已初始化
|
|
PackUtils.is_initialized[P.name] = true
|
|
end
|
|
|
|
-- ==============================================================
|
|
-- 执行启动流程
|
|
-- ==============================================================
|
|
|
|
-- 同步清理孤儿插件并注册禁用名单
|
|
PackUtils.sync(specs, disabled)
|
|
|
|
-- 正式下载/更新插件
|
|
vim.pack.add(specs)
|
|
|
|
-- 加载 configs/ 注册所有监听器
|
|
local config_path = vim.fn.stdpath("config") .. "/lua/pack/configs"
|
|
if vim.fn.isdirectory(config_path) == 1 then
|
|
for name, type in vim.fs.dir(config_path) do
|
|
if type == "file" and name:match("%.lua$") then
|
|
pcall(require, "pack.configs." .. name:gsub("%.lua$", ""))
|
|
end
|
|
end
|
|
end
|