VSCode＆ccls＆TabNineを使った快適な補完を目指してみた

# この記事について

筆者が愛用している「VSCode」と
C++の[Language Server（以下、LSと略）](https://langserver.org/)である「ccls」を連携して使ってみた感想と
最近少し話題になっている「TabNine」を連携させて使ってみた感想を記します

# cclsについて

一言で言えば「C++専用のLS」です
C++用LSは他に「clangd」と「cquery」が存在します
「clangd」は[動作が不安定らしいです](https://www.reddit.com/r/cpp/comments/cafj21/ccls_clangd_and_cquery/et8l9ie/)
※ただし、後述する「ccls」の作者も「clangd」のコミッターの一人なので、今後良くなっていく可能性があります
「cquery」は結構快適に動作するようですが、[masterブランチへのマージコミットが2018/11/27で更新が停止してしまっています](https://github.com/cquery-project/cquery/commits/master)
「ccls」は「cquery」の快適さをそのままに色々改善したLSらしく、[作者がその良さを語っています](https://github.com/MaskRay/emacs-ccls/issues/1#issuecomment-386179958)

今回は、あまり日本語記事が存在しないので「ccls」を取り上げてみることにします

# TabNineについて

昨今流行りの深層学習を利用した補完エンジン
話題のツイート：
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">これはすごい！ソースコードを解析しあらゆる言語のオートコンプリートを提供するのが、TabNine 。新しいリリースでは、<br><br>①GitHubにある2万本以上のファイルを分析<br>②GPT-2で次に予想されるコードを提案（Pythonの例↓）<br><br>プログラミングのハードルもだいぶ下がるはず🐍。<a href="https://t.co/gIroluRwcK">https://t.co/gIroluRwcK</a> <a href="https://t.co/vXD4D9B18W">pic.twitter.com/vXD4D9B18W</a></p>&mdash; Sangmin @ChoimiraiSchool (@gijigae) <a href="https://twitter.com/gijigae/status/1152016462397108224?ref_src=twsrc%5Etfw">July 19, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

2019/09/10時点ではProfessional版もバージョンβということで無料で使用できます
[詳しくは公式サイトのsubscribeを見たほうがいいです](https://tabnine.com/subscribe)
![e6e6f081e3073ce0fff201e266d90f47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102564/7b0a119d-f0ff-edc3-f76a-47222622b088.png)

深層学習でクラウドのGPUアクセラレートを使用して高速に補完することも可能ですが、
その場合プライベートなコードが漏れる可能性があります
[デフォルトでは無効化されています](https://tabnine.com/faq#leak)
一応セルフホストでGPUアクセラレートを使用できるみたいですが、公式に問い合わせが必要となっています

# 勘違いするところ

筆者だけかもしれませんが、勘違いしやすいところを明記しておきます

* cclsはLSなので「補完以外にも定義ジャンプや引数の詳細表示などを支援」してくれます
* TabNineは「補完に特化しているので **それ以外の機能はサポートしていません** 」

# TabNineのセマンティック補完

TabNineはあくまで「入力された履歴」「周りにあるコード」から推測して補完候補を表示してくれます
ただ、推測で候補表示するため正しくない情報を表示する可能性があります
（例えば、そのクラスには実装されていないメソッド名を候補に出したりなど）

TabNineにはそういった問題を解決するために[「セマンティック補完」](https://tabnine.com/semantic)という機能が備わっています
LSと通信して言語特有の情報を取得し、補完提案するという機能です
これによりより正確な補完候補を表示してくれます
詳しくは[公式ブログ](https://tabnine.com/blog/semantic/)が参考になるかと思います

# 実際にVSCodeで使ってみる

## VSCodeとcclsを連携させる

まずはVSCodeにcclsを入れてどの程度動くのかを確認します

VSCodeの拡張インストール画面から「ccls」を検索してインストールします
![4f422abd7a5168aac4e327479e7c92d2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102564/b40a42ad-9ebc-8d06-3594-410651ae7edc.png)


cclsの実行パスやオプションを[公式Wikiに従って](https://github.com/MaskRay/ccls/wiki/Visual-Studio-Code)VSCodeに設定します
設定例：

```jsonnet:settings.json

{
    // 環境変数PATHにcclsまでの実行パスが入っていれば以下の設定で動きます
    "ccls.launch.command": "ccls.exe",

    // デフォルトでこのパスになっているそうです
    "ccls.cache.directory": "${workspaceFolder}/.ccls-cache/",

    // セマンティックハイライト各種有効化フラグ
    // お好みでどうぞ（めっちゃカラフルになります）
    // "ccls.highlighting.enabled.types": true,
    // "ccls.highlighting.enabled.freeStandingFunctions": true,
    // "ccls.highlighting.enabled.memberFunctions": true,
    // "ccls.highlighting.enabled.freeStandingVariables": true,
    // "ccls.highlighting.enabled.memberVariables": true,
    // "ccls.highlighting.enabled.namespaces": true,
    // "ccls.highlighting.enabled.macros": true,
    // "ccls.highlighting.enabled.enums": true,
    // "ccls.highlighting.enabled.typeAliases": true,
    // "ccls.highlighting.enabled.enumConstants": true,
    // "ccls.highlighting.enabled.staticMemberFunctions": true,
    // "ccls.highlighting.enabled.parameters": true,
    // "ccls.highlighting.enabled.templateParameters": true,
    // "ccls.highlighting.enabled.staticMemberVariables": true,
    // "ccls.highlighting.enabled.globalVariables": true,

    // C/C++拡張をインストールしている場合競合するので
    // 以下の設定をする必要がある
    "C_Cpp.updateChannel": "Insiders",
    "C_Cpp.autocomplete": "Disabled",
    "C_Cpp.formatting": "Disabled",
    "C_Cpp.errorSquiggles": "Disabled",
    "C_Cpp.intelliSenseEngine": "Disabled",
}
```

実際に試したいC++プロジェクトを立ち上げてみます
今回はC++の静的解析として有名な[cppcheck](https://github.com/danmar/cppcheck)をcloneして試してみます
念の為、最新リリース版である1.89タグで実行します

```bash
git clone https://github.com/danmar/cppcheck
cd cppcheck
git checkout 1.89
```

cclsはコンパイルコマンドから、indexとなるキャッシュファイルを生成していい感じに補完を出す手がかりを探すみたいです
そのため、[公式Wiki](https://github.com/MaskRay/ccls/wiki/Project-Setup)にもあるようにcompile_commands.jsonを生成します

```bash
cmake -H. -BDebug -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=YES
ln -s Debug/compile_commands.json
```
Visual Studioのclコマンドを経由する場合、CMakeの CMAKE_EXPORT_COMPILE_COMMANDS は対応していないようですので
ninjaを経由するような感じにします

```bash
cmake -H. -BDebug -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=YES -DCMAKE_C_COMPILER="clang-cl.exe" -DCMAKE_CXX_COMPILER="clang-cl.exe"
ninja -C Debug -t compdb cxx cc > compile_commands.json
```

ここまでできれば設定完了です
VSCodeをcompile_commands.jsonと同じディレクトリで起動ですれば
元気にcclsが動いていることが確認できると思います
![8c301be3dded22de4d6d9581ec2a63bc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102564/61d376cb-29a2-f9c0-bb03-2b0b9e4517bc.png)


デモ：
![demo_ccls.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102564/8afd1a04-f5d9-f3ea-1641-bd7edd99f47b.gif)


## VSCodeとcclsとTabNineを連携させる

ここで一気にTabNineと連携させます

TabNineのVSCode拡張をインストールします
![d76bc8753473c11c08cb6d8b58488b03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102564/b8473bc0-faaa-b2cf-bfbe-f716bb035839.png)

この時点ですでにTabNineは有効化されています
結構いい感じに動いてくれます

コード上に `TabNine::config` を入力します
するとブラウザでコンフィグ画面が立ち上がります
![9500e81704cc1dfb28f141b84b6b6763.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102564/58a30130-28ae-2fa3-85f0-84e3c6dcc414.png)


Configuration pathのtabnine_config.jsonと同じ場所にある
「TabNineExample.toml」をコピーして「TabNine.toml」にリネームします

デフォルトだと「cquery」をC++のLSとして使用するようになっているため
「ccls」を使用するように変更します
サンプル設定：

```TabNine.toml
[language.cpp]
command = "ccls.exe"
args = ["-init={}"]
synchronous_triggers = ["."]
synchronous_timeout_ms = 200
num_server_instances = 2
always_prefer = false
```

コード上で `TabNine::sem` と入力すれば入力したソースコードのセマンティック補完が有効になります 
ちゃんと確認したい場合は「tabnine_config.json」を開き以下のような状態になっていれば有効化されています

```jsonnet:tabnine_config.json
"semantic_status": {
    "cpp": "Enabled"
  },
```

念の為、 `TabNine::restart` を入力すればTabNineを再起動させることができます

デモ(おぼつかなくてすみません・・・)：
![demo_ccls_tabnine.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102564/e63572fd-1e5f-d576-ec03-92e022b21cc7.gif)


# 使ってみた所感

## ccls

* いい感じにタグジャンプや補完は機能している
* 規模によるが、キャッシュファイル生成とそれの読み込みにCPUとメモリを持っていかれる
  * 試したところ、2000ファイルを超えるとかなりPCリソースを食う感じになりました
* セマンティックハイライト標準装備は個人的に嬉しい
* `C_Cpp.intelliSenseEngine` を無効化してしまうと Alt+oでヘッダー⇔ソースの切り替えまで封じてしまうので不便
  * 一応[ソースとヘッダーを切り替えるだけのプラグイン](https://marketplace.visualstudio.com/items?itemName=ryzngard.vscode-header-source) が存在するがそれだけのために入れるのが微妙・・・
* `.ccls-cache` をバージョン管理除外に入れておかないと大量に変更差分が出てしまうので注意が必要
  * 単純に .gitignoreファイルに入れればOK

## TabNine + ccls
* TabNineの補完がかなり強力
  * 特にstd::find_if()の補完は相当便利
* むちゃくちゃ重い＆メモリ食われる
  * TabNineをセマンティック補完有効化したときcppcheckの規模で約2.5GB程度のメモリを食う
    * これはcclsも影響しているかもしれない
  * 重いのはLS自体が結構レスポンスが重いものが多いらしく、それのせいらしい
    * ただ、VSCode + ccls起動した時はそこまで重くなかった
    * VSCode + TabNine単体でもそこまで重くなかった
* TabNineへの命令がコードに直接マジックコメントを書くしかないのが新鮮
  * [マジックコメント一覧](https://tabnine.com/faq#magic_strings)
  * ソースコードを汚すので個人的にはなにか別の方法がほしい
    * コマンドパレットから開くなど

## 総括

* PCマシンがリッチであればTabNine+cclsは十分選択肢に入るのではないかと思いました
  * それが厳しいのであれば、TabNineだけでも十分かなと思いました
* 正直、手探りで色々試したため間違っている箇所が大いにありそうです
  * コメントでご指摘いただけると幸いです