あなたがサイヤ人の時、Vimはスーパーサイヤエディタになる

人はスーパーサイヤ人に憧れるものです、しかし現実ではなれる人は限られています、日夜修行していつかスーパーサイヤ人になれる日を夢見るのもいいですが、ここでは変わりにVimにスーパーサイヤエディタになってもらいます。

そんなプラグインを作りました。

# kuririn-no-kotoka.vim

![image.png](https://qiita-image-store.s3.amazonaws.com/0/51728/3dd158dd-513e-11b4-848c-978ce4964763.png)

[wordijp/kuririn-no-kotoka.vim(GitHub)](https://github.com/wordijp/kuririn-no-kotoka.vim)
インストール方法や使い方はREADME.mdに記載しています。
*※動作確認はWindowsのみで行っているため、Linunx/Macでの確認ができていません*

# デモ

このプラグインは[こちらの動画(Youtube)](https://www.youtube.com/watch?v=7IYkjyWhZ5g)を見ながら作成しています。

あなたがサイヤ人ならクリリンを殺された怒りでVimがスーパーサイヤエディタへと覚醒します
![demo.gif](https://qiita-image-store.s3.amazonaws.com/0/51728/bf098435-0fe3-ab84-d58a-f54e0b094ab5.gif)
デモでは音が出ませんが、ｼｭｲﾝｼｭｲﾝの効果音も出ています。

あなたが~~へたれ~~王子なら恐怖のあまりガタガタ震えます
![demo_prince.gif](https://qiita-image-store.s3.amazonaws.com/0/51728/bf5c7ca0-6f01-9006-9544-66b1264e0680.gif)


# 苦労した点

## スーパーサイヤ人らしさ

特徴といえば「逆立った髪」「金色のエフェクト」「ｼｭｲﾝｼｭｲﾝ」だと思います。
このうち、「逆立った髪」はエディタで何が逆立つのか分からなかったので却下、「金色のエフェクト」はカラースキームをリアルタイムに変更しようとしたら編集に支障が出るレベルで遅いので却下。
最終的に、金色っぽいカラースキームに変更後、「ｼｭｲﾝｼｭｲﾝ」だけ発生させてスーパーサイヤエディタっぽくするようにしました、見辛くてもいけないのでカラースキームも「golden.vim」という名前が金色なもので妥協しました。


# 技術的な話

以下は今回のプラグインを作成した際の技術的な話です、読み飛ばしてもらっても構いません。

# 利用したVim8の新機能

## exepath

あなたがサイヤ人か、はたまた~~へたれ~~王子なのかの判定に利用しています。
それぞれの専用ディレクトリへパスを通したあと、ユーザー名コマンドを作成し、exepathで取得したフルパスに「saiyajin」か「prince」が含まれているかで判定しています。

```
kuririn-no-kotoka.vim
`-- autoload
    |-- kuririn_no_kotoka.vim # princeとsaiyajinへパスを通す
    |-- prince
    `-- saiyajin
        `-- wordi             # 作成されたユーザー名コマンド
```
 
## タイマー(Timer)

タイマーはアニメーション処理で利用しています、Vimがスーパーサイヤエディタになるには、いくつかの過程を経る必要があるため、それらをアニメーションで処理しています。

アニメーション処理用の関数はこちらです

```vim:kuririno_no_kotoka.vim
" 一定間隔で指定回数funcを読んだあと、最後にnext_funcを呼ぶ
function! s:regist_animate(interval, max_count, func, next_func) abort
  " クロージャ動作をさせるバインド
  let l:Func = a:func
  let l:Next_func = a:next_func
  let l:max_count = a:max_count
  let l:interval = a:interval
  let l:count = 1

  " ループコールバック
  function! s:regist_animate_internal_loop(local, timer) abort
    call a:local.Func() " max_count回数呼ばれる

    if a:local.max_count == 0
      " 無限ループ
      return
    endif

    let a:local.count += 1
    if a:local.count > a:local.max_count
      call timer_stop(a:timer)
      call s:regist_animate_internal_next(a:local.Next_func)
    endif
  endfunction
  call timer_start(a:interval, function('s:regist_animate_internal_loop', [l:]), {'repeat': -1})

  " 終了時コールバック
  function! s:regist_animate_internal_next(next_func) abort
    let l:Next_func = a:next_func

    function! s:regist_animate_internal_next_internal(local, timer) abort
      call a:local.Next_func() " max_count回数後に呼ばれる
    endfunction

    " NOTE : 非同期でa:local.Next_funcを呼び出し、ループコールバック関数の使用中を解除する
    call timer_start(0, function('s:regist_animate_internal_next_internal', [l:]), {'repeat': 1})
  endfunction
endfunction
```

この関数を利用して、過程を経てスーパーサイヤエディタになります。

過程は、Stateパターン(のようなもの)で実装しています。
*※解説用にシンプルにしています。*

```vim:regist_animateの利用サンプル
" Phase 1
function! s:phase_1() abort
  let l:foo = 'local'
  function! s:phase_1_loop(local) abort
    " ここにアニメーション処理を書く
    echo a:local.foo
  endfunction

  function! s:phase_1_done() abort
    " 次のPhaseへ
    call s:phase_2()
  endfunction

  " 50ms間隔でloop関数を40回実行後、done関数を呼び出す
  call s:regist_animate(50, 40, function('s:phase_1_loop', [l:]), function('s:phase_1_done'))
endfunction

" ----------------------------------
" Phase 2
function! s:phase_2() abort
  function! s:phase_2_loop() abort
    " ここにアニメーション処理を書く
  endfunction
  function! s:phase_2_loop2() abort
    " ここにアニメーション処理を書く
  endfunction

  let l:remain = 0
  function! s:phase_2_done(local) abort
    let a:local.remain -= 1
    if a:local.remain <= 0
      " no-op : 最終Phaseなので何もしない
    endif
  endfunction

  " アニメーションを合成する
  call s:regist_animate(50, 40, function('s:phase_2_loop'), function('s:phase_2_done', [l:]))
  let l:remain += 1
  " 異なるFPSでもOK
  call s:regist_animate(100, 20, function('s:phase_2_loop2'), function('s:phase_2_done', [l:]))
  let l:remain += 1
endfunction
```

アニメーションは合成が出来ます、今回のプラグインではセリフと震えを合成しています、アニメーションをそれぞれの専用関数に実装して合成すると、FPSの違いを考慮しながら実装する手間がなくなり楽に実装できました。


# 作ってみた感想

ウインドウの振動を表現するために乱数が欲しいなと思いました！
https://github.com/vim-jp/issues/issues/983


# おわりに

悟空が「クリリンのことかー！」と叫ぶのはスーパーサイヤ人になった**あと**なんですが、勢いがあるので気にしないことにしました。