読者です 読者をやめる 読者になる 読者になる

blog.syfm

徒然なるままに考えていることなどを書いていくブログ

Neovim のリモートプラグインを利用して好きな言語でプラグインを書く

この記事は Aizu Advent Calendar 2016 (Qiita) の 17日目の記事です。
前の人は @acomagu1 さんです。 次の人は @noah_orberg さんです。

f:id:ktr_0731:20161209001057p:plain

Neovim

突然ですが、Neovim はご存知でしょうか。
Vim は言わずもがな非常によく知られたテキストエディタの一つですが、Neovim はその Vim をフォークし、モダンアーキテクチャで再実装したテキストエディタです。

数十年前からの秘伝のタレは美味しいかもしれませんが、数十年前から続くソースコードが美味しいはずもなく、現在ではメンテナンスが大変なようです。
Neovim はメンテナンスの簡略化、効率化も図っており、自身の大きな特徴の一つとなっています。
ソースコードはもちろん OSS で公開されています。

github.com

新しいプラグイン/GUIアーキテクチャ

他の特徴としては Neovim のリモートプラグイン (rplugin) という、拡張性を高めるためのアーキテクチャがあります。

リモートプラグインと聞くとなんだか分かりにくいですが、プラグインを Neovim 本体とは別のプロセスで稼働させて、msgpack-RPC を利用して Neovim と相互にやりとりをしています。
msgpack-RPC でできることは以下の通りです。詳しくは 公式 Wiki を参照して下さい。

  • Vim コマンドの実行
  • Vim script の評価
  • バッファやウインドウ、タブの操作
  • Neovim 上のイベントの受け取り、ハンドリング

これを利用してGUI の部分を外部へ分離することで、Neovim コアのコードを軽量化し、メンテナブルにしているようです。
今回はリモートプラグインの開発について紹介しますが、UI のイベントを msgpack-RPC 経由で取得して、Neovim 自体を別のプラットフォームで動作させるようなアプリケーションなどもあります。
例えば Neovim の GUI を Web フロントエンド技術で実装した rhysd さんの NyaoVim が有名です。

github.com

実際には msgpack-RPC をラップしたクライアントライブラリを利用してプラグインを開発します。
つまり、msgpack-RPC を扱える言語であれば、原理上どんな言語でもプラグインを開発することができます。

コマンドラインで確認してみます。
まず、Neovim のみ (リモートプラグインを利用していない) 場合は、

f:id:ktr_0731:20161209002454p:plain

リモートプラグインを読み込んでいる場合は、

f:id:ktr_0731:20161209002720p:plain

このように、確かにリモートプラグインが Neovim 本体とは別のプロセスで動いていることがわかります。

クライアントライブラリ

Neovim 公式のクライアントライブラリは、Python、Node.js、Go、Lua の4つが用意されています。
コミット数やスターを見る限り、最も活発なのは Python クライアントのようです。

また、:help remote_plugin にも Python を用いたサンプルプラグインのコードが記載されています。
サードパーティ製のクライアントライブラリは、公式 Wiki で記載されているだけでも十分な数があります。

つくってみた 🚀

自分は Vimプラグインを開発したことはなかったのですが、Python で書けるならばできるかも…と感じ、実際につくってみました。
今回は、公式の python-client を利用しました。
Python で実装されたリモートプラグインの数は、現在最も多く、有名なものに Shougo さんの deoplete、denite などがあります。

とはいえ、ドキュメントは皆無に近いです。
ドキュメントを探すより、python-client のソースコードや他のプラグインの実装を見たりするのが一番手っ取り早いと思います。(割りと理解しやすいです🙌 )

つくったもの

Twitter クライアントを作りました。
Python3 を使っているので、Python3 で利用できるライブラリが使えるのがうれしいです。

github.com

そんなに時間を掛けていないので、最低限の機能しかないです…。
利用できる機能は、

  • ホーム、メンション、リストタイムラインの表示(非ストリーム)
  • ツイート
  • リツイート
  • いいね
  • リプライ

で、画面はこんな感じです。ストリームは上手くいかなくて保留中です。

f:id:ktr_0731:20161217143724p:plain

ディレクトリ構成

リモートプラグインディレクトリ構成は以下のようにする必要があります。
また、通常のプラグインと同様に $runtimepath/ 以下に配置するようにします。

.
└── rplugin
     └── python3
         └── tweet-nvim
             └── __init__.py

python-client での実装

python-client が Neovim とのインターフェースとなってくれているので、ほとんど意識する必要なくプラグインを書けます。
具体的には、クラスや各メソッドに Neovim のコマンドや関数として利用するためのデコレータを付与してあげる感じです。

詳しくはソースコードを見て欲しい(あまり整理してないので見て欲しくない)ですが、簡単に抜粋すると以下のような感じになります。

プラグイン定義

@neovim.plugin
class TweetNvim(object):

ツイート

    @neovim.command(_command_prefix + 'Tweet', nargs='+')
    def tweet(self, lines):
        content = '\n'.join(lines)

        if len(content) > TwitterAPI.MAX_CHARS:
            self.echo('Tweet must be less than {}'.format(TwitterAPI.MAX_CHARS))
            return

        TwitterAPI().tweet(content)
        self.echo('Tweeted')

感想

Vim script を知らなくても書けますが、流石に少しは Vim script を読めないと厳しいような感じでした。
とはいえ、Python を書けるならば、ほとんど学習コストはかかりませんでした。

ただ、一部の状態(window の id など)を python-client から取得できなかったので、まだあまり安定はしていないのかなとは感じます。
ちなみに冒頭でも言ったとおり、Vim script の評価はできるので、そこだけ Vim script で書くようにすれば取得自体はできます。

プラグインを自作する、と聞くとどうしても敷居が高そうに感じますが、意外とそうでもないので一度試してみてはどうでしょうか!