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


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

環境

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 などに追記する必要はないと思うが,パッケージが見つからない場合は下記のページを元に設定してほしい.

デフォルトでは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 の各セクションに追記する

(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を読むか,redditに投稿されていたやつを使うとよい.

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

プロジェクト単位の動作

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

  • Makefile
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
namespace lib {
    int add(int x, int y);
}
  • lib.cpp
namespace lib {
    int add(int x, int y) {
        return x + y;
    }
}
  • main.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 内の関数が補完にでてくる.

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

詳細な設定

  • clang-format

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

  • 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

感想

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