【Vim Advent Calendar 2011】unite.vim でアニメーション【33日目】

1月2日が欠番になったらしいので代打です。
遅れてしまいましたが Vim Advent Calendar 2011 33日目の記事になります。
本当は2週目のネタにする予定だった unite.vim を使ったアニメーションの作り方について書きたいと思います。
途中までは普通の unite-source の作り方なので unite-source を作る場合にも参考になる…かも?

[宣伝]

最初に宣伝でも。
さり気なく Vim Advent Calendar 2011 は2週目の参加者を募集していたりします。
Vimmer のクリスマスは 11月30日までらしいヨ?
まだ Vim について書き足りないことや言いたいことがある方は教えてください。
もちろん1週目に参加されていない方でも大歓迎です!
ちなみに今回のネタを2週目に書こうと思っていたので、2週目のネタはまだ未定です…。

[必要なもの]

[unite-source の定義の仕方]

さて、これから unite-source を書いていきますが、unite-source の定義の方法は二種類あります。
定義の仕方が少し違いますが、基本的な unite-source の書き方は変わりません。
:help unite-create-source

1.autoload/unite/source/ に Vimスクリプトファイルを用意する

autoload/unite/source/ に unite-source を記述した Vimスクリプトファイルを作成します。
こうすることで unite.vim が自動的に Vimスクリプトファイルの読み込みを行います。
unite-source のプラグインとして公開されているものはこの方法で定義されています。

2.unite#define_source 関数を使用する

unite#define_source 関数を使用することで動的に unite-source を追加することが出来ます。
今回はこちらを使って unite-source を定義します。

[unite-source を定義する]

まずは、簡単な unite-source を定義してみたいと思います。
最低限必要な unite-source はこんな記述になります。

[ソース]
" unite-kaomozi を定義
" name が unite-source 名になる
let s:source = {
\    "name" : "kaomozi"
\}

" unite-kaomozi を登録
call unite#define_source(s:source)

" もう使わないので後始末
unlet s:source

これで何もしない unite-source が出来ました。
試しに上記のコードを適当な Vimスクリプトファイルに保存して :source すると :Unite kaomozi が出力されるようになると思います。

[:Unite kaomozi]


これを枠組みとして作っていきたいと思います。

[unite-source で出力する]

次は実際に unite.vim にデータを出力してみたいと思います。
s:source にデータの出力処理を行う gather_candidates(args, context) 関数を定義します。

[ソース]
let s:source = {
\    "name" : "kaomozi",
\}

call unite#define_source(s:source)


