みなさん初めまして、バックオフィスグループのサーバーサイドエンジニア@lighttiger2505です。普段は弊社が提供するタクシーアプリ「GO」を利用するタクシー事業者がタクシー配車状況を確認したり、車両の情報を管理するための事業者向けの管理画面などを作っています。
本日はMoTの話とは少し離れて、私も使っているテキストエディタ。Neovimの話をします。
なぜかというと、つい先月Neovimの新バージョンv0.5がリリースされたからです。イチNeovimユーザーとして、ブログにこのネタを採用しないわけにはいけません。
NeovimとはVimのフォークプロジェクト。つまりある地点から開発ソースが分離され、個別に開発が進んでいるVimとは別のテキストエディタです。よく誤解されるのですが、いくつかの機能や思想の違いはあるもの、NeovimのほうがVimより新しいとか、優れているというわけではありません。 NeovimとVimの違いを書けば、それだけで1記事できてしまうため、詳しくは以前私が書いたNeovimがどういうプロジェクトなのかまとめという記事をご覧ください。
Neovim v0.5のリリースは、これまでのv0.4やv0.3アップデートと比較してもひときわ大きな新機能がありました。
Neovim v0.5の新機能をNeovim公式のロードマップから抜粋したものが以下の通りです。今回はこれら機能を順に解説していきたいと思います。(Extended marksについては説明しづらいため今回は割愛します)
全体が長いため、Luaに関連する話を前半、Tree-sitterやLSPクライアントなどの新機能の話を後編という形で分けさせていただきました。後編も時間に余裕があればぜひご一読ください。
Neovim v0.5リリース記念 v0.5の新機能を紹介します【後編】
まずNeovim v0.5の新機能のコアとなるLuaの組込みについてです。v0.5ではLua 5.1が組込みされており、Neovim本体のバイナリがあればLuaスクリプトを実行できるようになりました。 これによりNeovimからLuaスクリプトを呼び出しでき、さらにLuaスクリプトからNeovimの機能を呼び出しできるようになったのです。詳しくはヘルプ(:h lua)を参照してください。
Luaは軽量なスクリプト言語で、NGINXやRedisなど、C言語で記載されたプログラムの拡張機能などの実装に使われています。
すでにNeovimにはNeovimを操作するためのスクリプトとしてVim scriptがあり、Vim scriptで書かれた膨大なVimプラグイン郡という資産があります。なぜVim scriptがあるのにLuaでNeovimを操作する必要があるのでしょうか?
多くのVimユーザーが言っているVimの問題点は Vim scriptの実行速度が遅い ということです。 いわくVim scriptなら約5.5秒かかる処理をLua(LuaJIT)は約0.003秒で処理するとが可能だそうです。
もう少し話を深堀りしましょう。ではなぜNeovimの開発チームはほかの言語ではなくLuaを選択したのでしょうか? この回答は公式のFAQに『why-embed-lua-instead-of-x』で以下のように説明されています。
詳しくは本文を読んでほしいですが、要約すると。
Luaはとても小さい言語だから組込みしやすいし、LuaJITは現状のスクリプト言語のランタイムでは最速クラスで、Pythonよりずっと早い。Lua 5.1は構文の後方互換性が保証されているから、言語アップデートでプラグインが壊れる恐れはない。それにPython/Rubyを組み込むとライブラリが膨大過ぎてNeovimのバイナリがものすごく大きくなる。あとPythonやJavaScriptで制御するエディタはほかにもあるからNeovimと差別化できない。とのことです。
去年実施されたオンラインカンファレンスVimconf.liveにて、Neovimのコアメンテナーの一人TJ DeVries氏がて『Why is Lua a good fit for Neovim』という発表を行っています。この発表では、上記内容をより詳しく解説されているので、興味がある方はこちらもご覧ください。
この後紹介する新機能はすべて、Luaの組込みありきで作成をされています。まさにLuaの組込みは今回のv0.5の新機能の土台そのものでもあるのです。
一方でVimも同様の問題を抱えています。その解決策であるVim9 scriptについても紹介しておきましょう。
VimConfというカンファレンスがあります。これは年に一度開催されるVimmerによるVimmerのためのVimmerによる祭典なのですが、2018年度開催のVimConf 2018ではVimの生みの親であるBram Moolenaar氏がKeynote speakerとして参加されました。
Bram氏はここで『Vim: From hjkl to a platform for plugins』という発表をされました。発表ではVimが単純なテキストエディタからVim scriptで書かれたプラグインのプラットフォームになっていったこと、そしてVim scriptの速度の問題に行き当たったこと、そのために外部の言語を呼び出すためのインタフェースをサポートしたりしたこと。さらに根本的にVim scriptの速度の問題を解消するためにVim9 scriptの開発を考えていることを明らかにしました。
Vim9 scriptについて詳しいことは同じ2018にKeynote speakerを担当されたmattnさんのVim9 script解説記事がわかりやすいのでご参照ください。
Vim scriptの速度問題に対してVimはVim9 scriptを作成し、NeovimはLuaを採用した。このアプローチの違いは、今後大きな違いになっていくことが予想されます。
Luaの組込みという変更によって、プラグイン作成の選択肢にLuaで作成するという選択肢が新たに提示されたわけですが、Vim script以外でVimのプラグインを書く試みは、Luaが初めてではありません。Vimの時点でもif_pythやif_luaなど外部スクリプトの実行機能はありましたし。Neovimにはリモートプラグインという機能が存在し、Vim script以外でもプラグインを書くこと自体はできていました。
私も利用しているdeoplete.nvimはPython、優秀なVimのLSPクライアントとして有名なcoc.nvimはTypeScriptで記述されています。またVim scriptをメイン言語としつつも高速処理が必要な部分をRustで記述した選択的インタラクティブフィルタ(Fuzzy Finderなどとも呼ばれます)vim-clapなど、プラグイン開発者はVimというプラットフォームに複雑で高性能なプラグインを動かすため、さまざまな工夫をこらしてきました。
Vim script以外でプラグインを実装する流れがある一方、プラグインのプロセスとNeovimとの通信でRPC通信を行うなどのノウハウが必要であり、そのハードルは高かったように思います。そのためVim script以外で記述するプラグインが主流にならず、上記プラグインように自動補完やLSクライアント、FuzzyFinderなどの複雑なプラグインを構築する手法にとどまっていたように思います。
現状調べてみると、v0.5がリリースされた時点でv0.5のLua組込みを前提としてプラグインはすでに数多く開発されています。公式ニュースでも多くのプラグインが紹介されていました。
現存するLua製プラグインだけで、従来のVim script製プラグインで拡張されたVimと同等の環境が作れるほどに、盛んに開発が行われています。この事態はこれまで見られなかったことです。Luaの組込みがNeovimのプラグインプラットフォームとしての側面に大きな影響を与えたと言わざるを得ません。
これまでの説明でLuaプラグインが今後増えていき、やがてすべてのVimプラグインはLuaで書かれている時代がくるのでは?と思われた方もいるかもしれませんね。 ですがシステム開発において銀の弾丸は存在せず、当然Luaプラグインも欠点があります。それはNeovim v0.5以降でしか利用できないということです。Luaプラグインはv0.5以前のバージョンのNeovimやVimでは動作しません。
VimからNeovimへ移行したユーザもいますが、依然Vimを利用し続けるユーザも多くいます。VimもNeovim同様に進化を続けているのだから自然なことです。つまりNeovimでしか動作しないプラグインは、それだけでリーチできるユーザーを半減させていることになるのです。これは大きな欠点です。
余談ですがVim/Neovimの両方で動作するプラグインをTypeScriptで作るため、denops.vimというDenoを利用したプラグイン開発プラットフォームがgina.vimやfern.vimなどのプラグイン作者として有名な、lambdalisue氏によって開発されるなど、Luaとはまた別の選択肢も生まれています。
上記の長い説明を経て、やっと本題の一つに入れます。前述のLuaの組込みによりキーバインドなどのVimの設定を、Vim scriptではなくLuaで記述できるようになりました。驚くべきことにVim scriptを一行も書かずにVimの設定を書けるのです。
もともとVimの設定ファイルといえば、ホームディレクトリに配置された.vimrcがVim起動時に読み込みされる設定ファイルのデフォルトとされてきました。それがNeovimではinit.vim(Unixなら~/.config/nvim/init.vim)に変更されました。 そしてv0.5からはinit.lua(Unixなら~/.config/nvim/init.lua)を起動時の設定ファイルにできるようにになりました。詳細はhelp(:h init.lua)を参照してください
ここではLuaによるVimの設定のしかたについて、さわりを紹介します。 より詳しく知りたい方はnvim-lua-guideを参照するとよいでしょう。すごくわかりやすくまとまっています。
Luaで書かれたプラグインの中にはプラグインの設定がLuaでしか行えないものがあるため、Luaプラグインも利用するNeovimのヘビーユーザーであればLuaについて最低限の記述方法は覚える必要があります。 Vim scriptからLuaを実行するときはlua(:h :lua)コマンドを使用します。もし手元にv0.5以降のNeovimがあるなら以下のコマンドを実行してみてください。
:lua print(vim.inspect(package.loaded))
またVim scriptのファイル中にヒアドキュメントを用いることで、*.vimファイル内にLuaを書くことができます。私の場合、Neovimの初期化はinit.luaをつかわないでinit.vimを利用しているため、luaで設定を記述するときはヒアドキュメントを用いています。
lua << EOF
require('myluamodule')
EOF
逆にLuaからVim scriptを実行するには、Luaに提供されたAPIを使います。いくつか種類がありますが一番わかり易いのがvim.cmd()です。この関数によりVimのExコマンドやVim scriptを実行できます。
vim.cmd('echo 42')
vim.cmd('set number')
他の関数について詳しく知りたい方は:h lua-vimscriptを参照してください。
では具体的にVimのオプションをLuaに置き換える方法を見てみましょう。 例えばVimの左端に行数を表示するnumberオプションですが、Vimであれば以下のように設定します
set number
Luaだとset(:h set)コマンドによって設定していたものはvim.opt(:h vim.opt)によって設定する形になっています。
vim.opt.number = true
キーバインドの設定は、Vimならおなじみのmapコマンド(:h map-commands)を用いることになります。以下はHキーに行頭への移動を割り当ていている例です。
noremap H ^
Luaではvim.api.nvim_set_keymap(:h nvim_set_keymap)というAPIをLuaから実行することで可能になっています。
call nvim_set_keymap('n', 'H', '^', {'nowait': v:true})
LuaからVimの制御を可能にしているのはNeovimをプラグイン及び外部プロセスから操作するためのAPIがNeovimに実装されているおかげです。 Lua組込み以前から、NeovimにはVim script以外の言語でNeovimのプラグインを構築できるようにするという思想があり、そのための開発が行われてきました。LuaによってNeovimの設定ができるようになっているのはこれまでのNeovimが積み上げてきた実装の賜物と言えるのではないでしょうか。
Vimにはプラグインの管理を支援するプラグインマネージャーと呼ばれるものがあります。dein.vimやvim-plugなどはその読み込み設定をVim scriptで設定するのですが、LuaによるVimの設定ができるようになったことでLuaによってプラグインの読み込み設定を書くpacker.nvimというプラグインマネージャも登場してきました。
後編に続く