开发

Nvim :help 页面,生成源代码 使用 tree-sitter-vimdoc 解析器。


Nvim 的开发 dev
本参考描述了用于开发 Nvim 应用程序或 Nvim 本身的的设计约束和指南。请参阅 dev-arch 以了解 Nvim 的架构和内部概念的讨论。
Nvim 是免费且开源的。鼓励所有人贡献。 https://github.com/neovim/neovim/blob/master/CONTRIBUTING.md

设计目标 design-goals

最重要的内容排在最前面(大致上)。有些项目存在冲突,这是故意的。必须找到平衡点。

NVIM 是... 改进的 design-improved

Nvim 的 Neo 部分应该使其成为更好的 Vim,而不会变成一个完全不同的编辑器。
在品味方面,优先考虑 Vim/Unix 传统。如果不存在相关的 Vim/Unix 传统,请考虑“常见情况”。
可以添加的功能没有限制。根据 (1) 用户的要求、(2) 实现所需的工作量和 (3) 实际执行的人员来选择新功能。
向后兼容性是一项功能。尤其是 RPC API 永远不应该中断。

NVIM 是... 文档齐全的 design-documented

未记录的功能是无用的功能。新功能的补丁必须包含文档。
文档应该全面且易懂。请使用示例。
不要使文本过长。较少的文档意味着更易于查找某个项目。

NVIM 是... 快速且体积小的 design-speed-size

保持 Nvim 体积小巧且运行速度快。这直接影响着多功能性和易用性。
计算机每年都在变得越来越快、越来越大。Vim 也可以增长,但增长速度不能快于计算机的增长速度。保持 Vim 在旧系统上可用。
许多用户经常从 shell 启动 Vim。启动时间必须很短。
命令必须高效运行。它们消耗的时间必须尽可能短。有用的命令可能需要更长的时间。
不要忘记有些人通过缓慢的连接使用 Vim。尽量减少通信开销。
Vim 是其他组件中的一个组件。不要将其变成一个庞大的应用程序,但要使其与其他程序良好协作(“可组合性”)。

NVIM 是... 易于维护的 design-maintain

源代码不应变得混乱。它应该是可靠的代码。
以有用的方式使用注释!引用函数名和参数名并不好。请解释它们的作用。
应简化移植到其他平台的过程,无需对平台无关代码进行过多更改。
使用面向对象的理念:将数据和代码放在一起。尽量减少知识传播到代码的其他部分。

NVIM 不是... design-not

Nvim 不是操作系统;相反,它应该与其他工具组合在一起或作为组件托管。Marvim 曾经说过:“与 Emacs 不同,Nvim 不包含厨房水槽……但它很适合水管工。”

开发人员指南 dev-guidelines

提供者 dev-provider

Nvim 的主要目标是允许扩展编辑器,而无需在核心方面具有特殊知识。一些核心功能被委派给作为外部脚本实现的“提供者”。
示例
1. 在 Vim 源代码中,剪贴板逻辑占用了超过 1k 行的 C 源代码 (ui.c),以执行现在可以使用 shell 命令(如 xclip 或 pbcopy/pbpaste)完成的两项任务。
2. Python 脚本支持:Vim 有三个文件专门用于嵌入 Python 解释器:if_python.c、if_python3.c 和 if_py_both.h。这些文件加起来大约有 9.5k 行 C 源代码。相比之下,Nvim Python 脚本由一个在 ~2k 行 Python 代码中实现的外部主机进程执行。
提供者框架从 C 调用 Vimscript。它由 eval.c 中的两个函数组成
eval_call_provider({name}, {method}, {arguments}, {discard}):使用 {method}{arguments} 调用 provider#{name}#Call。如果 {discard} 为真,则提供者返回的任何值都将被丢弃,并返回空值。
eval_has_provider({name}):检查 g:loaded_{name}_provider 变量,该变量必须由提供者脚本设置为 2,以指示它“已启用且工作”。由 has() 调用以检查功能是否可用。
例如,Python 提供者由“autoload/provider/python.vim”脚本实现,该脚本仅在找到有效的外部 Python 主机时才将 g:loaded_python_provider 设置为 2。然后 has("python") 反映 Python 支持是否有效。
provider-reload
有时 GUI 或其他应用程序可能希望强制提供者“重新加载”。要重新加载提供者,请取消定义其“已加载”标志,然后使用 :runtime 重新加载它
:unlet g:loaded_clipboard_provider
:runtime autoload/provider/clipboard.vim

