NeoVim + coc.nvim + cclsという環境でなかなか快適にC/C++プロジェクトの開発を行えているので、そのセットアップなどを軽く書き残しておく。
coc.nvim
2020年前期の現在、NeoVim向けLSPクライアントのデファクトはneoclide/coc.nvimである。メンテナンスもよくされている。
Conquer of Completionの名は伊達ではなく、非常に高機能でよくできている。自分にとってNvimの補完に関してはこれ一本で一切不満がない(cocを導入を機にShougo/deoplete.nvimをやめてしまった)。
ccls
C/C++のLSPサーバーのデファクトは、MaskRay/cclsだろう。インストールはArch LinuxならAURにccls
がある。その他の人はリポジトリをcloneしてcmake --build Release --target install
すればいいと思う(試してないから分からん)。
ただし、coc.nvimとcclsをインストールするだけでは、C/C++で補完やコードジャンプなどLSPの機能を使うことはできない。cocとcclsとそれぞれ自分で設定する必要がある。
coc.nvimの設定
cocのcclsに関する設定手順はLanguage servers · neoclide/coc.nvim Wikiにまとまっている。
NeoVimを起動して:CocConfig
コマンドを実行すると、$XDG_CONFIG_HOME/nvim/coc-settings.json
が開く。このJSONファイルがcocの設定ファイルとなっている。
{
"languageserver": {
"ccls": {
"command": "ccls",
"filetypes": ["c", "cpp", "cuda", "objc", "objcpp"],
"rootPatterns": [".ccls-root", "compile_commands.json"],
"initializationOptions": {
"cache": {
"directory": "/tmp/ccls-cache"
}
}
}
}
}
上記のように"languageserver"
の下に"ccls"
エントリを追加する。rootPatterns
にマッチするファイルがあるディレクトリが、ccls
のプロジェクトのルートとして識別される。"compile_commands.json"
は後述するが、cclsで使用する.ctags
みたいなファイルである。自分はいらないと思ったが、適宜.git
など追加するといい。
cclsの設定
cclsのセットアップ手順はProject Setup · MaskRay/ccls Wikiにまとまっている。cclsの設定と言うと、cclsのLSPサーバーに対して、シンボルの定義やその定義されたファイルパスなどを教えることと同義になるが、それにはいくつかの方法がある。そのうちもっとも簡便で一般的と思われる、compile_commands.json
を使う方法を紹介する。
compile_commands.json
を使う方法
そもそもcompile_commands.json
とは、C/C++のコンパイルオプションを管理する方法のひとつである。中身は
[
{
"arguments": [
"gcc",
"-c",
"-DHAVE_CONFIG_H",
"-I.",
"-I..",
"-g3",
"-O0",
"-std=c11",
"-Wall",
"-Wextra",
"-Wno-unused-parameter",
"-Wno-sign-compare",
"-Wno-pointer-sign",
"-Wno-missing-field-initializers",
"-Wformat=2",
"-Wstrict-aliasing=2",
"-Wdisabled-optimization",
"-Wfloat-equal",
"-Wpointer-arith",
"-Wbad-function-cast",
"-Wcast-align",
"-Wredundant-decls",
"-Winline",
"-I../include",
"-I/usr/include/gtk-3.0",
"-I/usr/include/pango-1.0",
"-I/usr/include/glib-2.0",
"-I/usr/lib/glib-2.0/include",
"-I/usr/include/harfbuzz",
"-I/usr/include/freetype2",
"-I/usr/include/libpng16",
"-I/usr/include/fribidi",
"-I/usr/include/cairo",
"-I/usr/include/pixman-1",
"-I/usr/include/gdk-pixbuf-2.0",
"-I/usr/include/libmount",
"-I/usr/include/blkid",
"-I/usr/include/gio-unix-2.0",
"-I/usr/include/atk-1.0",
"-I/usr/include/at-spi2-atk/2.0",
"-I/usr/include/dbus-1.0",
"-I/usr/lib/dbus-1.0/include",
"-I/usr/include/at-spi-2.0",
"-I/usr/include/vte-2.91",
"-pthread",
"-o",
"tym-meta.o",
"meta.c"
],
"directory": "/home/<$USER>/src/github.com/endaaman/tym/src",
"file": "meta.c"
},
{
"arguments": [
"gcc",
// 以下略
というようなもので、一つのターゲットごとに"arguments"
、"directory"
、"file"
の3つのプロパティを持つエントリの配列である。
しかしこれを用意すると言っても、こんなものを手で書く人間はいない。この世にはすでに、これを生成するためのツールが存在している。
bearを使ってcompile_commands.json
を作る
rizsotto/Bearを使えば簡単にcompile_commands.json
を作ることが出来る。Arch LinuxユーザーはAURにあるので簡単にインストールできる。その他の人はよく知らないが、一応
Bear is packaged for many distributions. Check out your package manager. Or build it from source.
rizsotto/Bear: Bear is a tool that generates a compilation database for clang tooling.
らしい。使い方は簡単で、
$ bear -- make
bearのコマンド引数について
古いバージョンではbearにキャプチャさせるコマンドはただbear
の後ろに $ bear make
とするだけで良かったが、どこかのバージョンから $ bear -- make all
というように --
を挟むようになった。ある「引数を含む完成されたコマンド」を文字列として引数に持つようなコマンドは(docker
やLXDのlxc
など)--
が使用される。後続の引数が、引数側となっているコマンドのものか、メインで実行してるコマンドのものか明示的に区別するためのものである。>
と実行するだけである。仕組みは単純で、$ make
でコンパイルすると
こんな感じの出力があると思うが、このstdoutをキャプチャしてコンパイルオプションらしきものを集めてcompile_commands.json
に書き出しているだけである。ちなみにmake
以外にもいろいろ対応してるらしいが、自分は試してないのでどこまで面倒見てくれるか分からないので各自READMEを見に行ってほしい。
stdbool.hを#includeしてるのにundeclaredと怒られる問題
stdbool.h
を使用時に
のような警告が出ることがある。これは、stdbool.h
が/usr/lib/clang/10.0.0/include/stdbool.h
のようにclangと一緒に配布される形式になっていることに由来する。このようなclagのリソースの一部でもあるインクルードパス/usr/lib/clang/10.0.0/include
は、cclsがコンパイルされる際に、clang -print-resource-dir
の結果を定数としてcclsに埋め込まれる。なので、cclsをビルドしたあとにclangのバージョンが上がったりするとcclsが参照するインクルードパスにはすでに存在しない状態に陥る可能性がある。ゆえに対処は簡単で、cclsをビルドし直すだけで直る。
.ccls
を使った共通ヘッダの追加
プロジェクトルートに.ccls
という名前のファイルを置くと、そのファイルの各行をプロジェクト共通のコンパイルオプションとして追加することもできる。
たとえば.ccls
ファイルは
-I/path/to/include
%compile_commands.json
のようにすると、compile_commands.json
を引き継ぎつつ、/path/to/include
を共通のインクルードパスに追加できる。%
の他にもいろいろオプションがあるので、詳しくはGitHubリポジトリのwikiを確認してほしい。
あとは使うだけ
ここまでやれば不満なく動くはずである。よきLSPライフを。