vim-flymakeを試して見た

vimには標準でQuickFixと呼ばれる機能が付いている。コンパイラなどの文法チェッカのエラー出力を保存しておいて、エラー行にジャンプができるという機能で、開発以外のシーンでも:vimgrepやらなんやらでとても役に立つ。更に、単純にメッセージ中の行番号にジャンプするわけではなく、ソースに変更があって行が変わった場合もそれを考慮して正しい行にジャンプしてくれる。昨今のIDEほどじゃあないけれどedit -> Compile -> editのサイクルを短くして、生産性を高めることができる、、、というか、たっくさんエラーとか警告(品質向上、という意味では特にこっちが重要。エラーは出ている限りどっちにしても動かないので・・・)出るってのに、これが無いとふつうにムリ。
しかし、eclipseのようにインクリメンタルにチェックを行うIDEに比べて

  1. コンパイル、するという動作、しているという状態、が表に出てしまっていて思考を妨げる
  2. 同時に複数のerror/warningを(ソースコード上で)ハイライトなどで判別できないので、深刻さや関連の区別がつきにくい。

などは少しだけ気になるところ。最初のはauでBufWritePostとかに貼ってしまえばよいのかなーと思うけど、バックグラウンドではムリっぽいし"Press ENTER or..."はどうしてもでちゃうっぽく、まあここは諦めるべきところなのかな・・。後者はなんとかなるんでは? とか思ってた。
ちょうど最近Emacsをちょくちょく使うようになってきて*1、なんとなくflymakeモードを思い出して使ってみると*2やはり使いやすい。vimじゃムリだろうな、とか思ってたんだけど、ググってみたら思いっきりあった。
ちょっと試してみよっ。と

vim-flymakeを拾ってくる

元々はikegamiさんのところ。それを微妙に添削したものを、こっから
http://github.com/kana/vim-flymake
拾ってきた。.vimにコピー。で試しにダメダメな例を。

#include <stdio.h>

int main()
{
  int a;
  int b;
  int c;                        // warn here

  printf("%d,%d\n", a,b);       // warn here
  b = something_not_defined();  // warn here

  a += 10

  return a;   // err here
}

スパッと書いてみて、Makefile書いて

check-syntax: damedame.o

やってみた。
さあ、そそっかしい僕に代わってチェックをしておくれ。綺麗に表示しておくれ。

しかし・・・・

??????????????
無反応。vimが凍ってる。なんも動かない。裏で見てみると。。。ディスクが食いつぶされてるー!
どうしてこうなった!

コピー?全部?

慌て者の僕はこういうちょっとしたコードを試すときについついホームにドカっと書いて、あとからサブツリーに投げる悪い癖がある(最悪!)。この時もめんどくさがってホームでやってしまった。
実はflymake.vim

function! s:FlyMakeTemporaryDirectory(dir)
  let tmp_dir = substitute(system('mktemp -d'), '\r\|\n', '', 'g')
  call system('cp -a ' . a:dir . '/* ' . tmp_dir)
  return tmp_dir
endfunction

のように、最初にソースのあるところから下全ツリーを/tmpにコピーするという暴挙気の利いたことをしてくれている。つまりホームの全サブツリーのクローンを一生懸命/tmpにこさえてた!!というわけ(あほらし)。んーーーーー、別に同じ場所で作ってくれちゃっても問題ないと思うんだけど・・・
まあ、これは気をつければ済む話か。でも、ちょっとした実験コードをスパッと書きたい時とかにうっかり変なところで:wするとまた大変なことに・・・あと、デバッグ中には大量の変なtempファイルができあがる、と悪いことがたくさん有る気がするんだけど・・・そのうちなんとかしよう。
今回はとりあえず「試す」のが目的なので。。。

あれれれれ? なんでwarning無視?

気を取り直して、サブツリーに放ってから再実行。で、あれれれれれれ?

なんと。warnがない
どうしてこうなった!?

「ERRが終わるまでは警告なんて潰すんじゃねえ!」??

理由は良く解らんけど、もともと以下のように

 if num_errors == 0
   call s:FlyMakeDisplay('*FlyMakeWarn*', 'Todo', messages, a:warn_regexp)
 endif

