この記事は Vim アドベントカレンダー 2016 の23日目の記事です。
id:yuttie さんの comfortable-motion.vim よさそうなので入れてみたが自分の MacVim な環境では "E118: 関数の引数が多過ぎます:
…と思ったけど、修正版が上がってたのでしばらく使ってみよう。
慣性スクロール素敵。
TL;DR
はじまり
Facebook の FlowType ようの Vim プラグインは有る。
GitHub - flowtype/vim-flow: A vim plugin for Flow
諸々挙動が気に入らなくて、最初 PR しようかと思ったが、ローカルの npm のモジュールを使う Pull Request が放置されたり、PR の敷居が高そうだし、直すなら全部書き換えくらいな雰囲気なコードなので自分専用で作った。
GitHub - heavenshell/vim-flood: A simple Vim plugin for facebook flow
どうせなら、job と channel 使って非同期で色々やりたかった。
型チェックのエラー表示は QuickFix に非同期で出す、補完も非同期でやる方法を知りたかった。
# ちなみに Flow は割と速いので同期と非同期の差はほとんど無い
今現在 Flow が持つ機能全て実装してある。
TypeScript には GitHub - Quramy/tsuquyomi: A Vim plugin for TypeScript という素晴らしい IDE があるが、これには及ばない。
エディタ機能については Language Service がある TypeScript が勝ってる。
complete() の微妙な挙動
一通り機能を実装し終わった後に実際に使っていると、補完候補が 1 件の場合の動作が混乱した。
complete() は補完候補を作るが、completeopt で menu かつ noinsert や noselect を指定している場合の挙動がわかりづらかった。
noselect や noinsert は補完候補が沢山ある場合は、候補のみを表示して、実際には補完せずユーザーに選ばせる。
menu は補完候補が 1 件の場合はそれが挿入される。
また普通の omnifunc の場合補完時はエコーエリアに何が起きているかを表示してくれるが、complete() はしない。
つまり補完候補が 1 件の場合、何も表示されず補完候補が何もないように錯覚する。
# 指摘されて気づいたが completeopt=menuone,noselect とかのように menuone の場合は起きない
なんか分かり辛いなーと思い、この挙動を自分で変えられないかと思った。
Vim のコードを読む。
とりあえず vim のコードを clone して src の下で grep する。
キーワード的に noinsert とか noselect あたりを使うと、edit.c に当たる。
edit.c を読んでいくと、set_completion という関数に当たる。
complete() 時noselect や noinsert が何をやっているかというと、補完時に completeopt にそれらがあれば、KEY_DOWN と KEY_UP のイベントを中で呼び、補完候補の選択位置を変えてる。
vim/edit.c at master · vim/vim · GitHub
そのため completeopt=menu,noselect,noinsert の場合は、補完候補の位置が隠れている状態になる。
ということは補完候補が 1 つなら completeopt=menu と同じ挙動にすれば良いと思う。
本来なら complete() 時もメッセージ出してやればいいのだろうけど、いじる箇所が多くて大変そうなので、簡単な方法から試してみる。
変更を入れる場所がわかったので、あとは補完候補の数を調べれば良い。
compl_length というそれっぽい変数があったので、これかと思い、条件文を加えて、ビルドして Vim を動かしてみたが、うまく動かなかった。
やむをえないので gdb を使う。
ついでに Mac より Linux でデバッグした方がやりやすいので、VM で Ubuntu を立ち上げ、Vim をデバッグオプション付きでビルドする。
出来上がったバイナリを gdb 経由で起動し、set_completion にブレークポイントを張る。
簡単に再現するように以下のような Vim script をでっち上げる。
set completeopt=menu,noselect inoremap <F5> <C-R>=ListMonths()<CR> func! ListMonths() call complete(col('.'), ['January']) return '' endfunc inoremap <F6> <C-R>=ListMonths2()<CR> func! ListMonths2() call complete(col('.'), ['January', 'Feb']) return '' endfunc
F5 や F6 を押せば自動的に gdb でブレークポイントを張ったところで止まる。
で、compl_length の変数の中身を見たが違った。
どこかで補完候補を持ってるはずと、コードを眺めていたら、引数で list_T *list という引数がある。
この中を gdb で見ると、lv_len というものを持っており、これに補完候補数が格納されている。
というわけでこれを使えば良い。
noselect_patch.diff · GitHub
できた。テストとかはまぁ後でいいやと思い、vim-jp にぶん投げて反応を見る。
補完候補が 1 つの際の noselect,noinsert の挙動 · Issue #984 · vim-jp/issues · GitHub
で、menuone でええやんと教えてもらうが、menu の時の挙動がわかりづらいことを説明して、vim_dev に仕様変更としてどうよ?と投げてもらったが、既存補完系のプラグインの動き壊すと言われた。
まぁそうだよなーもっともだし、completeopt が menu,noselect,noinsert の時のみこの変更が動くようにすればいいのかなーと思ったが、completeopt=menuone,noselect,noinsert なら困らんしとモチベーションが若干落ちたので、放置したままになってしまった。
意欲が湧けばまた取り掛かる。