Neovim v0.5リリース記念 v0.5の新機能を紹介します【前編】

みなさん初めまして、バックオフィスグループのサーバーサイドエンジニア@lighttiger2505です。普段は弊社が提供するタクシーアプリ「GO」を利用するタクシー事業者がタクシー配車状況を確認したり、車両の情報を管理するための事業者向けの管理画面などを作っています。

本日はMoTの話とは少し離れて、私も使っているテキストエディタNeovimの話をします。

なぜかというと、つい先月Neovimの新バージョンv0.5がリリースされたからです。イチNeovimユーザーとして、ブログにこのネタを採用しないわけにはいけません。

NeovimとはVimのフォークプロジェクト。つまりある地点から開発ソースが分離され、個別に開発が進んでいるVimとは別のテキストエディタです。よく誤解されるのですが、いくつかの機能や思想の違いはあるもの、NeovimのほうがVimより新しいとか、優れているというわけではありません。 NeovimとVimの違いを書けば、それだけで1記事できてしまうため、詳しくは以前私が書いたNeovimがどういうプロジェクトなのかまとめという記事をご覧ください。

Neovim v0.5のリリースは、これまでのv0.4v0.3アップデートと比較してもひときわ大きな新機能がありました。

Neovim v0.5の新機能をNeovim公式のロードマップから抜粋したものが以下の通りです。今回はこれら機能を順に解説していきたいと思います。(Extended marksについては説明しづらいため今回は割愛します)

  • Lua remote plugin host
  • Lua user-config: init.lua
  • Treesitter syntax engine
  • LSP client for code navigation, refactoring
  • Extended marks (text properties, decorations, virtual text)

全体が長いため、Luaに関連する話を前半、Tree-sitterやLSPクライアントなどの新機能の話を後編という形で分けさせていただきました。後編も時間に余裕があればぜひご一読ください。

Neovim v0.5リリース記念 v0.5の新機能を紹介します【後編】

Lua remote plugin host

まずNeovim v0.5の新機能のコアとなるLuaの組込みについてです。v0.5ではLua 5.1が組込みされており、Neovim本体のバイナリがあればLuaスクリプトを実行できるようになりました。 これによりNeovimからLuaスクリプトを呼び出しでき、さらにLuaスクリプトからNeovimの機能を呼び出しできるようになったのです。詳しくはヘルプ(:h lua)を参照してください。

Luaは軽量なスクリプト言語で、NGINXRedisなど、C言語で記載されたプログラムの拡張機能などの実装に使われています。

なぜLuaを組込みするのか

すでに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 is a very small language, ideal for embedding. The biggest advantage of Python/Ruby/etc is their huge collection of libraries, but that isn't relevant for Nvim, where Nvim is the "batteries included" library: introducing another stdlib would be redundant.
  • Lua 5.1 is a complete language: the syntax is frozen. This is great for backwards compatibility.
  • Nvim also uses Lua internally as an alternative to C. Extra performance is useful there, as opposed to a slow language like Python. LuaJIT is one of the fastest runtimes on the planet. It is at least 10x faster than Python.
  • Python/JS cost more than Lua in terms of size and portability, and there are already numerous Python/JS-based editors. So Python/JS would make Nvim bigger and less portable, in exchange for a non-differentiating feature.

詳しくは本文を読んでほしいですが、要約すると。

Luaはとても小さい言語だから組込みしやすいし、LuaJITは現状のスクリプト言語のランタイムでは最速クラスで、Pythonよりずっと早い。Lua 5.1は構文の後方互換性が保証されているから、言語アップデートでプラグインが壊れる恐れはない。それにPython/Rubyを組み込むとライブラリが膨大過ぎてNeovimのバイナリがものすごく大きくなる。あとPythonJavaScriptで制御するエディタはほかにもあるからNeovimと差別化できない。とのことです。

去年実施されたオンラインカンファレンスVimconf.liveにて、Neovimのコアメンテナーの一人TJ DeVries氏がて『Why is Lua a good fit for Neovim』という発表を行っています。この発表では、上記内容をより詳しく解説されているので、興味がある方はこちらもご覧ください。

この後紹介する新機能はすべて、Luaの組込みありきで作成をされています。まさにLuaの組込みは今回のv0.5の新機能の土台そのものでもあるのです。

Vim script速度問題に対するVimのアプローチ

一方で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組込みがNeovimのエコシステムに与えた影響

Luaの組込みという変更によって、プラグイン作成の選択肢にLuaで作成するという選択肢が新たに提示されたわけですが、Vim script以外でVimプラグインを書く試みは、Luaが初めてではありません。Vimの時点でもif_pythif_luaなど外部スクリプトの実行機能はありましたし。Neovimにはリモートプラグインという機能が存在し、Vim script以外でもプラグインを書くこと自体はできていました。

