EmacsでC/C++用にclangdとclang-formatを使う

この頃のエディタはlanguage server protocol (LSP) という規格で，補完とか整形を外部ツールと通信することで実現している．Emacsでもclangd/clang-formatというLLVMが提供するツールを使えば簡単にリッチなC/C++環境が作れる．

## 環境

- Spacemacs 0.200.13@26.1
- Ubuntu 18.04

## clangd のインストール

Debian系のLinuxを使っていればaptが使える．現時点でstableなver 6.0を次のように入れた．

```
sudo apt-get install clang-tools-6.0 clang-format-6.0
```

18.04なら恐らく `/etc/apt/sources.list` などに追記する必要はないと思うが，パッケージが見つからない場合は下記のページを元に設定してほしい．

http://apt.llvm.org/

デフォルトではPATHが通っていないので，以下のように `.bashrc` に追記して通した．

```
export PATH=/usr/lib/llvm-6.0/bin:$PATH
```


## Emacs 向けの設定

```
M-x package-install lsp-mode lsp-ui company-lsp clang-format
```

```init.el
  (add-hook 'c++-mode-hook 'company-mode) ; 補完用
  (add-hook 'c++-mode-hook 'flycheck-mode) ; チェック用
  (add-hook 'c++-mode-hook #'lsp)
```

- lsp-mode: 各種LSPの言語モードを実装している C/C++ の設定もここに含まれている．
- company-lsp: 補完を担うcompany-modeのLSP向け設定
- lsp-ui: ドキュメントの表示など各種アクションを実装している.デモ用のGIF動画がある https://github.com/emacs-lsp/lsp-ui

## Spacemacs向けの設定

`.spacemacs` の各セクションに追記する

``` lisp
(defun dotspacemacs/layers ()
  ;; ...
  (setq-default
   ;; ...
   dotspacemacs-additional-packages
   '(
     ;; LSP用のパッケージ
     lsp-mode
     lsp-ui
     company-lsp
     clang-format
     )
  ))

(defun dotspacemacs/user-config ()
  ;; ...
  (add-hook 'c++-mode-hook 'company-mode) ; 補完用
  (add-hook 'c++-mode-hook 'flycheck-mode) ; チェック用
  (add-hook 'c++-mode-hook #'lsp)
  )
```

## ファイル単位の動作

ファイルを開くと選択肢がでてくるので `Do nothing ...` を選んだ．

clang-formatのデフォルトのコマンドは `M-C-\` らしい．デフォルトのC++モードではC++17から追加になった `if constexpr` などでフォーマットが崩れていたのできちんと整形されて嬉しい．キーバインドの設定は[README](https://github.com/sonatard/clang-format)を読むか，[redditに投稿されていたやつ](https://www.reddit.com/r/emacs/comments/7uq9w1/replace_emacs_c_autoformatting_with_clangformat/
)を使うとよい．

flycheckがコンパイル時のWarningやErrorの箇所が色付き下線で表示する，そこにカーソルを置くとメッセージが読める．companyは標準ライブラリなどは補完してくれる．競プロとかで便利そう．`_大文字`から始まる内部データまで見えるのは，よくないが...うまく設定できないだろうか

![image.png](https://qiita-image-store.s3.amazonaws.com/0/37859/785daec7-3096-6e42-8682-9862ce5e2363.png)

## プロジェクト単位の動作

以下の２つのファイルを同じディレクトリに置いた．

- Makefile

``` make
CXXFLAGS := -std=c++17 -Wall -Wextra -std=c++17 -I$(HOME)/tool/boost_1_67_0/stage/include -L$(HOME)/tool/boost_1_67_0/stage/lib -lboost_serialization

main.out: main.cpp lib.o
	$(CXX) $^ -o $@ $(CXXFLAGS)

%.o: %.cpp
	$(CXX) -c $< $(CXXFLAGS)
```

- lib.hpp

```c++
namespace lib {
    int add(int x, int y);
}
```

- lib.cpp

```c++
namespace lib {
    int add(int x, int y) {
        return x + y;
    }
}
```

- main.cpp

```cpp
#include <boost/serialization/serialization.hpp>
#include "lib.hpp"

using namespace boost::serialization;

int main() {
  auto x = lib::add(1, 2);
}
```

`Import project root ...` みたいなやつを選ぶと，lsp-uiが起動して明らかに先程の1ファイルを開いたときとは違う見た目になる．そして当たり前のようにローカルの `lib.hpp` 内の関数が補完にでてくる．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/37859/97fcbe3f-34e5-7bf6-e1da-8d6a009bb3e5.png)

Makefileに書いただけだが，普通に外部ライブラリのboostも補完された．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/37859/2b7612e1-4a50-61d2-483a-dfaf0f5df518.png)



## 詳細な設定

- clang-format

公式ドキュメントを読んで `.clang-format` をプロジェクト配下に置く．なお再帰的に親ディレクトリを見に行くっぽいので，$HOMEディレクトリによく使う設定を置くと便利．私は下記ページの一番下のVisualStudioっぽい設定が好きなので使っています．

https://clang.llvm.org/docs/ClangFormatStyleOptions.html

- clangd

clangd もさすがに全てのオプション(たとえば-std=c++17とか)は見てくれなさそうだなと思った所，compiledbというツールを使えば設定ファイルのjsonを履いてくれるらしい．便利だ．

設定ファイルの仕様 https://clang.llvm.org/docs/JSONCompilationDatabase.html

今回のMakefileからcompiledbが生成したjsonは以下の通り

```
pip install compiledb
compiledb make
```

```compile_commands.json
[
 {
  "directory": "/home/skarita/Desktop/test",
  "arguments": [
   "g++",
   "-c",
   "lib.cpp",
   "-std=c++17",
   "-Wall",
   "-Wextra",
   "-std=c++17",
   "-I/home/skarita/tool/boost_1_67_0/stage/include",
   "-L/home/skarita/tool/boost_1_67_0/stage/lib",
   "-lboost_serialization"
  ],
  "file": "lib.cpp"
 },
 {
  "directory": "/home/skarita/Desktop/test",
  "arguments": [
   "g++",
   "main.cpp",
   "lib.o",
   "-o",
   "main.out",
   "-std=c++17",
   "-Wall",
   "-Wextra",
   "-std=c++17",
   "-I/home/skarita/tool/boost_1_67_0/stage/include",
   "-L/home/skarita/tool/boost_1_67_0/stage/lib",
   "-lboost_serialization"
  ],
  "file": "main.cpp"
 }
]
```

このjsonファイル以外にも`compile_flags.txt`というファイルに1行1オプション羅列する方法もある．

```compile_flags.txt
-Wall
-Wextra
-std=c++17
-I....
-L....
-lboost_serialization
```

## 感想

今回は簡単なプロジェクトで使ってみたが，数十ファイルの巨大プロジェクトでもかなり軽快に動いている印象がある．外部プロセスなのでエディタに負荷が少ないのかもしれない．