開發
Nvim 的 :help
頁面,是從 原始碼 使用 tree-sitter-vimdoc 解析器 產生 而來。
此參考描述了開發 Nvim 應用程式或 Nvim 本身時的設計限制和指南。有關 Nvim 架構和內部概念的討論,請參閱
dev-arch。
最重要的事優先(大致上)。有些項目會衝突;這是故意的。必須找到平衡點。
Nvim 的 Neo 部分應該使其成為更好的 Vim,而不是成為一個完全不同的編輯器。
在品味方面,偏好 Vim/Unix 的傳統。如果沒有相關的 Vim/Unix 傳統,則考慮「常見情況」。
可以新增的功能沒有限制。根據 (1) 使用者的要求、(2) 實作所需的努力程度以及 (3) 實際上有人實作來選擇新功能。
向後相容性是一個功能。特別是 RPC API 永遠不應中斷。
沒有文件的功能是無用的功能。新功能的修補程式必須包含文件。
文件應詳盡且易於理解。使用範例。
不要讓文字不必要地冗長。文件越少,表示項目越容易找到。
保持 Nvim 小巧快速。這會直接影響其多功能性和可用性。
電腦每年都變得更快更大。Vim 也可以成長,但成長速度不應快於電腦的成長速度。保持 Vim 在較舊的系統上可用。
許多使用者經常從 Shell 啟動 Vim。啟動時間必須短。
指令必須有效率地運作。它們消耗的時間必須盡可能短。有用的指令可能需要較長時間。
別忘了有些人透過慢速連線使用 Vim。盡量減少通訊開銷。
Vim 是其他元件中的一個元件。不要將其變成一個龐大的應用程式,而是讓它能與其他程式良好地協同工作(「可組合性」)。
原始碼不應變得混亂。它應該是可靠的程式碼。
以有用的方式使用註解!引用函式名稱和參數名稱沒有用。請說明它們的用途。
移植到另一個平台應該很容易,而無需變更太多與平台無關的程式碼。
使用物件導向的精神:將資料和程式碼放在一起。盡量減少知識分散到程式碼的其他部分。
Nvim 不是作業系統;相反地,它應該與其他工具組合或作為元件託管。Marvim 曾經說過:「與 Emacs 不同,Nvim 不包括廚房水槽...但它很適合水管工程。」
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
「直接說出來」。在所有文件(文件字串、使用者手冊、網站資料、新聞通訊...)中避免含糊不清、口語化的措辭。不要拐彎抹角。歡迎謹慎地使用個性和風格,但一般來說,請針對讀者的時間和精力進行最佳化:要「精確而簡潔」。
偏好主動語態:「Foo 執行 X」,而不是「X 由 Foo 執行」。
以現在時(「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 的差異;不需要其他區別。
使用一致的語言。
help 標籤中的「terminal」一律表示「嵌入式終端機模擬器」,而不是「使用者主機終端機」。
使用「tui-」作為與主機終端機相關的 help 標籤的前綴,並在散文中盡可能使用「TUI」。
有關 Lua 文件應放置位置的粗略指南
Nvim API 函式 vim.api.nvim_*
應在 api.txt
中。
如果模組很大且與通用和較低層級的 Lua 功能無關,則強烈建議將其分開。範例:treesitter.txt
否則,將它們新增至 lua.txt
嚴格的「vimdoc」子集
使用以「-」或「•」為前綴的清單(如這個!),適用於您不希望自動換行的相鄰行。清單一律以「流動」版面配置(軟換行)而非傳統 :help
文件中常見的預格式化(硬換行)版面配置呈現。
以空白行分隔內容區塊(段落)。
不要在隨機位置使用縮排,這會阻止頁面使用「流動」版面配置。如果您需要預格式化的區段,請將其放在以「>」開頭的
help-codeblock 中。
參數和欄位的文件記載為 {foo}
。
選擇性參數和欄位的文件記載為 {foo}?
。
Nvim API 文件以文件字串(文件註解)的形式存在於原始碼中,位於函式定義上。
api :help 是從 src/nvim/api/*.c 中定義的文件字串產生。
文件字串格式
行以 ///
開頭
特殊符號以 @
開頭,後跟符號名稱:@note
、@param
、@return
支援 Markdown。
標籤寫為 [tag]()
。
參考寫為 [tag]
使用 ``` 表示程式碼範例。程式碼範例可以標註為 vim
或 lua
範例:
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-docLua 文件以文件字串的形式存在於原始碼中,位於函式定義上。
lua-vim :help 是從文件字串產生。
文件字串格式
支援 Markdown。
標籤寫為 [tag]()
。
參考寫為 [tag]
使用 ``` 表示程式碼範例。程式碼範例可以標註為 vim
或 lua
使用
@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
保持核心 Lua 模組
lua-stdlib 簡單。避免精巧的 OOP 或虛擬 OOP 設計。外掛程式作者只想要呼叫的函式,而不是大型、精美的繼承階層。
避免在 Nvim 標準函式庫中要求或回傳特殊物件。單純的表格或值更容易序列化、更容易從字面值建構、更容易檢查和列印,並且本質上與所有 Lua 外掛相容。(此指南不適用於不透明的、非資料物件,例如 vim.cmd
。)
標準函式庫函式應遵循這些常見模式
接受可迭代物件,而不僅限於表格。
注意: 在某些情況下,可迭代物件沒有意義,例如 spair() 依定義對輸入進行排序,因此沒有理由接受可迭代物件,因為輸入需要「實體化」;它不能在「串流」上操作。
如果可能,回傳可迭代物件(產生器)而不是表格。
如果函式旨在用於
for-in 迴圈中,請模仿 pairs() 或 ipairs() 介面。
dev-error-patterns若要將失敗傳達給消費者,請從這些模式中選擇(依偏好順序):1.
retval, errmsg
第 1 項的特殊情況。當只有一個「不存在」的情況時(例如,快取查閱、字典查閱)。3. error("no luck")
用於無效狀態(「不應該發生」),當失敗是例外情況,或在消費者不太可能以有意義的方式處理它的低層級。優點是傳播是免費發生的,並且更難以意外吞下錯誤。(例如,在沒有檢查回傳值的情況下使用 uv_handle/pipe:write()
是很常見的。)4. on_error
參數
用於非同步和「訪問者」遍歷圖形,在工作繼續時可能會收集許多錯誤。5. vim.notify
(有時帶有可選的 opts.silent
(非同步,訪問者 ^))
高層級/應用程式層級訊息。最終使用者直接調用這些訊息。
如果可能,這些模式適用於 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})
查詢功能的緩衝區本機狀態。
新增 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 之上的高層級功能。
命名非常重要:事物的名稱是用戶使用、討論、搜尋、共用它的主要介面...標準函式庫、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:緩衝區
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}
。
對於給定的主題,優先新增單一的 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_xx
、
nvim_buf_xx
、
nvim_win_xx
和
nvim_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-clientAPI 用戶端封裝 Nvim
API,以針對其各自的平台提供慣用的「SDK」(請參閱
術語)。您可以為您最喜歡的平台或程式設計語言建構新的 API 用戶端。
API 客戶端套件不應使用像「neovim」或「python-client」這樣含糊不清的名稱。請使用「nvim」作為前綴/後綴,並遵循生態系統慣例加上其他識別符。
例如,Python 套件的名稱中通常帶有「py」,因此「pynvim」是一個好名稱:它符合習慣用法且明確。如果套件名稱為「neovim」,會讓使用者感到困惑,並使文件和討論變得複雜。
API 客戶端套件名稱的範例
✅ OK:nvim-racket
✅ OK:pynvim
❌ NO:python-client
❌ NO:neovim_
使用至少實作 MessagePack 規格第 5 版的 MessagePack 程式庫,該版本支援 Nvim 使用的 BIN 和 EXT 類型。
使用單執行緒的事件迴圈程式庫/模式。
為用於實作客戶端的語言使用纖程/協程程式庫。這些程式庫可大幅簡化並發性,並允許程式庫在非阻塞事件迴圈之上公開阻塞式 API,而無需搶佔式多工處理帶來的複雜性。
不要假設 RPC 請求的回應順序。
客戶端應預期收到請求,並且必須立即處理,因為 Nvim 在等待客戶端回應時會被阻塞。
客戶端應預期收到通知,但這些通知可以「儘快」(而不是立即)處理,因為它們不會阻塞 Nvim。
外部 UI 應注意
api-contract。特別是,未來版本的 Nvim 可能會將新項目新增至現有事件。API 具有很強的向後相容性,但如果將新的(可選)欄位新增至現有事件,客戶端不得中斷。
外部 UI 預期會實作這些常見功能
游標樣式(形狀、顏色)應符合使用 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 預設值、外掛程式等發生潛在衝突。