人はスーパーサイヤ人に憧れるものです、しかし現実ではなれる人は限られています、日夜修行していつかスーパーサイヤ人になれる日を夢見るのもいいですが、ここでは変わりにVimにスーパーサイヤエディタになってもらいます。
そんなプラグインを作りました。
kuririn-no-kotoka.vim
wordijp/kuririn-no-kotoka.vim(GitHub)
インストール方法や使い方はREADME.mdに記載しています。
※動作確認はWindowsのみで行っているため、Linunx/Macでの確認ができていません
デモ
このプラグインはこちらの動画(Youtube)を見ながら作成しています。
あなたがサイヤ人ならクリリンを殺された怒りでVimがスーパーサイヤエディタへと覚醒します
デモでは音が出ませんが、シュインシュインの効果音も出ています。
苦労した点
スーパーサイヤ人らしさ
特徴といえば「逆立った髪」「金色のエフェクト」「シュインシュイン」だと思います。
このうち、「逆立った髪」はエディタで何が逆立つのか分からなかったので却下、「金色のエフェクト」はカラースキームをリアルタイムに変更しようとしたら編集に支障が出るレベルで遅いので却下。
最終的に、金色っぽいカラースキームに変更後、「シュインシュイン」だけ発生させてスーパーサイヤエディタっぽくするようにしました、見辛くてもいけないのでカラースキームも「golden.vim」という名前が金色なもので妥協しました。
技術的な話
以下は今回のプラグインを作成した際の技術的な話です、読み飛ばしてもらっても構いません。
利用したVim8の新機能
exepath
あなたがサイヤ人か、はたまたへたれ王子なのかの判定に利用しています。
それぞれの専用ディレクトリへパスを通したあと、ユーザー名コマンドを作成し、exepathで取得したフルパスに「saiyajin」か「prince」が含まれているかで判定しています。
kuririn-no-kotoka.vim
`-- autoload
|-- kuririn_no_kotoka.vim # princeとsaiyajinへパスを通す
|-- prince
`-- saiyajin
`-- wordi # 作成されたユーザー名コマンド
タイマー(Timer)
タイマーはアニメーション処理で利用しています、Vimがスーパーサイヤエディタになるには、いくつかの過程を経る必要があるため、それらをアニメーションで処理しています。
アニメーション処理用の関数はこちらです
" 一定間隔で指定回数funcを読んだあと、最後にnext_funcを呼ぶfunction!s:regist_animate(interval, max_count, func, next_func) abort
" クロージャ動作をさせるバインドletl:Func =a:func
letl:Next_func =a:next_func
letl:max_count =a:max_count
letl:interval =a:interval
letl:count=1 " ループコールバックfunction!s:regist_animate_internal_loop(local, timer) abort
calla:local.Func()" max_count回数呼ばれるifa:local.max_count ==0 " 無限ループreturnendifleta:local.count+=1ifa:local.count>a:local.max_count
call timer_stop(a:timer)calls:regist_animate_internal_next(a:local.Next_func)endifendfunctioncall timer_start(a:interval,function('s:regist_animate_internal_loop', [l:]), {'repeat': -1}) " 終了時コールバックfunction!s:regist_animate_internal_next(next_func) abort
letl:Next_func =a:next_func
function!s:regist_animate_internal_next_internal(local, timer) abort
calla: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})endfunctionendfunction
この関数を利用して、過程を経てスーパーサイヤエディタになります。
過程は、Stateパターン(のようなもの)で実装しています。
※解説用にシンプルにしています。
" Phase 1function!s:phase_1() abort
letl:foo ='local'function!s:phase_1_loop(local) abort
" ここにアニメーション処理を書く
echo a:local.foo
endfunctionfunction!s:phase_1_done() abort
" 次のPhaseへcalls:phase_2()endfunction " 50ms間隔でloop関数を40回実行後、done関数を呼び出すcalls:regist_animate(50,40,function('s:phase_1_loop', [l:]),function('s:phase_1_done'))endfunction" ----------------------------------" Phase 2function!s:phase_2() abort
function!s:phase_2_loop() abort
" ここにアニメーション処理を書くendfunctionfunction!s:phase_2_loop2() abort
" ここにアニメーション処理を書くendfunctionletl:remain =0function!s:phase_2_done(local) abort
leta:local.remain -=1ifa:local.remain <=0 " no-op : 最終Phaseなので何もしないendifendfunction " アニメーションを合成するcalls:regist_animate(50,40,function('s:phase_2_loop'),function('s:phase_2_done', [l:]))letl:remain +=1 " 異なるFPSでもOKcalls:regist_animate(100,20,function('s:phase_2_loop2'),function('s:phase_2_done', [l:]))letl:remain +=1endfunction
アニメーションは合成が出来ます、今回のプラグインではセリフと震えを合成しています、アニメーションをそれぞれの専用関数に実装して合成すると、FPSの違いを考慮しながら実装する手間がなくなり楽に実装できました。
作ってみた感想
ウインドウの振動を表現するために乱数が欲しいなと思いました!
https://github.com/vim-jp/issues/issues/983
おわりに
悟空が「クリリンのことかー!」と叫ぶのはスーパーサイヤ人になったあとなんですが、勢いがあるので気にしないことにしました。