補完プラグイン用 source の asyncomplete-omni.vim を作りました

## はじめに

この記事は [Vim Advent Calendar 2017](https://qiita.com/advent-calendar/2017/vim) 9日目の記事です。
昨年は [タブページ数に応じて幅が変わる tabline プラグインを作成しました - Qiita](https://qiita.com/yami_beta/items/6c999fe18fa3fa154cd3) という記事を Advent Calender で書きました。
今年は [yami-beta/asyncomplete-omni.vim](https://github.com/yami-beta/asyncomplete-omni.vim) というプラグインを作ったので、こちらを Advent Calener で掲載します。

## asyncomplete-omni.vim

asyncomplete-omni.vim は [prabirshrestha/asyncomplete.vim](https://github.com/prabirshrestha/asyncomplete.vim) 用の source でオムニ補完候補を渡すプラグインとなっています。

![asyncomplete-omni-demo.gif](https://qiita-image-store.s3.amazonaws.com/0/31403/af4c2605-0634-6548-0788-eaffdbef98ff.gif)

### asyncomplete.vim とは

prabirshrestha 氏による Vim 8, Neovim 用の補完プラグインです。
Language Server Protocol 用のプラグインである [prabirshrestha/vim-lsp](https://github.com/prabirshrestha/vim-lsp) の作者でもあります。

## 作った経緯

補完プラグインの乗り換え先を探していたときに偶然 asyncomplete.vim を発見しました。
asyncomplete.vim 自体は補完プラグインというより、補完ポップアップを表示するためのライブラリみたいな実装です。

中身を見たときに、オムニ補完用 source を作れば filetype ごとに source を入れなくても良いのではないだろうか、という **思いつきで作り始めました。**

## asyncomplete-omni.vim の仕組み

asyncomplete-omni.vim は以下の流れで補完候補を取得しています。
基本的には `omnifunc` を実行し、補完候補を asyncomplete.vim に渡しているだけです。

1. 現在のバッファの `omnifunc` を Funcref で取得
2. `asyncomplete#sources#omni#completor` の引数でカーソル位置と入力中のテキストが取得
3. オムニ補完の Funcref を第一引数を `1` にして呼び出す（１回目）
    - `omnifunc` は第一引数の値で、補完テキストの始点を返す or 補完候補を返す
    - `omnifunc` によっては、第一引数を `1` にした状態で1度呼び出さないと補完候補を用意しないものがある
    - `:h complete-functions`
4. カーソル位置と入力中のテキストををオムニ補完の Funcref に渡して呼び出す（2回目）
5. 取得した補完候補を asyncomplete.vim に合わせて整形し返す

## Github に公開してみると

自分がよく使う filetype で補完候補が出ることを確認した後、Github に公開したのが 2017年5月27日です。
それから2週間ほどで以下の issue が出来ていました。
[send PR to asyncomplete.vim to be included in README.md · Issue #1 · yami-beta/asyncomplete-omni.vim](https://github.com/yami-beta/asyncomplete-omni.vim/issues/1)

asyncomplete.vim の作者である prabirshrestha 氏から README に載せるから Pull Request を送ってくれという内容でした。
思いつきで作ったプラグインが、まさか作者本人に試してもらえるとは思いもよりませんでした。

こうして asyncomplete-omni.vim は asyncomplete.vim の README に、作者以外で最初に載った source になりました :tada: 
(コミットログで確認)

## asyncomplete-omni.vim を自分で使ってみて

ノリと勢いで作った感があるプラグインですが、しばらく試した後完全に乗り換えました。
強力な補完や高速な補完が欲しいというよりは、とりあえず補完候補が自動で出ればラッキーなくらいのものを自分は求めており asyncomplete.vim が程よく条件を満たしていたので、乗り換え出来ました。

asyncomplete-omni.vim を使っていて一番驚いたのが [fatih/vim-go](https://github.com/fatih/vim-go) を入れて Go のコードを書き始めたときです。
vim-go は `omnifunc` を設定するため asyncomplete-omni.vim で自動で補完候補が表示でき、これが思っていた以上に快適でした。
gocode が高速なこともあり asyncomplete.vim, asyncomplete-omni.vim, vim-go の組み合わせで、それなりに快適な Go を書く環境が出来ました。

## asyncomplete-omni.vim の欠点

- 非同期では無い
- `omnifunc` によっては上手く動かない

asyncomplete-omni.vim という名前にも関わらず、非同期処理ではありません。
以下のような処理を `job` かなにかで出来れば、非同期化も可能かもしれないと思いつつ、中身が複雑になりそうなので手をつけていません…

1. 現在のバッファを一時ファイルとして保存し、別バッファで開く
2. 別バッファで `omnifunc` を実行し補完候補を取得
3. 元のバッファで補完候補を受け取り、asyncomplete.vim で表示

また `omnifunc` によって、動かない場合があることも把握しています。
下記の issue では、カーソルを移動させる `omnifunc` があることが分かりました。
[Cursor jumps around with completions · Issue #4 · yami-beta/asyncomplete-omni.vim](https://github.com/yami-beta/asyncomplete-omni.vim/issues/4)

## おわりに（asyncomplete-omni.vim の今後）

昨今、Language Server Protocol (LSP) の普及により、様々な言語で Language Server が用意されるようになりました。
asyncomplete.vim にも LSP 用の source である [prabirshrestha/asyncomplete-lsp.vim](https://github.com/prabirshrestha/asyncomplete-lsp.vim) があるため、いずれこの source だけであらゆる言語の補完が可能になる未来がやってくるかもしれません。
もしそうなれば asyncomplete-omni.vim は役割を失いますが、それはむしろ喜ばしいことではないかと個人的には考えています。

様々な言語で、開発環境を問わず同じ支援が受けられるようになる日まで、緩やかに asycomplete-omni.vim の開発を続けていこうと思います。