文档 dev-doc

“直说”。在所有文档(docstrings、用户手册、网站资料、新闻稿……)中避免使用含糊不清的口语化表达方式。不要拐弯抹角。个性和风格(适度使用)是受欢迎的——但总的来说,要以读者的宝贵时间和精力为优化目标:“简洁明了”。
优先使用主动语态:“Foo 执行 X”,而不是“X 由 Foo 执行”。
“你选择的词语是用户体验的重要组成部分。” https://developer.apple.com/design/human-interface-guidelines/writing
“……但不要过于口语化或轻浮。” https://developers.google.com/style/tone
编写 docstrings(而不是内联注释)时,使用现在时态(“获取”),而不是祈使句(“获取”)。这往往会减少歧义并提高清晰度,因为它描述的是“什么”,而不是“如何”。
✅ OK:
/// Gets a highlight definition.
❌ NO:
/// Get a highlight definition.
避免以“The”或“A”开头 docstrings,除非为了避免歧义。这是一个视觉辅助工具,可以减少噪音。
✅ OK:
/// @param dirname Path fragment before `pend`
❌ NO:
/// @param dirname The path fragment before `pend`
Vim 的差异
不要在帮助标签前加前缀“nvim-”。使用 vim_diff.txt 来记录与 Vim 的差异;没有其他区别是必要的。
如果删除了 Vim 功能,请删除其帮助部分,并将标签移至 vim_diff.txt
deprecated.txt 中提及已弃用的功能,并删除其旧文档。
使用一致的语言。
帮助标签中的“terminal”始终表示“嵌入式终端模拟器”,而不是“用户主机终端”。
使用“tui-”作为与主机终端相关的帮助标签的前缀,并在散文中尽可能使用“TUI”。
有关 Lua 文档应放置位置的大致指南
Nvim API 函数 vim.api.nvim_* 应在 api.txt 中。
如果模块很大,并且与通用和低级 Lua 功能无关,那么它很可能需要单独存放。例如:treesitter.txt
否则,请将其添加到 lua.txt
文档格式
对于 Nvim 拥有的文档,请使用以下“vimdoc”的严格子集,以确保帮助文档在其他格式(如 HTML:https://neovim.fullstack.org.cn/doc/user)中能很好地呈现。
严格的“vimdoc”子集
使用列表(像这样!),以“-”或“•”作为前缀,用于你不希望自动换行的相邻行。列表始终以“流动”布局(软换行)呈现,而不是以传统 :help 文档中常见的预格式化(硬换行)布局呈现。
限制:目前,解析器 https://github.com/neovim/tree-sitter-vimdoc 不理解编号列表项,因此在编号项前使用项目符号(- 或 •),例如“• 1.” 而不是“1.”。
使用空行分隔内容块(段落)。
不要在随机位置使用缩进——这会阻止页面使用“流动”布局。如果你需要一个预格式化的部分,请将其放在以“>”开头的 help-codeblock 中。
参数和字段以 {foo} 形式记录。
可选参数和字段以 {foo}? 形式记录。
C docstrings
Nvim API 文档位于源代码中,作为函数定义中的 docstrings(doc 注释)。api :help 是从 src/nvim/api/*.c 中定义的 docstrings 生成的。
Docstring 格式
行以 /// 开头
特殊标记以 @ 后跟标记名称开头:@note@param@return
支持 Markdown。
标签以 [tag]() 形式编写。
引用以 [tag] 形式编写
对代码示例使用 ```。代码示例可以注释为 vimlua
示例:nvim_open_win() 的帮助是从 src/nvim/api/win_config.c 中定义的类似于以下 docstring 生成的
/// Opens a new window.
/// ...
///
/// Example (Lua): window-relative float
///
/// ```lua
/// vim.api.nvim_open_win(0, false, {
///   relative='win',
///   row=3,
///   col=3,
///   width=12,
///   height=3,
/// })
/// ```
///
/// @param buffer Buffer to display
/// @param enter  Enter the window
/// @param config Map defining the window configuration. Keys:
///   - relative: Sets the window layout, relative to:
///      - "editor" The global editor grid.
///      - "win"    Window given by the `win` field.
///      - "cursor" Cursor position in current window.
/// ...
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
Lua docstrings
dev-lua-doc
Lua 文档位于源代码中,作为函数定义中的 docstrings。lua-vim :help 是从 docstrings 生成的。
Docstring 格式
支持 Markdown。
标签以 [tag]() 形式编写。
引用以 [tag] 形式编写
对代码示例使用 ```。代码示例可以注释为 vimlua
使用 @since <api-level> 来记录函数何时变得“稳定”的 api-level。如果 <api-level> 大于当前的稳定版本(或 0),则将其标记为“实验性”。
请参阅 scripts/util.lua 以了解 api-level 与 Nvim 版本的映射关系。
使用 @nodoc 阻止文档生成。
使用 @inlinedoc@class 块内联到 @param 块中。例如
--- Object with fields:
--- @class myOpts
--- @inlinedoc
---
--- Documentation for some field
--- @field somefield? integer
--- @param opts? myOpts
function foo(opts)
end
将呈现为
foo({opts})
    Parameters:
      - {opts}? (table) Object with the fields:
                - {somefield}? (integer) Documentation
                  for some field
声明为 @meta 的文件仅用于类型化和文档化(类似于“*.d.ts” typescript 文件)。
示例:vim.paste() 的帮助是从 runtime/lua/vim/_editor.lua 中用类似于以下 docstring 装饰 vim.paste 生成的
--- Paste handler, invoked by |nvim_paste()| when a conforming UI
--- (such as the |TUI|) pastes text into the editor.
---
--- Example: To remove ANSI color codes when pasting:
---
--- ```lua
--- vim.paste = (function()
---   local overridden = vim.paste
---   ...
--- end)()
--- ```
---
--- @since 12
--- @see |paste|
---
--- @param lines  ...
--- @param phase  ...
--- @returns false if client should cancel the paste.

LUA STDLIB 设计指南 dev-lua

另请参阅 dev-naming
保持核心 Lua 模块 lua-stdlib 简单。避免使用复杂的 OOP 或伪 OOP 设计。插件作者只需要调用函数,而不是庞大而复杂的继承层次结构。
避免在 Nvim stdlib 中要求或返回特殊对象。普通表或值更易于序列化,更易于从文字构造,更易于检查和打印,并且本质上与所有 Lua 插件兼容。(此指南不适用于不透明的非数据对象,如 vim.cmd。)
stdlib 函数应遵循以下常见模式
返回 lua-result-or-message (any|nil,nil|string) 以传达失败,或者在适当的情况下选择 dev-error-patterns
接受可迭代对象,而不仅仅是表。
注意:在某些情况下,可迭代对象没有意义,例如,spair() 按定义对输入进行排序,因此它没有理由接受可迭代对象,因为输入需要被“具象化”;它不能对“流”进行操作。
如果可能,返回一个可迭代对象(生成器)而不是表。
如果函数旨在用于 for-in 循环,则模仿 pairs() 或 ipairs() 接口。
dev-error-patterns
为了向使用者传达失败,请从以下模式中选择(按优先级排序):1. retval, errmsg
当失败是正常情况,或者使用者以其他方式继续(回退)是可行的时候。参见 lua-result-or-message。2. 可选结果,无 errmsg
1 的特殊情况。当只有一个“不存在”的情况(例如,缓存查找,字典查找)。3. error("no luck")
对于无效状态(“绝不应该发生”),当失败是异常情况,或者在低级水平上,使用者不太可能以有意义的方式处理它时。优点是传播会自动发生,并且更难意外吞并错误。(例如,使用 uv_handle/pipe:write() 而不检查返回值很常见。)4. on_error 参数
对于异步和遍历图的“访问者”,在继续工作时可能收集到许多错误。5. vim.notify(有时带有可选的 opts.silent(异步,访问者^))
高级/应用程序级消息。最终用户直接调用它们。
dev-patterns
接口约定
在可能的情况下,这些模式适用于 Lua 和 API。
在接受缓冲区 ID 等时,0 表示“当前缓冲区”,nil 表示“所有缓冲区”。窗口 ID、标签页 ID 等也是如此。
任何接受回调的函数签名(例如:table.foreach())都应将它放在最后一个参数(在 opts 之后),如果可能的话(或者总是用于“延续回调”——只调用一次的函数)。
通过将“噪声”较少的参数放在开头,提高了可读性。
与 luv 保持一致。
对于将 function(<args>, cb(<ret>)) 转换为 function(<args>) -> <ret> 的未来异步库很有用。
示例
-- ✅ OK:
filter(…, opts, function() … end)
-- ❌ NO:
filter(function() … end, …, opts)
-- ❌ NO:
filter(…, function() … end, opts)
“启用”(“切换”)接口和行为
enable(…, nil)enable(…, {buf=nil}) 是同义词,用于控制功能的“全局”启用。
is_enabled(nil)is_enabled({buf=nil}) 同样查询功能的全局状态。
enable(…, {buf: number}) 设置一个缓冲区本地的“启用”标志。
is_enabled({buf: number}) 同样查询功能的缓冲区本地状态。
参见 vim.lsp.inlay_hint.enable()vim.lsp.inlay_hint.is_enabled(),了解这些“最佳实践”的参考实现。

API 设计指南 dev-api

另请参阅 dev-naming
添加 API 时,请检查以下内容
你借鉴了什么先例?你的解决方案与它们相比如何?
你的新 API 是否允许未来扩展?如何?或者为什么不?
新 API 是否与现有 API 相似?我们需要弃用旧的 API 吗?
你是否在文档中交叉引用了相关的概念?
避免“相互排斥”的参数——如果需要,可以通过约束或限制。例如,nvim_create_autocmd() 具有相互排斥的“回调”和“命令”参数;但可以通过简单地不支持 Vimscript 函数名称来消除“命令”参数,并将字符串“回调”参数视为一个 Ex 命令(它可以调用 Vimscript 函数)。“缓冲区”参数也可以通过将数字“模式”视为一个缓冲区编号来消除。
避免依赖于光标位置、当前缓冲区等的函数。相反,函数应该接收一个位置参数、缓冲区参数等。
去哪里
API(libnvim/RPC):公开低级内部机制,或客户端或 C 使用者需要的基本内容(例如 nvim_exec_lua())。
Lua 标准库 = 建立在 API 之上的高级功能。

命名指南 dev-naming

命名极其重要:事物的名称是使用它、讨论它、搜索它、分享它……的主要接口。标准库、API 和 UI 中一致的命名有助于用户和开发人员发现并直观地理解相关概念(“家族”),并减轻认知负担。可发现性鼓励代码重用,同样避免冗余、重叠的机制,这减少了代码表面积,从而最大程度地减少了错误……
命名约定
一般来说,在选择名称时,请查看先例,即查看现有的(未弃用的)函数。特别地,请参见以下内容……
dev-name-common
如果可能,使用现有的通用 {verb} 名称(动作)
add:追加或插入到集合中
attach:监听某事物以获取来自它的事件(待办事项:重命名为“on”?)
call:调用一个函数
cancel:取消或关闭一个事件或交互,通常是用户发起的,并且没有错误。(与“abort” 相比,后者取消并发出错误/失败信号。)
clear:清除状态,但不销毁容器
create:创建一个新的(非平凡的)事物(待办事项:重命名为“def”?)
del:删除一个事物(或一组事物)
detach:处置附加的侦听器(待办事项:重命名为“un”?)
enable:启用/禁用功能。签名应为 enable(enable?:boolean, filter?:table)
eval:评估一个表达式
exec:执行代码,可能会返回一个结果
fmt:格式化
get:获取事物(通常通过查询)
inspect:提供一个高级的,通常是交互式的视图
is_enabled:检查功能是否已启用。
open:打开某事物(一个缓冲区,窗口,……)
parse:解析某事物为结构化形式
set:设置一个事物(或一组事物)
start:启动一个长期运行的进程。除了“start”明显更合适的情况外,更喜欢“enable”。
stop:“start”的逆运算。拆卸一个长期运行的进程。
try_{verb}:尽力而为的操作,失败则返回 null 或错误对象
不要使用以下弃用的动词
disable:更喜欢 enable(enable: boolean)
exit:更喜欢“cancel”(如果合适,也可以使用“stop”)。
is_disabled:更喜欢 is_enabled()
list:与“get”冗余
notify:与“print”,“echo”冗余
show:与“print”,“echo”冗余
toggle:更喜欢 enable(not is_enabled())
在 API 函数中使用一致的 {topic} 名称:缓冲区在任何地方都称为“buf”,而不是在某些地方称为“buffer”,而在其他地方称为“buf”。
buf:缓冲区
chan:频道
cmd:命令
cmdline:命令行 UI 或输入
fn:函数
hl:高亮
pos:位置
proc:系统进程
tabpage:标签页
win:窗口
不要使用以下弃用的名词
buffer:改用“buf”
callback:改用 on_foo
command:改用“cmd”
window:改用“win”
dev-name-events
使用“on_”前缀为事件处理回调以及“注册”此类处理程序的接口命名(on_key)。双重性质是可以接受的,以避免对这些相关概念使用混淆的命名约定。
编辑器 事件(自动命令)在历史上被称为
{Noun}{Event}
使用此格式为 API(RPC)事件命名
nvim_{noun}_{event-name}_event
示例
nvim_buf_changedtick_event
dev-api-name
使用此格式为新的 RPC API 函数命名
nvim_{topic}_{verb}_{arbitrary-qualifiers}
不要添加新的 nvim_buf/nvim_win/nvim_tabpage API,除非你确信该概念永远不会应用于超过一个“范围”。也就是说,{topic} 应该是作用于范围(buf/win/tabpage/global)的主题(“ns”,“extmark”,“option”……),而不是范围。相反,范围应该是一个参数(通常表现为像 nvim_get_option_value() 这样的相互排斥的 buf/win/… 标志,或者更不常见的是像 nvim_get_option_info2() 这样的 scope: string 字段)。
示例:nvim_get_keymap('v') 在全局上下文中运行(第一个参数不是缓冲区)。“get”动词表示它获取与给定过滤器参数匹配的任何内容。“list”动词是不必要的,因为 nvim_get_keymap('')(空过滤器)返回所有项目。
示例:nvim_buf_del_mark 作用于 Buffer 对象(第一个参数)并使用“del” {verb}

接口模式 dev-api-patterns

更喜欢为给定主题添加一个单独的 nvim_{topic}_{verb}_… 接口。
示例
nvim_ns_add(
  ns_id: int,
  filter: {
    handle: integer (buf/win/tabpage id)
    scope: "global" | "win" | "buf" | "tabpage"
  }
): { ok: boolean }
nvim_ns_get(
  ns_id: int,
  filter: {
    handle: integer (buf/win/tabpage id)
    scope: "global" | "win" | "buf" | "tabpage"
  }
): { ids: int[] }
nvim_ns_del(
  ns_id: int,
  filter: {
    handle: integer (buf/win/tabpage id)
    scope: "global" | "win" | "buf" | "tabpage"
  }
): { ok: boolean }
反例
为同一个 xx 主题创建独立的 nvim_xxnvim_buf_xxnvim_win_xxnvim_tabpage_xx 函数,需要 4 倍的文档量、测试量、样板和接口,用户必须理解,维护者必须维护,等等。因此,以下建议不要使用(将这些 12 个(!) 函数与上述 3 个函数进行比较)
nvim_add_ns(…)
nvim_buf_add_ns(…)
nvim_win_add_ns(…)
nvim_tabpage_add_ns(…)
nvim_del_ns(…)
nvim_buf_del_ns(…)
nvim_win_del_ns(…)
nvim_tabpage_del_ns(…)
nvim_get_ns(…)
nvim_buf_get_ns(…)
nvim_win_get_ns(…)
nvim_tabpage_get_ns(…)

API 客户端 dev-api-client

api-client
API 客户端包装 Nvim API 以为各自的平台提供惯用的“SDK”(参见 术语)。你可以为你喜欢的平台或编程语言构建一个新的 API 客户端。
node-client pynvim 这些客户端可以被认为是 API 客户端的“参考实现”
标准功能
API 客户端的存在是为了隐藏 msgpack-rpc 细节。包装器可以通过读取 Nvim 中的 api-metadata 来自动生成。 api-mapping
客户端应在连接后调用 nvim_set_client_info(),以便用户和插件可以通过处理 ChanInfo 事件来检测客户端。这避免了对特殊变量或其他客户端提示的需求。
客户端应处理 nvim_error_event 通知,如果对 nvim 的异步请求被拒绝或导致错误,则会发送这些通知。
包命名
API 客户端包不应以“neovim”或“python-client”之类的含糊不清的名称命名。使用“nvim”作为某个其他标识符的前缀/后缀,以遵循生态系统约定。
例如,Python 包往往在名称中包含“py”,因此“pynvim”是一个不错的名称:它既惯用又明确。如果包名为“neovim”,则会混淆用户,并使文档和讨论变得复杂。
API 客户端包名称示例
✅ OK:nvim-racket
✅ OK:pynvim
❌ NO:python-client
❌ NO:neovim_
API 客户端实现指南
将传输层与库的其余部分分开。 rpc-connecting
使用至少实现 MessagePack 规范第 5 版的 MessagePack 库,该库支持 Nvim 使用的 BIN 和 EXT 类型。
使用单线程事件循环库/模式。
对于用于实现客户端的语言,使用纤维/协程库。这些极大地简化了并发,并允许库在非阻塞事件循环之上公开一个阻塞 API,而无需预先抢占式多任务处理带来的复杂性。
不要假设对 RPC 请求的响应顺序。
客户端应期望请求,这些请求必须立即处理,因为 Nvim 在等待客户端响应时处于阻塞状态。
客户端应期望通知,但这些通知可以“尽快”(而不是立即)处理,因为它们不会阻塞 Nvim。
对于 C/C++ 项目,建议使用 libmpack 而不是 msgpack.org 库。 https://github.com/libmpack/libmpack/ libmpack 体积小巧(无依赖,可内联到 C/C++ 项目中)且高效(无内存分配)。它还实现了 msgpack-RPC,这是 Nvim 所需的协议。 https://github.com/msgpack-rpc/msgpack-rpc

外部 UI dev-ui

外部 UI 应该了解 api-contract。 特别是,未来版本的 Nvim 可能会在现有事件中添加新项。 API 向后兼容性强,但客户端在现有事件中添加新(可选)字段时不应中断。
标准功能
预计外部 UI 将实现这些通用功能
连接后调用 nvim_set_client_info(),以便用户和插件可以通过处理 ChanInfo 事件来检测 UI。 这避免了对特殊变量和 UI 特定配置文件(gvimrc、macvimrc 等)的需求。
光标样式(形状、颜色)应符合 'guicursor' 属性,该属性由 mode_info_set UI 事件传递。
将 ALT/META(macOS 上的“Option”)键发送为 <M- 键序。
将“超级”键(Windows 键,Apple 键)发送为 <D- 键序。
避免与 Nvim 键映射空间冲突的映射; GUI 有许多新的键序(<C-,> <C-Enter> <C-S-x> <D-x>)和模式(“shift shift”),这些键序和模式不会与 Nvim 默认设置、插件等发生潜在的冲突。
将“option_set” ui-global 事件视为其他 GUI 行为的提示。 此事件中发布了各种与 UI 相关的选项('guifont''ambiwidth' 等)。 另请参阅“mouse_on”、“mouse_off”。
UI 通常不应设置 $NVIM_APPNAME(除非用户明确要求)。
支持 ui-event-hl_attr_define 给出的文本装饰/属性。 “url” 属性应显示为可点击的超链接。
主要
命令索引
快速参考