読者です 読者をやめる 読者になる 読者になる

Vimプラグインが出来るまで

Vim

備忘的なVimプラグイン入門。
最近やっとVim scriptを書き始めて、プラグインの形でもってGithubにて公開というところまで辿り着きました。
いろいろと足りない部分は多いんですが、それでも初のプラグインであり初のオープンソースです。
で、こういう「初めて」の頃の感覚や手順って、後々になると覚えていなかったりするのでまとめておきます。
プラグインのヘルプはまだ書いてないから、「出来上がって」はいないんですけどね。

事前準備

日本語版のヘルプを手に入れましょう。
ヘルプは必須です。これがなければ始まりません。
英語が読める人は英語でもいいと思いますが、ぼくはすらすら読めないので日本語版を入れています。
どこかで「日本語ヘルプが許されるのは初心者まで」というのを見た気がしますが、ぼくはまだ初心者なので問題ありません。

インストールにあたっては、今だとここが最新情報なのかな?
HowToUse · vim-jp/vimdoc-ja Wiki · GitHub

Vim scriptの基本構文は覚えておく

プラグインを書くにはまずVim scriptを書けるようになる必要があります。
Vim scriptを書き始めるにあたって特に参考になる記事はこの4つです。

どれもすばらしく、分かりやすく、これといって読む順番を決める必要はありません。

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でも発表しましたけど、あると便利。
たぶん何年にも渡ってLingrTwitter2ch(は見ないから知らんけど)で、同じような質疑応答が繰り返されてる気がします。
これ無駄じゃないですか。
質問する側は気を使うでしょうし、答える側の手間もかかります。
まあ、そう思うならお前がなんかWiki作れよって話ですけど…。
どっかサーバー借りて作ろうかな。うーん。
(誰か作ってくれないかな)

:help!

ヘルプの設定とか。

set keywordpreg=:help

デバッグとか

コレだ!というデバッグ方法は探し中です。
とりあえず今はechomsgでひたすら出力結果を確認しています。
JavaでいうSystem.out.printlnデバッグPHPでいうvar_dumpデバッグJavascriptでいうalertデバッグです。

echomsg 変数名

と、したあとで対象ソースを:sourceして:messagesで確認です。
正直非効率だなとは思うんですが、他にコレといって見つかっておらず。
ちなみにechomsgの引数は文字列じゃないといけないのでlistとかはechomsg string(list)とする必要があります。

その他

実際に動作確認していく中では何度もVimを再起動することがあります。
そんな時に便利なのがこちら

おわりに

ここまで来たら、あとはGithubにあげて公開するといいかもしれません。
「ドキュメント書いてから」とか「ソースコード綺麗にしてから」とかダラダラしているのは勿体無いと思います。
とりあえず動くなら公開しちゃえばいいんじゃないかと。
今のぼくがそう思うだけで、数年後には「ダメだよちゃんとしてから公開してよ」とか言うかもしれませんが。

あとは、もっとサクサク作れるようになりたいかな。
でもプログラマとしての普通の勉強もしたい。
気力がある時に時間が足りない。時間がある時に気力が足りない。