errorが0じゃないときは warnは表示しないようになっていた。ここを修正。してみた。してみた。
が、やっぱり結果同じ。

いったいどうしてこうなった!??!

問題は2つ

どうも

 execute 'match' a:type "'\\%" . sorted_keys[0] . "l'"

と、一個目しかみてないっぽい。ここも修正!
しかしこれでもナゼかerrorしか反転しない。そりゃそうだ。highlightはmatchでやってるわけだけど、これは分けなきゃ。2matchをつかった。だーー

ついでに
 call cursor(sorted_keys[0], 1)

となってるんだけど、場所によっては見にくい!し、このままだと警告のトップに必ず行ってしまう。
全ソートして一番若い行にやるべきかとも思うんだけど、今回は試すのが(ry
単純に消した。

よ〜しこんどこそ T〜T


やったーーーー、と思ったんだけど、14行目のエラーがあると9行目のuninitializedがでないみたい。
まあそれはいいや。
とにかく上手く言った。しかしなんか不毛なことしてた気がする。

パッチ

とりあえずこんな感じ。まだまだ治すところだらけだけど、ヤル気はもうナイ。

diff --git a/plugin/flymake.vim b/plugin/flymake.vim
index 4860175..81062a7 100644
--- a/plugin/flymake.vim
+++ b/plugin/flymake.vim
@@ -22,6 +22,8 @@ function! FlyMake(checker, err_regexp, warn_regexp)

   call FlyMakeCloseWindows()

+  call FlyMakeClearMatches()
+
   " copy source files in build_dir into temp_dir
   let tmp_dir = s:FlyMakeTemporaryDirectory(build_dir)

@@ -34,9 +36,10 @@ function! FlyMake(checker, err_regexp, warn_regexp)
   \                                 a:err_regexp)

   " for each warning message, display it and highlight erroneous line
-  if num_errors == 0
-    call s:FlyMakeDisplay('*FlyMakeWarn*', 'Todo', messages, a:warn_regexp)
-  endif
+  " [CHG] display warnings even if num_errors != 0
+  wincmd w   " change current window to the source-window for matching
+  call s:FlyMakeDisplay('*FlyMakeWarn*', 'Todo', messages, a:warn_regexp)
+  "endif

   " cleanup
   call system('rm -r ' . tmp_dir)
@@ -52,8 +55,9 @@ function! FlyMakeCloseWindows()
   endfor
 endfunction

-
-
+function! FlyMakeClearMatches()
+  call clearmatches()
+endfunction

 " Private

@@ -123,10 +127,17 @@ function! s:FlyMakeDisplay(buf, type, msg, regexp)
   endif
   let sorted_keys = sort(keys(dic), function('s:FlyMakeNumberSort'))

-  execute 'match' a:type "'\\%" . sorted_keys[0] . "l'"
-  call cursor(sorted_keys[0], 1)
-  10 new
+  " make pattern
+  let pat = join(sorted_keys, "l\\|\\%")
+  let match_cmd = a:type =~ 'Todo'? "match" : "2match"
+
+  execute match_cmd a:type "'\\%" . pat . "l'"
+
+  "call cursor(sorted_keys[0], 1)
+  5 new
   setlocal bufhidden=wipe buftype=nofile noswapfile
+  " FIXME: suppress 'No lines in buffer' message.
+  normal O
   file `=a:buf`
   for n in reverse(sorted_keys)
     for mes in reverse(dic[n])

結論

QuickFixつかいましょ。

*1:僕は最初に使ったのがx68kのmicro-emacsだったので、PC UNIX圏に入った時は自然とEmacsに流れたんだけど、2年ほど前からフットワークの軽さと強力さを見せつけられ、vimに浮気。で、つい最近「ChangeLog日誌はemacsでつけてるのになぜvimメイン」とか自問自答しているうちにまたなんとなくEmacsに戻ってしまったという優柔不断っぷりです

*2:当時は厨房丸出しで「こんな軟弱なもんいらねーぜ」とかやったんだけども・・・*'_'*