" 辞書に関数を定義
" unite.vim から情報を収集する際に呼ばれる
function! s:source.gather_candidates(args, context)
    " word をキーに持った辞書の配列
    " word が実際に出力される文字列になる
    let result = [
\        { "word" : "(´・_・`)"                     },
\        { "word" : "シマ(´・_・)⊂彡☆))_・`)パーン" },
\        { "word" : "\(´・_・\)(/・_・`)/"        },
\        { "word" : "バン(∩´・_・)バンバンバンバン゙ン" },
\        { "word" : "/人´・_・`人\"               },
\        { "word" : "∩(´・_・`*)∩"                },
\        { "word" : "☆(´・_・`)vキャピ"            },
\        { "word" : "(∪´・_・)わんわんお!"      },
\    ]
    return result
endfunction

call unite#define_source(s:source)
unlet s:source
[:Unite kaomozi]


こんな風に辞書にコールバック関数を定義します。
上記で記述した辞書

{ "word" : "(´・_・`)" }

で1つのデータを定義します。
"word" は『画面に表示される文字列』の値です。
他にも "kind" や "is_multiline" といった設定なども1つのデータごとに設定を行うことが出来ます。
詳細は :help unite-notation-{candidate} を参照して下さい。


このままでも出力は問題はないのですが、各データごとに辞書を定義するのはちょっと手間なので map を使って展開してみます。

[ソース]
let s:source = {
\    "name" : "kaomozi",
\}

call unite#define_source(s:source)

function! s:source.gather_candidates(args, context)

    " 出力するデータの配列
    let list = [
\        "(´・_・`)",
\        "シマ(´・_・)⊂彡☆))_・`)パーン",
\        "\(´・_・\)(/・_・`)/",
\        "バン(∩´・_・)バンバンバンバン゙ン",
\        "/人´・_・`人\",
\        "∩(´・_・`*)∩",
\        "☆(´・_・`)vキャピ",
\        "(∪´・_・)わんわんお!",
\    ]

    " map を使用して文字列の配列から辞書の配列に変換を行う
    return map(list, '{"word" : v:val}')
endfunction

call unite#define_source(s:source)
unlet s:source

ちょっとすっきりとしましたね。
各データごとに設定を行わないならこのままの形で問題ないと思います。


さて、本来の unite-source であれば、ここから unite-source 固有の kind や action などを定義していくんですが、今回はアニメーションということなので割愛します。

[unite.vim に連続して出力する]

これで unite.vim へ出力することが出来ました。
しかし、gather_candidates は候補を収集する際に1度しか呼ばれないのでそのままではアニメーションすることが出来ません。
そこで、一定時間ごと処理が呼ばれる async_gather_candidates 関数を使用します。

[ソース]
let s:source = {
\    "name" : "kaomozi",
\}

call unite#define_source(s:source)

" gather_candidates から async_gather_candidates に変更
" 引数は gather_candidates と同じ
function! s:source.async_gather_candidates(args, context)

    let list = [
\        "(´・_・`) #焼肉食べたいJP",
\    ]

    return map(list, '{"word" : v:val}')
endfunction

call unite#define_source(s:source)
unlet s:source
[:Unite kaomozi]


これで一定時間ごとにデータが出力されるようになりました。
これを利用してアニメーションを行います。

[顔文字を動かしてみる]

実際に顔文字をアニメーションしてみます。
データを用意するのがちょっと手間ですが、処理自体はそんなに難しくありません。

[ソース]
" アニメーション用のカウンタを追加
let s:source = {
\    "name" : "kaomozi",
\    "counter" : 0
\}

call unite#define_source(s:source)

function! s:source.async_gather_candidates(args, context)
    " 関数が呼ばれるごとにキャッシュをクリア
    " これで unite.vim の出力をクリアする
    let a:context.source.unite__cached_candidates = []
    
    " アニメーションデータ
    " 1コマずつ用意してカウンタによって切り替える
    let anim = [
\        "(´・_・`)",
\        "( ´・_・)",
\        "(  ´・_)",
\        "(   ´・)",
\        "(    ´)",
\        "(      )",
\        "(      )",
\        "(`     )",
\        "(・`    )",
\        "(_・`   )",
\        "(・_・`  )",
\    ]
    let list = [ anim[self.counter % len(anim)] ]

    " カウンタをインクリメント
    let self.counter += 1

    return map(list, '{"word" : v:val}')
endfunction

call unite#define_source(s:source)
unlet s:source
[:Unite kaomozi]


これでアニメーションすることが出来ました。
やったね!

[:Unite のコマンドライン引数を設定]

動作としてはこのままでも問題ないんですが、カーソル行のハイライトがかぶってしまって見辛いです。

Unite のコマンドライン引数で
bold;"> -cursor-line-highlight を設定してカーソル行のハイライトを変更します。

また、更新速度(アニメーションの速度)は -update-time で設定することが出来ます。

[:Unite kaomozi -cursor-line-highlight=Normal -update-time=50]

[まとめ]

簡単ですが、unite.vim を使ったアニメーションの作成について書いてみました。
unite.vimVim script のアニメーションフレームワークだったんですね!
unite.vim を使うとコールバック関数を1つ用意するだけなので、自分でバッファ作成したり管理するよりも簡単に作成出来ると思います。
この他にもシンタックスハイライトを使った色付けなども出来ますが、それはまた別の機会にでも書こうと思います。
みなさんもどんどん unite-source を書いていきましょう!

[おまけ]

let s:source = {
\    "name" : "kaomozi",
\    "counter" : 0
\}

call unite#define_source(s:source)

function! s:source.async_gather_candidates(args, context)
    let a:context.source.unite__cached_candidates = []

    let anim = [
\        "(´・_・`)",
\        "( ´・_・)",
\        "(  ´・_)",
\        "(   ´・)",
\        "(    ´)",
\        "(      )",
\        "(      )",
\        "(`     )",
\        "(・`    )",
\        "(_・`   )",
\        "(・_・`  )",
\    ]
    let self.counter += 1

    return map(range(20), '{"word" : anim[(self.counter+v:val) % len(anim)]}')
endfunction

call unite#define_source(s:source)
unlet s:source

床屋のアレみたいですね。
ちょっと怖い。