開発
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が使えるようにしてください。
多くのユーザーはシェルからVimを頻繁に起動します。起動時間は短くなければなりません。
コマンドは効率的に動作する必要があります。コマンドが消費する時間は可能な限り短くする必要があります。便利なコマンドは時間がかかる場合があります。
遅い接続でVimを使用している人がいることを忘れないでください。通信オーバーヘッドを最小限に抑えてください。
Vimは他のコンポーネントの中の1つのコンポーネントです。巨大なアプリケーションに変えるのではなく、他のプログラムとうまく連携するようにしてください(「composability:構成可能性」)。
ソースコードがめちゃくちゃになってはいけません。信頼性の高いコードでなければなりません。
コメントは役に立つ方法で使用してください!関数名と引数名を引用するのは役に立ちません。それらが何のためにあるのかを説明してください。
プラットフォームに依存しないコードをあまり変更することなく、別のプラットフォームへの移植を容易にする必要があります。
オブジェクト指向の精神を使用してください。データとコードをまとめてください。コードの他の部分に広がる知識を最小限に抑えてください。
Nvimはオペレーティングシステムではありません。代わりに、他のツールと組み合わせて使用するか、コンポーネントとしてホストする必要があります。Marvimはかつてこう言いました。「Emacsとは異なり、Nvimにはキッチンシンクは含まれていません...しかし、配管には適しています。」
Nvimの主な目標は、コアに関する特別な知識がなくてもエディターを拡張できるようにすることです。一部のコア機能は、外部スクリプトとして実装された「プロバイダー」に委任されます。
例
1. Vimのソースコードでは、クリップボードロジックは1k行以上のCソースコード(ui.c)を占めており、xclipやpbcopy/pbpasteなどのシェルコマンドで実行される2つのタスクを実行します。
2. Pythonスクリプトのサポート:Vimには、Pythonインタープリターの埋め込み専用の3つのファイルがあります。if_python.c、if_python3.c、およびif_py_both.hです。これらのファイルは合計で約9.5k行のCソースコードになります。対照的に、Nvim Pythonスクリプトは、約2k行のPythonで実装された外部ホストプロセスによって実行されます。
プロバイダーフレームワークはCからVimscriptを呼び出します。これはeval.cの2つの関数で構成されています
eval_call_provider({name}
, {method}
, {arguments}
, {discard}
): {method}
と {arguments}
を使用して provider#{name}#Call
を呼び出します。 {discard}
がtrueの場合、プロバイダーによって返される値は破棄され、空の値が返されます。
eval_has_provider(
{name}
): プロバイダースクリプトによって2に設定する必要がある
g:loaded_{name}_provider
変数をチェックして、「有効で機能している」ことを示します。機能が利用可能かどうかを確認するために
has() によって呼び出されます。
たとえば、Pythonプロバイダーは「autoload/provider/python.vim」スクリプトによって実装されます。このスクリプトは、有効な外部Pythonホストが見つかった場合にのみ g:loaded_python_provider
を2に設定します。その後、has("python")
はPythonのサポートが機能しているかどうかを反映します。
provider-reloadGUIまたは他のアプリケーションがプロバイダーの「リロード」を強制する場合があります。プロバイダーをリロードするには、その「loaded」フラグを未定義にしてから、
:runtime を使用してリロードします
:unlet g:loaded_clipboard_provider
:runtime autoload/provider/clipboard.vim
「ただそれを言ってください」。すべてのドキュメント(docstring、ユーザーマニュアル、Webサイト資料、ニュースレターなど)で、曖昧で口語的な表現は避けてください。言葉を濁さないでください。個性と風味は、控えめに使用すれば歓迎されますが、一般的には、読者の時間とエネルギーを最適化するために、「正確でありながら簡潔」にしてください。
能動態を優先してください。「FooはXを実行する」であり、「XはFooによって実行される」ではありません。
命令形(「取得する」)ではなく、現在形(「取得する」)でdocstring(インラインコメントとは対照的に)を書いてください。これは、「どのように」ではなく「何を」を記述することにより、あいまいさを軽減し、明確さを向上させる傾向があります。
✅ OK:
/// Gets a highlight definition.
❌ NO:
/// Get a highlight definition.
あいまいさを避けるために必要な場合を除き、docstringを「The」または「A」で始めないでください。これは視覚的な助けとなり、ノイズを低減します。
✅ OK:
/// @param dirname Path fragment before `pend`
❌ NO:
/// @param dirname The path fragment before `pend`
Vimとの違い
ヘルプタグに「nvim-」というプレフィックスを付けないでください。
vim_diff.txt を使用して、Vimとの違いをカタログ化します。他の区別は必要ありません。
一貫した言語を使用してください。
ヘルプタグの「terminal」は常に「組み込み端末エミュレーター」を意味し、「ユーザーホスト端末」を意味しません。
ホスト端末に関連するヘルプタグにはプレフィックス「tui-」を使用し、可能であれば散文には「TUI」を使用します。
Luaドキュメントの配置に関する大まかなガイドライン
Nvim API関数 vim.api.nvim_*
は api.txt
に配置する必要があります。
モジュールが大きく、汎用的で低レベルのLua機能に関連しない場合は、分離の有力な候補です。例:treesitter.txt
それ以外の場合は、lua.txt
に追加します
厳密な「vimdoc」サブセット
自動ラッピングしたくない隣接する行には、「-」または「•」で始まるリスト(これのように!)を使用します。リストは常に、「フロー」レイアウト(ソフトラップ)でレンダリングされます。従来の:helpドキュメントで一般的なプリフォーマット済み(ハードラップ)レイアウトではありません。
コンテンツのブロック(段落)は空白行で区切ります。
ランダムな場所にインデントを使用しないでください。これにより、ページで「フロー」レイアウトを使用できなくなります。プリフォーマットされたセクションが必要な場合は、 ">" で始まる
help-codeblock に配置します。
パラメータとフィールドは {foo}
としてドキュメント化されます。
オプションのパラメータとフィールドは {foo}?
としてドキュメント化されます。
Nvim APIドキュメントは、関数定義のdocstring(ドキュメントコメント)としてソースコードに存在します。
api :helpは、src/nvim/api/*.cで定義されているdocstringから生成されます。
Docstringの形式
行は ///
で始まります
特殊トークンは @
で始まり、その後にトークン名が続きます:@note
、@param
、@return
Markdownがサポートされています。
タグは [tag]()
として記述されます。
参照は [tag]
として記述されます
コードサンプルには ``` を使用します。コードサンプルには vim
または lua
として注釈を付けることができます
例:
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 docstring
dev-lua-docLuaドキュメントは、関数定義のdocstringとしてソースコードに存在します。
lua-vim :helpはdocstringから生成されます。
Docstringの形式
Markdownがサポートされています。
タグは [tag]()
として記述されます。
参照は [tag]
として記述されます
コードサンプルには ``` を使用します。コードサンプルには vim
または lua
として注釈を付けることができます
関数が「安定」になった
api-level を示すには、
@since <api-level>
を使用します。
<api-level>
が現在の安定版リリース(または0)よりも大きい場合、「実験的」とマークされます。
apiレベルとNvimバージョンのマッピングについては、scripts/util.luaを参照してください。
ドキュメントの生成を防ぐには、@nodoc
を使用します。
@class
ブロックを
@param
ブロックにインライン化するには、
@inlinedoc
を使用します。例:
--- 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のvim.pasteを装飾するdocstringから次のように生成されます
--- 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標準ライブラリ設計ガイドライン dev-lua
コアLuaモジュール
lua-stdlib をシンプルに保ちます。精巧なOOPまたは疑似OOP設計は避けてください。プラグインの作成者は、呼び出す関数が必要であり、大きく、派手な継承階層は必要ありません。
Nvim stdlibで特別なオブジェクトを要求または返さないでください。プレーンテーブルまたは値は、シリアル化が容易で、リテラルから構築しやすく、検査と印刷が容易であり、すべてのLuaプラグインと本質的に互換性があります。(このガイドラインは、vim.cmd
のような不透明な非データオブジェクトには適用されません。)
stdlib関数は、次の一般的なパターンに従う必要があります
テーブルだけでなく、反復可能オブジェクトを受け入れます。
注:場合によっては、反復可能オブジェクトは意味がありません。たとえば、spair() は定義上入力をソートするため、反復可能オブジェクトを受け入れる理由はありません。入力は「具体化」する必要があるためです。「ストリーム」では操作できません。
可能であれば、テーブルではなく反復可能オブジェクト(ジェネレーター)を返します。
関数が
for-in ループで使用されることを意図している場合は、pairs() または ipairs() インターフェースを模倣します。
dev-error-patternsコンシューマーに障害を伝えるには、次のパターンから選択します(優先順位順)。1.
retval, errmsg
1の特殊なケース。「存在しない」という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())は、可能であれば(または「継続コールバック」—正確に1回呼び出される関数の場合には常に)、最後のパラメーター(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」の引数がありますが、「command」の引数は、Vimscript関数名をサポートせず、文字列「callback」の引数をExコマンド(Vimscript関数を呼び出すことができる)として扱うことで削除できます。「buffer」の引数も、数値「pattern」をバッファ番号として扱うことで削除できます。
カーソル位置、現在のバッファなどに依存する関数は避けてください。代わりに、関数は位置パラメータ、バッファパラメータなどを取る必要があります。
API (libnvim/RPC): 低レベルの内部構造、またはクライアントやCのコンシューマーが必要とする基本的なもの(nvim_exec_lua()
など)を公開します。
Lua標準ライブラリ = APIの上に構築された高レベルの機能。
命名は非常に重要です。ものの名前は、それを使用し、議論し、検索し、共有するための主要なインターフェースです... 標準ライブラリ、API、およびUIでの一貫した命名は、ユーザーと開発者の両方が関連する概念(「ファミリ」)を発見して直感的に理解するのに役立ち、認知的負担を軽減します。発見可能性はコードの再利用を促進し、同様に冗長で重複するメカニズムを回避します。これにより、コードの表面積が削減され、バグが最小限に抑えられます...
一般的に、名前を選択する際には前例を探してください。つまり、既存の(非推奨ではない)関数を見てください。特に、以下を参照してください...
開発-名前-共通可能な場合は、既存の共通の
{動詞}
名(アクション)を使用してください。
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_{動詞}: ベストエフォート操作。失敗すると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関数では
{トピック}
に一貫した名前を使用してください。バッファは、一部の場所では「buffer」、他の場所では「buf」と呼ばれるのではなく、どこでも「buf」と呼ばれます。
buf: バッファ
cmd: コマンド
cmdline: コマンドラインUIまたは入力
fn: 関数
hl: ハイライト
pos: 位置
proc: システムプロセス
tabpage: タブページ
win: ウィンドウ
これらの非推奨の名詞を使用しないでください
buffer 「buf」の代わりに使用してください
callback on_fooの代わりに使用してください
command 「cmd」の代わりに使用してください
window 「win」の代わりに使用してください
開発-名前-イベントイベント処理コールバックと、そのようなハンドラを「登録」するためのインターフェース(on_key)に名前を付けるには、「on_」プレフィックスを使用します。これらの関連概念の命名規則が混同されるのを避けるために、二重の性質は許容されます。
エディター
イベント(自動コマンド)は、歴史的に次のように命名されています。
{Noun}{Event}
API (RPC) イベントに名前を付けるには、この形式を使用してください。
nvim_{noun}_{event-name}_event
例
nvim_buf_changedtick_event
開発-api-名前新しいRPC
API 関数に名前を付けるには、この形式を使用してください。
nvim_{topic}_{verb}_{arbitrary-qualifiers}
概念が複数の「スコープ」に適用されることは決してないと確信している場合を除き、新しいnvim_buf/nvim_win/nvim_tabpage APIを追加しないでください。つまり、
{トピック}
はスコープ(buf/win/tabpage/global)に作用するトピック(「ns」、「extmark」、「option」など)である必要があり、スコープ自体であってはなりません。代わりに、スコープはパラメータである必要があります(通常は
nvim_get_option_value()のような相互排他的なbuf/win/...フラグとしてマニフェストされるか、
nvim_get_option_info2()のような
scope: string
フィールドとしてマニフェストされることはあまりありません)。
例: nvim_get_keymap('v')
はグローバルコンテキストで動作します(最初のパラメータはバッファではありません)。「get」動詞は、指定されたフィルターパラメータに一致するものをすべて取得することを示します。nvim_get_keymap('')
(空のフィルター)はすべてのアイテムを返すため、「list」動詞は不要です。
例: nvim_buf_del_mark
はBuffer
オブジェクト(最初のパラメータ)に作用し、「del」{動詞}
を使用します。
特定のトピックに対して単一のnvim_{トピック}_{動詞}_…
インターフェースを追加することを優先してください。
例
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-クライアントAPIクライアントは、Nvim
API をラップして、それぞれのプラットフォームに慣用的な「SDK」を提供します(
専門用語を参照)。お気に入りのプラットフォームまたはプログラミング言語用の新しいAPIクライアントを構築できます。
クライアントは
nvim_error_event通知を処理する必要があります。これは、nvimへの非同期リクエストが拒否されたか、エラーが発生した場合に送信されます。
APIクライアントパッケージの名前は、「neovim」や「python-client」のようなあいまいな名前にしないでください。エコシステムの規則に従って、他の識別子のプレフィックス/サフィックスとして「nvim」を使用してください。
たとえば、Pythonパッケージの名前には「py」が含まれる傾向があるため、「pynvim」は良い名前です。慣用的で明確です。パッケージの名前が「neovim」の場合、ユーザーが混乱し、ドキュメントや議論が複雑になります。
APIクライアントパッケージ名の例
✅ OK: nvim-racket
✅ OK: pynvim
❌ NO: python-client
❌ NO: neovim_
トランスポート層をライブラリの残りの部分から分離します。
rpc-接続少なくともMessagePack仕様のバージョン5を実装しているMessagePackライブラリを使用してください。これは、Nvimで使用されているBINおよびEXTタイプをサポートしています。
シングルスレッドのイベントループライブラリ/パターンを使用してください。
クライアントを実装するために使用されている言語のファイバー/コルーチンライブラリを使用してください。これらは並行性を大幅に簡素化し、プリエンプティブマルチタスキングに伴う複雑さなしに、ライブラリがノンブロッキングイベントループの上にブロッキングAPIを公開できるようにします。
RPCリクエストに対するレスポンスの順序について、何も仮定しないでください。
クライアントはリクエストを予期する必要があります。リクエストは、クライアントのレスポンスを待機している間、Nvimがブロックされるため、すぐに処理する必要があります。
クライアントは通知を予期する必要がありますが、通知はNvimをブロックしないため、すぐにではなく「できるだけ早く」(ASAP) 処理できます。
外部UIは
api-contractを認識している必要があります。特に、将来のバージョンのNvimでは、既存のイベントに新しい項目が追加される可能性があります。APIは強力な後方互換性を備えていますが、既存のイベントに新しい(オプションの)フィールドが追加された場合でも、クライアントが壊れてはいけません。
外部UIは、以下の一般的な機能を実装することが期待されています。
カーソルのスタイル(形状、色)は、mode_info_set UIイベントで配信される
'guicursor'プロパティに準拠する必要があります。
ALT/METAキー(macOSでは「Option」キー)は、
<M-コードとして送信してください。
「スーパー」キー(Windowsキー、Appleキー)は、
<D-コードとして送信してください。
Nvimのキーマップスペースと競合するマッピングは避けてください。GUIには、Nvimのデフォルト、プラグインなどと競合する可能性のない、多くの新しいコード(<C-,>
<C-Enter>
<C-S-x>
<D-x>
)とパターン(「shift shift」)があります。