Hatena::ブログ(Diary)

Memo

2016-12-22

[] FlowType のプラグイン作った

この記事は Vim アドベントカレンダー 2016 の23日目の記事です。

id:yuttie さんの comfortable-motion.vim よさそうなので入れてみたが自分の MacVim な環境では "E118: 関数引数が多過ぎます: <SNR>128_tick" が出たので追いかけようと思います。

…と思ったけど、修正版が上がってたのでしばらく使ってみよう。

慣性スクロール素敵。


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 が勝ってる。

非同期の補完

同期の補完を作るのは omnifunc を使ってやればいい。簡単。

非同期の場合は、どうやったらいいのか最初分からなかったが、miyakogi さんの asyncjedi に答えがあった。

<c-x><c-o> を独自の補完関数マッピングしてやり、補完候補のリストを作り、complete() に渡せば良い。


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デバッグした方がやりやすいので、VMUbuntu を立ち上げ、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 なら困らんしとモチベーションが若干落ちたので、放置したままになってしまった。

意欲が湧けばまた取り掛かる。


結論らしきもの

Vim のコードをいじるのは今でもハードルが高いが、コードを変更して、make すればいいだけなので、ここら辺は昨今のフロントエンドの開発や Golang の開発と変わらないなーということで、みんなもっと、パッチ書けばいいと思う。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/heavenshell/20161222/1482419232