init.vimとginit.vimの違い(Neovimの設定ファイル)
Neovimの設定ファイルを調べていると、init.vimと並んでginit.vimという名前を見かけることがあります。両方とも「設定ファイル」ではありますが、担っている役割は別物なので、混同しないよう整理しておきます。そもそもNeovimがVimと何が違うのかを先に押さえておくと、この2つの使い分けも腑に落ちます。
この記事に載せた挙動は、macOS上のNVIM v0.12.4とVim 9.1(/usr/bin/vim)で確かめました。NeovimはXDG_CONFIG_HOMEで設定ディレクトリを一時ディレクトリに差し替え、nvim -n -i NONEから起動しています。貼ってある出力はその実行結果です。手元にGUIクライアントが無いので、GUI側で起動したときの値だけは未確認とし、出典を添えてあります。
目次(8項目)
init.vim:設定全般を書くファイル
Neovim公式ドキュメントでは次のように説明されています。
A file containing initialization commands is generically called a "vimrc" or config file. It can be Vimscript ("init.vim") or Lua ("init.lua"), but not both.
要は、Vimでいう.vimrcにあたる設定ファイルで、1行1行がExコマンドとして実行されます。配置場所はOSによって異なります。
- Unix系:
~/.config/nvim/init.vim - Windows:
~/AppData/Local/nvim/init.vim
環境変数XDG_CONFIG_HOMEを設定すれば、$XDG_CONFIG_HOME/nvim/init.vimとして任意の場所に置けます。この探索先はstdpath("config")が返す1つのディレクトリで、手元で/tmp/vbcfgを渡すと/tmp/vbcfg/nvimが返り、runtimepathの先頭も同じでした。Lua設定なら同じ役割をinit.luaが担います。
ただしNeovimが読むのはinit.vimとinit.luaのどちらか一方だけです。両方を同じディレクトリに置いて起動すると、設定を1行も読まないうちにE5422で止まります。
# 同じディレクトリに init.vim と init.lua を置いて起動する
$ XDG_CONFIG_HOME=/tmp/vbcfg nvim -n -i NONE --headless -c 'qa!'
E5422: Conflicting configs: "/tmp/vbcfg/nvim/init.lua" "/tmp/vbcfg/nvim/init.vim"
2つを読む順番を決める根拠がないので、Neovimは推測せずエラーで止まります。この動きは設定ファイルの配置場所でも扱っています。どちらが採用されたか迷ったときは$MYVIMRCが早く、読まれたファイルのフルパスが入っていました。
ginit.vim:GUI専用の起動設定を書くファイル
ginit.vimはinit.vimと同じディレクトリに置き、GUIクライアント固有の起動コマンドを書く場所として使われてきました。読まれるのはinit.vimの後です。押さえておきたいのは、それを読む役がNeovim本体ではない点で、端末でnvimを起動してもginit.vimは読まれず、変数は未定義のままでした。
# init.vim と ginit.vim に別々の変数を書いて、端末の nvim で起動する
$ mkdir -p /tmp/vbcfg/nvim
$ printf 'let g:from_init = 1\n' > /tmp/vbcfg/nvim/init.vim
$ printf 'let g:from_ginit = 1\n' > /tmp/vbcfg/nvim/ginit.vim
$ XDG_CONFIG_HOME=/tmp/vbcfg nvim -n -i NONE --headless \
-c 'echo "init=" . get(g:,"from_init","none") . " ginit=" . get(g:,"from_ginit","none")' \
-c 'echo "gui_running=" . has("gui_running")' -c 'qa!'
init=1 ginit=none
gui_running=0
tmuxの中で画面付きの端末UIとして読ませても結果は同じで、ginitはnoneのままでした。理由はNeovimのGUIの作りにあります。nvim-qtに同梱のヘルプは、GUIの立場を次のように説明しています。
In Neovim the GUI is an external process that communicates with the `nvim` process using the |msgpack-rpc-api|. This separation means the GUI is just a particular type of plugin, as a consequence some functionality that was available in Vim is a NOOP in Neovim, or is still in the process of being specified.
GUIが外部プロセスのプラグインである以上、GUI用の設定を読むのもGUI側の仕事です。ginit.vimはNeovimにとって特別な名前ではなく、stdpath("config")の直下に置かれた普通のVim scriptにすぎません。そこはruntimepathの先頭なので、クライアントは起動後に:runtimeで名前を指定して読めます。端末のNeovimでも手で打てば同じです。
$ XDG_CONFIG_HOME=/tmp/vbcfg nvim -n -i NONE --headless \
-c 'runtime ginit.vim' \
-c 'echo "ginit=" . get(g:,"from_ginit","none")' -c 'qa!'
ginit=1
この仕組みが分かるとginit.luaの扱いも説明できます。ginit.vimとginit.luaを同じディレクトリに置いてもE5422は出ません。どちらもNeovim本体が探しにいくファイルではないからで、実際に両方を置いて端末で起動すると2つとも読まれずに終わりました。nvim-qtのREADMEと同梱ヘルプが名前を挙げているのはginit.vimだけなので、Lua中心の設定ならGUI用の分岐もinit.lua側へ寄せるほうが確実です。
GUI用のコマンドをinit.vimに書くと端末で止まる
ginit.vimが読まれないと気づいて中身をそのままinit.vimへ移すと、今度は端末側で困ります。GuiFontのようなコマンドは、GUIクライアントが読み込むシムのプラグインが定義するもので、端末のNeovimには存在しないためです。init.vimの2行目にGuiFont Monaco:h13を書いて起動すると、毎回エラーが出ました。
$ XDG_CONFIG_HOME=/tmp/vbcfg nvim -n -i NONE --headless -c 'qa!'
Error in /private/tmp/vbcfg/nvim/init.vim:
line 2:
E492: Not an editor command: GuiFont Monaco:h13
nvim-qtのヘルプは、シムが読み込まれたかどうかを示すg:GuiLoadedで囲む書き方を例に載せています。端末で起動した手元のNeovimではexists("g:GuiLoaded")が0だったので、この形にしておけば端末では素通りします。
" GUI側のシムが読み込まれたときだけ実行する
if exists('g:GuiLoaded')
GuiFont Monaco:h13
endif
クライアントを名前で見分けたいときは、同じシムがGuiName()を用意しています。nvim-qtから呼べばnvim-qtが返るとヘルプにありますが、手元にクライアントが無いため未確認です。
現在はUIEnterイベントが推奨される
ginit.vimという仕組み自体は今も残っていますが、GUI固有の設定を書く方法としては、現在はUIEnterautocommandのほうが汎用的で推奨されています。UIがNeovimにアタッチしたタイミングで発火し、init.vim(またはinit.lua)側に一元化できます。
取り違えやすいのは、UIEnterがGUI専用のイベントではない点です。tmuxの中でnvimを起動し、init.vimに発火回数を数えるautocommandを置いて画面から読み出すと、GUIをまったく使っていないのにUIEnter発火回数=1 chan=1が返りました。端末UIのアタッチでも発火するので、書いただけでは分岐にならず、中でGUIかどうかを判定する必要があります。
" 端末UIでも UIEnter は発火するので、中でGUIかどうかを見る
autocmd UIEnter * if has('gui_running') | set guifont=Monaco:h13 | endif
判定に何を使うかはバージョンで変わります。公式ヘルプが載せているUIEnterの例はnvim_list_uis()からrgbフィールドを取り出しますが、これはGUIの判定には使えません。tmuxの端末UIで見るとrgbはtrueで、24bitカラーの端末とGUIが区別できないためです。端末で動いていることは、同じ辞書の"term_name": "screen-256color"や"stdout_tty": trueから読み取れます。
Neovim 0.9からはhas("gui_running")がGUIの判定に使えます。0.9のニュースファイルに「a GUI (not the TUI) is attached to Nvim」を調べる手段として明記されており、クライアントごとの変数名を覚えずに済みます。手元のNVIM v0.12.4では端末UIでも--headlessでも0が返りました。GUIで1になる側は未確認で、根拠はこのニュースファイルの記述だけです。
公式ヘルプによればUIEnterはUIがアタッチするたびに起きるので、UIを切り離して繋ぎ直す仕組みを使う環境では起動時の1回だけとは限りません。
両者の違いをまとめると次のとおりです。
| ginit.vim | UIEnterイベント | |
|---|---|---|
| 設定ファイル | init.vimとは別ファイル | init.vim(またはinit.lua)に一元化 |
| 対応範囲 | 特定のGUIクライアント専用 | どのGUIクライアントでも共通 |
| 複数GUIの使い分け | 設定が分散しやすい | 1つの設定で完結しやすい |
GUIクライアントの選択肢
Neovim自体はターミナル上で動くのが基本で、GUIで使うにはクライアントを別に入れます。古くからあるnvim-qtのほか、滑らかな描画とリガチャ表示に強いNeovide、macOS向けのVimRなどがあります。設定の書き方が変わるのは、目印になる変数と、ginit.vimを読みにいくかどうかが違うためです。
| クライアント | 目印になる変数 | ginit.vimの読み込み |
|---|---|---|
| nvim-qt | g:GuiLoaded | 読む(READMEが配置場所を案内) |
| Neovide | g:neovide | 公式ドキュメントに記述なし(未確認) |
| 端末UI | なし | 読まない(実測) |
nvim-qtのREADMEはginit.vimの置き場所をOSごとに挙げ、GUI固有のオプションはこのファイルから設定するよう書いています。一方のNeovideの設定ドキュメントはg:neovideだけを案内し、ginit.vimには触れていません。VimRは未確認のため保留します。この不揃いを覚えずに済ませたいなら、変数名ではなくhas("gui_running")で分岐させるほうが移植性が高くなります。
Vimの.vimrcと.gvimrcに当てはめる
この分かれ方はVimから引き継いだ形です。Vimでは端末版が.vimrcを読み、GUI版のgvimは.vimrcのあとに.gvimrcを読みます。ginit.vimはその.gvimrcにあたる位置づけで、Neovimのヘルプでもginit.vimとgvimrcと$MYGVIMRCが同じ見出しのタグとして並べられています。
| Vim | Neovim | |
|---|---|---|
| 全般の設定 | .vimrc | init.vim または init.lua |
| GUI固有の設定 | .gvimrc | ginit.vim |
| GUI設定を読む主体 | Vim本体 | GUIクライアント |
違いが出るのは3行目です。Vimでは.gvimrcを読むのもVim自身なので、GUI版で起動すれば必ず読まれます。Neovimは読むかどうかがクライアント側の実装なので、乗り換えた先が読まなければ黙って無視されます。GUI専用の設定の置き場所は、gVimのフォントとサイズの変え方と並べると違いがはっきりします。
端末のnvimでginit.vimに書いた設定が効かず、値を変えては起動し直すのを何度か繰り返したことがあります。書き方の間違いを疑ってサイズの表記を何通りか試し、ファイルの文字コードまで確かめたのですが、:scriptnamesを出したら一覧にginit.vimが載っておらず、そもそも一度も読まれていないと分かりました。中身を疑う前に読まれているかを見ればよかった、という話です。
まとめ
init.vimはNeovimが自分で探して読む設定ファイル、ginit.vimはGUIクライアントが読みにいくファイルで、後者はNeovim本体にとって特別な名前ではありません。端末で読まれないことも、乗り換えた先で黙って無視されることも、この一点から説明がつきます。新しく組むならinit.vim側にUIEnterとhas("gui_running")で分岐を書いておくと、クライアントを入れ替えても持ち運べます。