私も利用しているdeoplete.nvimPython、優秀な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組込みを前提としてプラグインはすでに数多く開発されています。公式ニュースでも多くのプラグインが紹介されていました。

  • Plenary – A library of useful utilities for developing Neovim plugins (some of which will later be integrated into core).
  • Packer – A package manager with support for plugin dependencies, lazy-loading, and installing luarocks.
  • Telescope – A highly extendable fuzzy finder over lists.
  • Gitsigns – A plugin for showing and interacting with changes on files in a git repository (asynchronously).
  • Nvim-compe – An auto completion framework for various sources, including Neovim’s builtin LSP client.
  • Nvim-dap – A debug adapter protocol implementation for step-through debugging of your code.
  • Colorizer – A high-performance color highlighter for Neovim without any external dependencies.
  • Formatter – A plugin for asynchronously executing external formatting tools on the current buffer or range.
  • Hop.nvim - An EasyMotion-like movement plugin that does not need to mess with your buffer.
  • Neogit - A Magit-like Git interface.

現存するLuaプラグインだけで、従来のVim script製プラグインで拡張されたVimと同等の環境が作れるほどに、盛んに開発が行われています。この事態はこれまで見られなかったことです。Luaの組込みがNeovimのプラグインプラットフォームとしての側面に大きな影響を与えたと言わざるを得ません。

Luaプラグインの欠点

これまでの説明でLuaプラグインが今後増えていき、やがてすべてのVimプラグインLuaで書かれている時代がくるのでは?と思われた方もいるかもしれませんね。 ですがシステム開発において銀の弾丸は存在せず、当然Luaプラグインも欠点があります。それはNeovim v0.5以降でしか利用できないということです。Luaプラグインはv0.5以前のバージョンのNeovimやVimでは動作しません。

VimからNeovimへ移行したユーザもいますが、依然Vimを利用し続けるユーザも多くいます。VimもNeovim同様に進化を続けているのだから自然なことです。つまりNeovimでしか動作しないプラグインは、それだけでリーチできるユーザーを半減させていることになるのです。これは大きな欠点です。

別の選択肢denops.vim

余談ですがVim/Neovimの両方で動作するプラグインをTypeScriptで作るため、denops.vimというDenoを利用したプラグイン開発プラットフォームがgina.vimfern.vimなどのプラグイン作者として有名な、lambdalisue氏によって開発されるなど、Luaとはまた別の選択肢も生まれています。

Lua user-config

上記の長い説明を経て、やっと本題の一つに入れます。前述のLuaの組込みによりキーバインドなどのVimの設定を、Vim scriptではなくLuaで記述できるようになりました。驚くべきことにVim scriptを一行も書かずにVimの設定を書けるのです。

もともとVimの設定ファイルといえば、ホームディレクトリに配置された.vimrcVim起動時に読み込みされる設定ファイルのデフォルトとされてきました。それがNeovimではinit.vim(Unixなら~/.config/nvim/init.vim)に変更されました。 そしてv0.5からはinit.lua(Unixなら~/.config/nvim/init.lua)を起動時の設定ファイルにできるようにになりました。詳細はhelp(:h init.lua)を参照してください

LuaによるVimの設定のしかた

ここではLuaによるVimの設定のしかたについて、さわりを紹介します。 より詳しく知りたい方はnvim-lua-guideを参照するとよいでしょう。すごくわかりやすくまとまっています。

Vim scriptからLuaを実行

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からVim scriptを実行するには、Luaに提供されたAPIを使います。いくつか種類がありますが一番わかり易いのがvim.cmd()です。この関数によりVimのExコマンドやVim scriptを実行できます。

vim.cmd('echo 42')
vim.cmd('set number')

他の関数について詳しく知りたい方は:h lua-vimscriptを参照してください。

Luaでの実際のオプション設定

では具体的に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)というAPILuaから実行することで可能になっています。

call nvim_set_keymap('n', 'H', '^', {'nowait': v:true})

LuaからVimの制御を可能にしているのはNeovimをプラグイン及び外部プロセスから操作するためのAPIがNeovimに実装されているおかげです。 Lua組込み以前から、NeovimにはVim script以外の言語でNeovimのプラグインを構築できるようにするという思想があり、そのための開発が行われてきました。LuaによってNeovimの設定ができるようになっているのはこれまでのNeovimが積み上げてきた実装の賜物と言えるのではないでしょうか。

Lua設定用のプラグインマネージャの登場

Vimにはプラグインの管理を支援するプラグインマネージャーと呼ばれるものがあります。dein.vimvim-plugなどはその読み込み設定をVim scriptで設定するのですが、LuaによるVimの設定ができるようになったことでLuaによってプラグインの読み込み設定を書くpacker.nvimというプラグインマネージャも登場してきました。

後編に続く