開發

Nvim 的 :help 頁面,是從 原始碼 使用 tree-sitter-vimdoc 解析器 產生 而來。


Nvim 的開發 dev
此參考描述了開發 Nvim 應用程式或 Nvim 本身時的設計限制和指南。有關 Nvim 架構和內部概念的討論,請參閱 dev-arch
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} 為 true,則提供者傳回的任何值都將被捨棄,並傳回空值。
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 或其他應用程式可能想要強制提供者「重新載入」。若要重新載入提供者,請取消定義其「loaded」旗標,然後使用 :runtime 重新載入它
:unlet g:loaded_clipboard_provider
:runtime autoload/provider/clipboard.vim

文件 dev-doc

「直接說出來」。在所有文件(文件字串、使用者手冊、網站資料、新聞通訊...)中避免含糊不清、口語化的措辭。不要拐彎抹角。歡迎謹慎地使用個性和風格,但一般來說,請針對讀者的時間和精力進行最佳化:要「精確而簡潔」。
偏好主動語態:「Foo 執行 X」,而不是「X 由 Foo 執行」。
「您選擇的文字是用戶體驗的重要組成部分。」https://developer.apple.com/design/human-interface-guidelines/writing
「...但不要過於口語化或輕浮。」https://developers.google.com/style/tone
以現在時(「Gets」)而不是命令式(「Get」)撰寫文件字串(而非內嵌註解)。這傾向於透過描述「是什麼」而不是「如何」來減少歧義並提高清晰度。
✅ OK:
/// Gets a highlight definition.
❌ NO:
/// Get a highlight definition.
避免以「The」或「A」開頭撰寫文件字串,除非需要避免歧義。這是一種視覺輔助,可以減少雜訊。
✅ OK:
/// @param dirname Path fragment before `pend`
❌ NO:
/// @param dirname The path fragment before `pend`
Vim 的差異
不要在 help 標籤前面加上「nvim-」。使用 vim_diff.txt 來編目與 Vim 的差異;不需要其他區別。
如果移除了 Vim 功能,請刪除其 help 部分,並將其標籤移動到 vim_diff.txt
deprecated.txt 中提及已棄用的功能,並刪除其舊文件。
使用一致的語言。
help 標籤中的「terminal」一律表示「嵌入式終端機模擬器」,而不是「使用者主機終端機」。
使用「tui-」作為與主機終端機相關的 help 標籤的前綴,並在散文中盡可能使用「TUI」。
有關 Lua 文件應放置位置的粗略指南
Nvim API 函式 vim.api.nvim_* 應在 api.txt 中。
如果模組很大且與通用和較低層級的 Lua 功能無關,則強烈建議將其分開。範例:treesitter.txt
否則,將它們新增至 lua.txt
文件格式
對於 Nvim 擁有的文件,請使用以下嚴格的「vimdoc」子集,以確保 help 文件在其他格式(例如 HTML:https://neovim.dev.org.tw/doc/user)中能良好地呈現。
嚴格的「vimdoc」子集
使用以「-」或「•」為前綴的清單(如這個!),適用於您不希望自動換行的相鄰行。清單一律以「流動」版面配置(軟換行)而非傳統 :help 文件中常見的預格式化(硬換行)版面配置呈現。
限制:目前解析器 https://github.com/neovim/tree-sitter-vimdoc 不了解編號的清單項目,因此請在編號項目之前使用項目符號(- 或 •),例如「• 1.」而不是「1.」。
以空白行分隔內容區塊(段落)。
不要在隨機位置使用縮排,這會阻止頁面使用「流動」版面配置。如果您需要預格式化的區段,請將其放在以「>」開頭的 help-codeblock 中。
參數和欄位的文件記載為 {foo}
選擇性參數和欄位的文件記載為 {foo}?
C 文件字串
Nvim API 文件以文件字串(文件註解)的形式存在於原始碼中,位於函式定義上。api :help 是從 src/nvim/api/*.c 中定義的文件字串產生。
文件字串格式
行以 /// 開頭
特殊符號以 @ 開頭,後跟符號名稱:@note@param@return
支援 Markdown。
標籤寫為 [tag]()
參考寫為 [tag]
使用 ``` 表示程式碼範例。程式碼範例可以標註為 vimlua
範例:nvim_open_win() 的 help 是從 src/nvim/api/win_config.c 中定義的文件字串產生,如下所示
/// 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 文件字串
dev-lua-doc
Lua 文件以文件字串的形式存在於原始碼中,位於函式定義上。lua-vim :help 是從文件字串產生。
文件字串格式
支援 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() 的 help 是從裝飾 runtime/lua/vim/_editor.lua 中 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 標準函式庫中要求或回傳特殊物件。單純的表格或值更容易序列化、更容易從字面值建構、更容易檢查和列印,並且本質上與所有 Lua 外掛相容。(此指南不適用於不透明的、非資料物件,例如 vim.cmd。)
標準函式庫函式應遵循這些常見模式
回傳 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. 可選結果,沒有 errormsg
第 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() 具有互斥的「callback」和「command」引數;但是,可以透過簡單地不支援 Vimscript 函式名稱,並將字串「callback」引數視為 Ex 命令(可以呼叫 Vimscript 函式)來消除「command」引數。「buffer」引數也可以透過將數字「pattern」視為緩衝區編號來消除。
避免使用依賴於游標位置、目前緩衝區等的函式。相反,函式應採用位置參數、緩衝區參數等。
內容的位置
API (libnvim/RPC):公開低層級內部元件,或客戶端或 C 消費者所需的基本元件(例如 nvim_exec_lua())。
Lua 標準函式庫 = 建構在 API 之上的高層級功能。

命名指南 dev-naming

命名非常重要:事物的名稱是用戶使用、討論、搜尋、共用它的主要介面...標準函式庫、API 和 UI 中一致的命名有助於使用者和開發人員發現並直觀地了解相關概念(「系列」),並減少認知負擔。可發現性鼓勵程式碼重複使用,並避免冗餘、重疊的機制,從而減少程式碼表面積,並因此將錯誤降至最低...
命名慣例
一般而言,選擇名稱時請尋找先例,也就是說,請查看現有的(非已棄用)函式。特別是,請參閱下方...
dev-name-common
如果可能,請使用現有的常見 {verb} 名稱(動作)
add:附加或插入到集合中
attach:監聽某個東西以從中取得事件(TODO:重新命名為「on」?)
call:呼叫函式
cancel:取消或關閉事件或互動,通常是使用者起始且沒有錯誤。(比較「abort」,它會取消並發出錯誤/失敗訊號。)
clear:清除狀態,但不破壞容器
create:建立新的(非簡單)東西(TODO:重新命名為「def」?)
del:刪除東西(或一組東西)
detach:處置附加的監聽器(TODO:重新命名為「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」、…),它不應該是範圍。相反,範圍應該是參數(通常表現為互斥的 buf/win/… 旗標,例如 nvim_get_option_value(),或較少見地表現為 scope: string 欄位,例如 nvim_get_option_info2())。
範例: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、…)。
游標樣式(形狀、顏色)應符合使用 mode_info_set UI 事件傳遞的 'guicursor' 屬性。
將 ALT/META(macOS 上的「Option」)鍵作為 <M- 和弦發送。
將「super」鍵(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」屬性應顯示為可點擊的超連結。
主要
命令索引
快速參考