snacks.nvim notify 模块详解:面向 Neovim 原生 vim.notify 的轻量工具函数集
【免费下载链接】snacks.nvim🍿 A collection of QoL plugins for Neovim项目地址: https://gitcode.com/GitHub_Trending/sn/snacks.nvim
本文围绕 snacks.nvim 的 notify 模块 展开,它定位为"一组与 Neovim 原生vim.notify协作的工具函数"(见 README 功能表),与负责浮窗渲染的 notifier 模块 是兄弟模块。读完本文,你将掌握Snacks.notify系列 API 的每个函数签名、once等选项的底层行为,并能把它直接接入自己的插件或配置中,替代裸vim.notify调用。
模块定位:notify 与 notifier 的分工
在 snacks.nvim 中,通知相关能力被拆成了两个独立模块,理解二者分工是正确使用的前提:
| 模块 | 描述 | 渲染器 |
|---|---|---|
| notify | 面向vim.notify的工具函数(薄封装) | 不关心渲染,委托给 Neovim 的vim.notify |
| notifier | 漂亮的vim.notify实现(浮窗渲染器) | 自带 compact / minimal / fancy / history 四种样式 |
从源码看,lua/snacks/notify.lua#L9-L11 中M.meta.desc明确写着"Utility functions to work with Neovim'svim.notify",即它不做任何 UI 渲染,只是帮你把消息、级别、选项组织好再交给vim.notify;而真正的界面层是 lua/snacks/notifier.lua,它通过覆盖vim.notify提供浮窗效果(见下文"与 notifier 的联动"一节)。
两者还能无缝协作:当你启用了 notifier 后,Snacks.notify传入的选项(title、icon、timeout、id、hl、keep、style等)会原样透传给 notifier 渲染,因为 notify 的类型别名直接复用了 notifier 的通知选项类型。
核心 API:五个函数签名全解析
原文档(docs/notify.md)定义的类型与全部函数如下,所有函数均支持msg: string|string[],即既可以传单条字符串,也可以传字符串数组(数组会自动按行拼接,见源码分析)。
Snacks.notify(msg, opts)
---@type fun(msg: string|string[], opts?: snacks.notify.Opts) Snacks.notify(msg, opts)最通用的入口,等价于Snacks.notify.notify(msg, opts)。它是模块的默认调用方式——模块本身被setmetatable成了可调用对象,__call直接转发到t.notify(...)(lua/snacks/notify.lua#L3-L7),所以Snacks.notify("hi")与Snacks.notify.notify("hi")效果完全一致。
Snacks.notify.notify(msg, opts)
---@param msg string|string[] ---@param opts? snacks.notify.Opts Snacks.notify.notify(msg, opts)底层实现函数,无预设级别(级别由opts.level决定或交给vim.notify默认值)。
三个便捷级别函数
---@param msg string|string[] ---@param opts? snacks.notify.Opts Snacks.notify.error(msg, opts) Snacks.notify.info(msg, opts) Snacks.notify.warn(msg, opts)三个函数分别预设vim.log.levels.ERROR、INFO、WARN级别。从源码看(lua/snacks/notify.lua#L29-L43),它们并非独立实现,而是统一委托给M.notify,并用vim.tbl_extend("keep", { level = ... }, opts or {})合并选项——注意是"keep"语义:用户在opts中显式传入的level会覆盖预设级别,因此Snacks.notify.error("msg", { level = vim.log.levels.WARN })会以 WARN 级别发出。
snacks.notify.Opts类型:从 notifier 继承的完整选项表
原文档给出了类型别名:
---@alias snacks.notify.Opts snacks.notifier.Notif.opts|{once?: boolean}即Snacks.notify的选项 =notifier 通知的全部选项 + 一个专属的once开关。前者定义在 lua/snacks/notifier.lua#L25-L37,字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
id | number\|string | 通知 ID,用于替换已有通知(详见下文示例) |
msg | string | 通知正文(通常由调用方直接传参,无需手动填) |
level | number\|snacks.notifier.level | 级别,数字或"trace"\|"debug"\|"info"\|"warn"\|"error"字符串 |
title | string | 标题 |
icon | string | 图标(覆盖默认级别图标) |
timeout | number\|boolean | 超时毫秒数;0或false表示一直保留直到手动关闭,true表示使用全局默认值 |
ft | string | 通知窗口的文件类型 |
keep | fun(notif): boolean | 自定义"保持显示"判定函数,返回true则通知不被超时关闭 |
style | snacks.notifier.style | 渲染样式:compact/minimal/fancy,也支持自定义渲染函数 |
opts | fun(notif) | 动态选项回调,每次渲染前调用,可动态修改图标等(如 LSP 进度动画) |
hl | snacks.notifier.hl | 高亮覆盖:title/icon/border/footer/msg |
history | boolean | 是否写入历史记录,默认写入 |
而 notify 模块独有的once选项,会让底层切换到 Neovim 的去重通知API(源码依据见下节)。
源码级机制:notify 到底做了什么
lua/snacks/notify.lua#L17-L25 的M.notify实现非常精炼,只有几行,却包含了四个值得注意的行为:
function M.notify(msg, opts) opts = opts or {} local notify = vim[opts.once and "notify_once" or "notify"] --[[@as fun(...)]] notify = vim.in_fast_event() and vim.schedule_wrap(notify) or notify msg = type(msg) == "table" and table.concat(msg, "\n") or msg --[[@as string]] msg = vim.trim(msg) opts.title = opts.title or "Snacks" return notify(msg, opts.level, opts) endonce→vim.notify_once:当opts.once为真时,调用 Neovim 的vim.notify_once(同名消息去重,同一时刻重复内容只通知一次);否则调用vim.notify。这是 notify 模块相对 notifier 最大的"附加价值"。- fast event 安全:若调用发生在 fast event 上下文(如部分 autocmd 回调),
vim.in_fast_event()为真,会用vim.schedule_wrap包裹,避免在受限上下文中直接触发 UI 操作。 - 数组消息自动拼接:
msg为字符串数组时用"\n"连接成多行文本,方便一次性输出多行内容。 - 默认标题与裁剪:对消息做
vim.trim去掉首尾空白;未指定title时默认设为"Snacks"。这也是为什么你在 Snacks 自己的模块里看到大量{ title = "Snacks Picker" }之类的写法——它们是在覆盖这个默认值,例如 lua/snacks/picker/actions.lua#L349 的Snacks.notify.warn("Only open buffers can be deleted", { title = "Snacks Picker" })。
调用后返回notify(msg, opts.level, opts)的结果(即通知 ID,可复用于替换/隐藏),因此Snacks.notify与vim.notify在返回值上保持兼容。
与 notifier 的联动:启用后 vim.notify 被整体接管
虽然 notify 模块本身不渲染,但当你同时启用 notifier 时(README 安装表中 notifier 标记为‼️,表示需要 setup),lua/snacks/init.lua#L219-L224 会在Snacks.setup()中把全局vim.notify替换为Snacks.notifier.notify:
if M.config.notifier.enabled then vim.notify = function(msg, level, o) vim.notify = Snacks.notifier.notify return Snacks.notifier.notify(msg, level, o) end end这意味着:启用 notifier 后,任何插件调用vim.notify(...)都会走 Snacks 的浮窗渲染;而Snacks.notify调用链(Snacks.notify→vim.notify→ 已被覆盖的Snacks.notifier.notify)则天然获得同样的效果。notifier 的全局默认配置定义在 lua/snacks/notifier.lua#L112-L144,包括timeout = 3000(默认 3 秒)、level = vim.log.levels.TRACE(TRACE 为最低显示门槛,所有通知仍写入历史)、sort = { "level", "added" }(先按级别再按时间排序)、top_down = true(自上而下排列)等,可通过opts = { notifier = { ... } }覆盖。
级别字符串与数字之间的映射由 lua/snacks/notifier.lua#L238-L246 维护:TRACE/DEBUG/INFO/WARN/ERROR数字对应"trace"/"debug"/"info"/"warn"/"error"字符串,未识别值一律归一为"info"(normlevel),因此Snacks.notify.info/warn/error传入的数字级别不会丢失。
实战示例:把 Snacks.notify 用起来
替换已有通知(进度类消息的标准做法)
notify 的id选项继承自 notifier,同 ID 的通知会原地替换而非叠加。这来自 notifier 的N:add实现(lua/snacks/notifier.lua#L395-L403):当opts.id已存在于队列时,复用旧的added时间戳并保留窗口,只更新内容。典型用法:
for i = 1, 10 do vim.defer_fn(function() Snacks.notify("Hello " .. i, { id = "test" }) end, i * 500) end每次调用都会替换 ID 为"test"的那条通知,而不是堆出 10 条。你也可以直接使用Snacks.notify的返回值作为 ID。
带级别的快捷调用
Snacks.notify.error("保存失败:磁盘空间不足", { title = "My Plugin" }) Snacks.notify.warn("文件已被外部修改", { title = "My Plugin", timeout = 8000 }) Snacks.notify.info({ "第一行", "第二行", "第三行" }) -- 数组自动拼接为多行once:去重提示
-- 快速事件上下文也安全;同内容短时间内只提示一次 Snacks.notify("配置已热重载", { once = true, level = vim.log.levels.INFO })动态 LSP 进度条
opts字段支持函数形式,在每次渲染前动态改写通知属性,适合做转圈动画(完整示例见 notifier 文档):
local spinner = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" } vim.api.nvim_create_autocmd("LspProgress", { callback = function(ev) vim.notify(vim.lsp.status(), "info", { id = "lsp_progress", title = "LSP Progress", opts = function(notif) notif.icon = ev.data.params.value.kind == "end" and "✓ " or spinner[math.floor(vim.uv.hrtime() / (1e6 * 80)) % #spinner + 1] end, }) end, })这里直接使用vim.notify也能生效——因为 notifier 已接管了它,而opts字段正是由 notifier 的 N:render 在每次渲染前调用。
总结与进一步阅读
Snacks.notify的价值在于:它把"与 Neovim 原生vim.notify打交道"的常见痛点(fast event 安全、数组消息、默认标题、去重、级别预设)收敛成一组稳定的工具函数,且选项体系与 notifier 完全打通——不启用 notifier 时它是薄封装,启用 notifier 时它自动获得漂亮的浮窗渲染。对插件作者来说,用Snacks.notify.error/warn/info代替裸vim.notify几乎零成本。
- 类型与函数签名原始定义:docs/notify.md
- 实现源码:lua/snacks/notify.lua、lua/snacks/notifier.lua
- 渲染器配置与示例:docs/notifier.md
- 全局安装与启用方式:README.md
【免费下载链接】snacks.nvim🍿 A collection of QoL plugins for Neovim项目地址: https://gitcode.com/GitHub_Trending/sn/snacks.nvim
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考