Vimプラグインが出来るまで
備忘的なVimプラグイン入門。
最近やっとVim scriptを書き始めて、プラグインの形でもってGithubにて公開というところまで辿り着きました。
いろいろと足りない部分は多いんですが、それでも初のプラグインであり初のオープンソースです。
で、こういう「初めて」の頃の感覚や手順って、後々になると覚えていなかったりするのでまとめておきます。
プラグインのヘルプはまだ書いてないから、「出来上がって」はいないんですけどね。
事前準備
日本語版のヘルプを手に入れましょう。
ヘルプは必須です。これがなければ始まりません。
英語が読める人は英語でもいいと思いますが、ぼくはすらすら読めないので日本語版を入れています。
どこかで「日本語ヘルプが許されるのは初心者まで」というのを見た気がしますが、ぼくはまだ初心者なので問題ありません。
インストールにあたっては、今だとここが最新情報なのかな?
HowToUse · vim-jp/vimdoc-ja Wiki · GitHub
Vim scriptの基本構文は覚えておく
プラグインを書くにはまずVim scriptを書けるようになる必要があります。
Vim scriptを書き始めるにあたって特に参考になる記事はこの4つです。
- :help vim-script-intro もしくは :help usr_41
- Vimスクリプト基礎文法最速マスター - 永遠に未完成
- Big Sky :: モテる男のVim Script短期集中講座
- Vimスクリプトリファレンス — 名無しのvim使い
どれもすばらしく、分かりやすく、これといって読む順番を決める必要はありません。
vimrcにとりあえず何か書いてみる
いきなりプラグインとして書き始めるのではなく、我々初心者はまずvimrcに書いてみるといいんじゃないでしょうか。
ネタを決める
今回は簡単に「完了/未完了の印を付け外しする」プラグインです。
ちょっとしたTODOプラグインですね。
書いてみる
- 最初の一歩
vimrcの好きなところに次のように書いてみます。
function! s:Done(line) call setline('.', substitute(a:line, '\[ \]', '[D]', '')) endfunction command! -nargs=0 MyTaskToggle call s:Done(getline('.'))
※コマンドじゃなくてキーマッピングでもいいと思いますが、設定がバッティングするかもしれないんでコマンドにしました。
そしたらvimrcを:sourceします。vimrcを開いているなら:source % ですね。
そしたら、vimrcのこれまたどこでもいいので、サンプルのTODOを置いてみましょう。
文頭に「"」を入れないとコメント扱いにならず面倒になるので入れておきましょう。
こんな感じ。
function! s:Done(line) call setline('.', substitute(a:line, '\[ \]', '[D]', '')) endfunction command! -nargs=0 MyTaskToggle call s:Done(getline('.')) " [ ] 備忘録のためのサンプルを書く
そしたらTODO行の上にカーソルを持って行って:MyTaskToggleと実行してみましょう。
「[ ]」が「[D]」となるはずです。
- 次の一歩
今度は取り消す方を実装します。
function! s:Undone(line) call setline('.', substitute(a:line, '\[D\]', '[ ]', '')) endfunction command! -nargs=0 MyTaskToggle call s:Undone(getline(".")) " [D] 備忘録のためのサンプルを書く
・commandの実行をs:Doneからs:Undoneに変えているので注意
あとは同じように:sourceしてTODO行で:MyTaskToggleと実行してみましょう。
「[D]」が「[ ]」となるはずです。
- もう一歩
一回のコマンドで出来るようにしましょう。
function! s:ToggleDone(line) if a:line =~ '^"*\s*\[D\]' " Undone call setline('.', substitute(a:line, '\[D\]', '[ ]', '')) else " Done call setline('.', substitute(a:line, '\[ \]', '[D]', '')) endif endfunction command! -nargs=0 MyTaskToggle call s:ToggleDone(getline(".")) " [ ] 備忘録のためのサンプルを書く
同じように:sourceしてTODO行で:MyTaskToggleと実行してみましょう。
何かいい感じに動くはずです。
- おまけ
どうせなら完了した時間も欲しいですね。
function! s:ToggleDone(line) if a:line =~ '^"*\s*\[D\]' call setline('.', substitute(a:line, '\[D\]<.*>', '[ ]', '')) else call setline('.', substitute(a:line, '\[ \]', '[D]<'.strftime("%Y/%m/%d %H:%M").'>', '')) endif endfunction command! -nargs=0 MyTaskToggle call s:ToggleDone(getline(".")) " [D]<2012/04/03 22:28> 備忘録のためのサンプルを書く
あとはお好みのキーにマッピングしてあげればワンタッチでいい感じです。
プラグインの形にしてみる
まずはhelp
- 何はなくともまずはhelp。:help write-pluginです。
- ここにある例題を一度自分の手で書いてみることをおすすめします。
- 記事は…特に参考にしなかったような。忘れているだけかもしれないですが、記憶にないです。
- ただ、上記にあげたmattnさんの記事中のautoloadはとても参考になりました。そういうことなのかーと
作り方
- pluginディレクトリに置く
さきほどvimrcに書いたコードをプラグインの形にしてみます。
プラグイン名を決める必要がありますが、ここでは適当にvimtaskとでもしておきます。
vimtask.vimを置く場所はpluginディレクトリの下です。
詳細は:help add-plugin もしくは :help add-global-plugin です
私は~/.vim/pluginの下に置きました。
/Users/kanno% tree .vim/plugin .vim/plugin └── vimtask.vim 0 directories, 1 file
" vimtask.vim
" 処理はさっきと同じ。最初と最後にプラグインのお約束を追加。詳細は:help write-plugin
if exists("g:loaded_vimtask") finish endif let g:loaded_vimtask = 1 let s:save_cpo = &cpo set cpo&vim function! s:ToggleDone(line) if a:line =~ '^"*\s*\[D\]' call setline('.', substitute(a:line, '\[D\]<.*>', '[ ]', '')) else call setline('.', substitute(a:line, '\[ \]', '[D]<'.strftime("%Y/%m/%d %H:%M").'>', '')) endif endfunction command! -nargs=0 MyTaskToggle call s:ToggleDone(getline(".")) let &cpo = s:save_cpo unlet s:save_cpo
そしたらさきほどvimrcに書いたコードをコメントアウトもしくは削除し、
Vimを再起動してみて:MyTaskToggleが実行できるならOKです。
- autoloadディレクトリに分ける
autoloadが何かは上記のmattnさんの記事、もしくは :help autoload です。
私は~/.vim/autoloadの下に置きました。
/Users/kanno% tree .vim/autoload .vim/autoload └── vimtask.vim 0 directories, 1 file
autoload/vimtask.vimには次のように書きます。
関数名を変えている以外、処理はこれまでと同じです。
let s:save_cpo = &cpo set cpo&vim function! vimtask#toggle(line) if a:line =~ '^"*\s*\[D\]' call setline('.', substitute(a:line, '\[D\]<.*>', '[ ]', '')) else call setline('.', substitute(a:line, '\[ \]', '[D]<'.strftime("%Y/%m/%d %H:%M").'>', '')) endif endfunction let &cpo = s:save_cpo unlet s:save_cpo
plugin/vimtask.vimを以下のように変更します。
if exists("g:loaded_vimtask") finish endif let g:loaded_vimtask = 1 let s:save_cpo = &cpo set cpo&vim command! -nargs=0 MyTaskToggle call vimtask#toggle(getline(".")) let &cpo = s:save_cpo unlet s:save_cpo
先ほどと同じようにVim再起動してもちゃんと動けばOKです。
終わりです。お疲れ様でした。
Githubに上げる場合
その場合は、以下のように構成にしてpushすればいいんじゃないかと思います。
/Users/kanno% tree vimtask vimtask ├── autoload │ └── vimtask.vim └── plugin └── vimtask.vim 2 directories, 2 files
作るにあたってあれやこれやの調べ方
サンプルでは実際に自分で作り始めると悩むポイントをスルーしました。
それは「必要な機能の実装方法を調べる方法が分からない」ということです。
- 「ググれよ」
- 「ググっても引っかからないんだよ!」
- 「Vim scriptで検索したのにJavascriptが引っかかるんだよ!」
- 「なんで"Vim script 時間"のサジェストの1つ目からすでにJavascriptなんだよ!」
- 「空白がいけないのかと思って詰めたらvbscriptになっちゃったよ!!!!」
- 「:help見れば書いてあるじゃん」
- 「なんてヘルプ引けばいいか分かんないんだよ!」
- 「ヘルプ見ても分からないから困ってんだろ!」→「ググれよ」(以下略
ということで、初心者にはこの「調べる」ことのハードルがかなり高いんじゃないかと感じました。
先人Vimmerブログや「名無しのvim使い」やVim-users.jpなどたくさん情報はあるんですが、なかなか辿りつけないこともあります。
慣れてくると少しずつ関数を覚えてくるし、探し方も何となく分かったり分からなかったりするんですが。
で、じゃあぼくはどうやって少しずつ「慣れていった」のか。
答えはまあ普通なんですが、やっぱり「読むこと」です。次いで「書くこと」です。
読むのはヘルプではなく、他の人が書いたプラグインやVim scriptです。
これには次の3つのメリットがあります。
- 構文に慣れる
- やはり、その言語に慣れるにはそのコードをたくさん読むということで。自然に雰囲気に慣れていきます。
- 分からない関数が出てきたらすかさず:help
- 関数を知る
- 分からない関数が出てきたらすかさず:help
- 機能を知る
- 「こんなこと出来るんだー」っていうのを知っておく。大事。
この積み重ねがいざ自分がプラグインを作る時に
「似たような機能をあのプラグインが提供していたはずだから、あのソースを読めば答えがありそうだ」
という解決への道標になります。
まともにVim scriptを読んだのはが初めてだったのですが、おかげでいい勉強になりました。
でもやっぱり、Web上に逆引きWikiとかあったらすごい便利だと思います。
以前にujihisa.vim#2のLTでも発表しましたけど、あると便利。
たぶん何年にも渡ってLingrやTwitterや2ch(は見ないから知らんけど)で、同じような質疑応答が繰り返されてる気がします。
これ無駄じゃないですか。
質問する側は気を使うでしょうし、答える側の手間もかかります。
まあ、そう思うならお前がなんかWiki作れよって話ですけど…。
どっかサーバー借りて作ろうかな。うーん。
(誰か作ってくれないかな)
:help!
ヘルプの設定とか。
- Vim-users.jp - Hack #45: help を引く
- vimrcに以下の設定をしておけばShift-Kでカーソル下の単語で:helpが即実行されて便利です。というか必須。
set keywordpreg=:help
- unite.vimを使っている人はtsukkee/unite-help · GitHubを使うと捗るかもしれません
デバッグとか
コレだ!というデバッグ方法は探し中です。
とりあえず今はechomsgでひたすら出力結果を確認しています。
JavaでいうSystem.out.printlnデバッグ、PHPでいうvar_dumpデバッグ、Javascriptでいうalertデバッグです。
echomsg 変数名
と、したあとで対象ソースを:sourceして:messagesで確認です。
正直非効率だなとは思うんですが、他にコレといって見つかっておらず。
ちなみにechomsgの引数は文字列じゃないといけないのでlistとかはechomsg string(list)とする必要があります。
その他
実際に動作確認していく中では何度もVimを再起動することがあります。
そんな時に便利なのがこちら
- tyru/restart.vim · GitHub
- unite.vimのUniteSessionSave/UniteSessionLoad