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 で書くようにすれば取得自体はできます。

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

SECCON 2016 に参加した

初心者チームwabisabiとして、自分(id:ktr_0731)とid:ywkw1717id:kobadlve、ともう2人の計5人で参加した。 SECCON自体は去年も参加していて、チーム名は違うけどメンバーはほとんど同じだった。 チーム全体では4問解いた。 0点のチームを含めると1834チーム、含めないと930チーム中272位だった。 去年は575位だったので、半分くらいは順位が上がった感じ。 id:ywkw1717はBinary100のAnti-DebuggingとForensics100のMemory Analysis、id:kobadlveと自分はVoIP、はVigenereを解いていた。 個人的にWeb問を全く解けなかったので悔しい。本当に。 少なくともWeb300は十分解ける問題だった。 チームとしては成長しているけども自分自身は成長してない気がしたのでもっと頑張りたい。

 なかったのでグルコスのマイページの情報を取得するライブラリを書いた

概要

BEMANI音ゲーの一つである SDVX や弐寺Jubeat なんかは非公式でもスコアデータを取得するアプリなりライブラリがある。そのどれもが根強い人気を誇っているのは多分音ゲーをやっている/やっていた人ならばわかるはず。
Groove Coaster (グルコス) は BEMANI とかチュウニズムに比べてあんまり人気がない…。(自分は BEMANI より好きです)
当然ノバでも同じで、グルコスで並んだことは恐らく 3 回くらいしかない。
ということはもちろんアプリもライブラリもないので、作るしかないと思ってライブラリを書いた。

github.com

グルコスのマイページの通信を見てる感じだと、XHR でデータを取得している感じで、通常のページ表示に必要な XHR リクエストの数からして、API をふつうに利用する限りではまったく負荷はかからない。
使い方は README.md か GoDoc を見れば大体わかるはず。

サンプル

例えばすべての楽曲のサマリを取得する MusicSummary() を利用すると、

package main

import (
    "fmt"

    "github.com/lycoris0731/go-groovecoaster/groovecoaster"
)

func main() {
    client := groovecoaster.New()

    summary, err := client.MusicSummary()
    if err != nil {
        fmt.Println(err)
        return
    }

    for _, music := range summary {
        fmt.Printf("%#v\n", music)
    }
}

総楽曲数は結構多いので、先頭から幾つかを見ると、

&groovecoaster.MusicSummary{ID:161, Title:"東方不死鳥", PlayCount:40, LastPlayTime:"2016-11-24 21:14:14"}
&groovecoaster.MusicSummary{ID:365, Title:"東方地神律", PlayCount:4, LastPlayTime:"2016-11-24 21:14:14"}
&groovecoaster.MusicSummary{ID:298, Title:"FUJIN Rumble", PlayCount:52, LastPlayTime:"2016-11-24 21:02:02"}
&groovecoaster.MusicSummary{ID:43, Title:"グルーヴ・リボルバー", PlayCount:19, LastPlayTime:"2016-11-24 21:02:02"}
&groovecoaster.MusicSummary{ID:192, Title:"OMAKENO Stroke", PlayCount:17, LastPlayTime:"2016-11-24 21:02:02"}
&groovecoaster.MusicSummary{ID:49, Title:"Got a pain cover?", PlayCount:53, LastPlayTime:"2016-11-24 20:49:04"}
&groovecoaster.MusicSummary{ID:508, Title:"ハレ晴レユカイ", PlayCount:1, LastPlayTime:"2016-11-24 20:49:04"}

こんな感じに取得できる。

所感

しんどい

このライブラリを作る以前にも良くグルコスのマイページのリクエストやソースは見ていたけども、タイトー or 受注している下請けはエンジニアが足りてないんじゃないかと思ってくる… 。
使われる場所はまとまってはいるけど、JSONXML の 2 つが使われていたり、JSON のキーに flag とあったのに、別のリクエストでは flg といった表記をしていたり、真偽値を返すべきところが 01 で表現されていたり、あと個人的には店舗名を tenpo_name とするのはどうしても受け付けがたい…。
あとは、v2.0 で既に廃止されたレベルが JSON に残っていたり。結構挙げるときりがない。
そのあたりのバラバラな要素をできるだけ吸収して統一するようにがんばった。
なんだかんだで Go を書くいい練習になった (気がする)

とにかくグルコスにかぎらずタイトーの Web サイトは色々おかしい部分があるので探してみるのも良いかもしれない。