Emacs

C++11時代のEmacs C++コーディング環境

More than 1 year has passed since last update.

ここ数年、年末年始にはEmacsの環境を見直すのが自分の中で恒例となっています。最近、本格的にC++11でコーディングすることが増えてきたのですが、これまで使っていた環境 (auto-complete-clang-async, GNU global (helm-gtags)) だと不自由することが多くなってきました。そこで、環境を色々見直して見たところ、現時点では以下の組み合わせがベストではないかと思います。

用途 パッケージ
定型文の挿入 yasnippet
コードの自動入力補完 company-mode + irony-mode
自動エラーチェック flycheck + flycheck-irony
タグジャンプ rtags

ということで、要は Emacs for C++の環境を実際に構築してみました、というだけですが、こちらの記事には実際の設定方法などが詳しく書かれていないので、その辺りを中心に紹介したいと思います。

なお、使用環境はOSがUbuntu 14.04 (X環境)、Emacsは個人的には25.1.50.2 (snapshot版) ですが、この記事の内容についてはUbuntu 14.04付属の24.3でも動作することを確認しています。

準備

まず、環境構築に必要となるパッケージを入れておきます。Ubuntu 14.04ならapt-getで入れるだけです。

$ sudo apt-get install llvm-dev clang libclang-dev cmake

cmakeでプロジェクトをビルドできるようにする

cmakeは、autoconfのようなMakefileを生成するためのツールです。コードの補完やタグジャンプを正確に行うには、インクルードパスやプリプロセッサ設定などのビルド条件をきちんと指定する必要がありますが、cmakeはこれらをJSONファイルに書き出すことができ、irony-modeとrtagsはこのファイルを読み込むことができるため、cmakeでビルドできるようにしておくとirony-modeとrtagsの設定がとても楽になります。

私も今回初めてcmakeを使いましたが、使ってみるとcmake自体非常に便利で、覚えるのにそれほど手間はかからないので、サクッと覚えてしまうのがいいと思います。また、既にMakefile等でビルド環境が構築されている既存プロジェクトの場合、このためだけにcmakeでビルドできるようにするのは面倒ですが、完全にビルドが通る必要はなく、ソースファイルの所在とインクルードパス、プリプロセッサ設定を指定する程度でも十分です。

例えば、トップディレクトリ以下から再帰的にすべての.cppファイルを対象のソースファイルとする場合、

CMakeLists.txt
cmake_minimum_required(VERSION 2.8)

set(CMAKE_CXX_FLAGS "-std=c++11 -Wall")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_definitions("-I/foo/dir1") # インクルードパスを追加
add_definitions("-DMACRO1=bar") # プリプロセッサ定義を追加

file(GLOB_RECURSE SRC_FILES "*.cpp") # 再帰的に.cppファイルを検索する
add_executable(cmake_test ${SRC_FILES})
target_link_libraries(cmake_test pthread) # リンクするライブラリを追加

のように、CMakeLists.txtという名前のファイルを作ればOKです。そして、

$ cmake .

とすれば、Makefileと同時にcompile_commands.jsonというJSONファイルが生成されます。

なお、CMakeLists.txtを書くときは、MELPAからcmake-modeを入れておくと色付けされて見やすくなるのでおすすめです。

各パッケージの設定

yasnippet

yasnippetについてはこの記事では改めて説明しませんが、irony-modeと併用することで関数の引数部分まで補完ができるようになるので、入れておくと便利です。もちろん、yasnippet単体でも定形パターンの挿入に活躍します。C++11のイディオムはデフォルトでは用意されていないものが多いので、よく使うパターンは自分で登録しておくとよいでしょう。

インストールはMELPAからyasnippetを入れるだけで、設定は以下の通りです。TABのキーバインドを無効にしていますが、これは関数引数の補完時に後述のcompany-modeの候補選択とyasnippetのフィールド移動のキーが被ってしまうためです。この設定を行うことで、被っているときはcompany-mode優先となります (このとき、フィールド移動はC-iで可能)。一方、company-modeの補完が起動していないときは、TABでもフィールド移動できます。

yasnippet
(eval-after-load "yasnippet"
  '(progn
     ;; companyと競合するのでyasnippetのフィールド移動は "C-i" のみにする
     (define-key yas-keymap (kbd "<tab>") nil)
     (yas-global-mode 1)))

company-mode

company-modeはauto-completeと同様の自動入力補完を行う拡張で、irony-modeのフロントエンドとして使用します。ac-ironyを使うとauto-completeもフロントエンドとして使用可能ですが、company-modeの方がデフォルトなのと、ac-ironyだとyasnippetとの連携がうまく行かなかったので、ここではcompany-modeを使用します。私はauto-completeをあまり使いこなしていなかったので特に問題はありませんでしたが、auto-completeを色々活用している方は、このcompany-modeへの乗り換えはネックになるかもしれません。

インストールはMELPAからcompanyを入れるだけで、設定は以下の通りです。auto-completeユーザのための company-modeの設定を参考にさせて頂きました。face関連の設定は、color-theme (私はsolarized-themeを使用) の方で行われているらしく、何もしなくてもそれなりの表示になったので、追加していません。また、私は今のところ自動補完を利用していますが、自動補完しない場合はコメントアウトしてある行を有効にして下さい。その場合はC-M-iで補完を行います。

company
(when (locate-library "company")
  (global-company-mode 1)
  (global-set-key (kbd "C-M-i") 'company-complete)
  ;; (setq company-idle-delay nil) ; 自動補完をしない
  (define-key company-active-map (kbd "C-n") 'company-select-next)
  (define-key company-active-map (kbd "C-p") 'company-select-previous)
  (define-key company-search-map (kbd "C-n") 'company-select-next)
  (define-key company-search-map (kbd "C-p") 'company-select-previous)
  (define-key company-active-map (kbd "<tab>") 'company-complete-selection))

irony-mode

irony-modeは、libclangを利用してコードの入力補完を行う拡張です。auto-complete-clang-async等も同様ですが、libclangとelispの間のやり取りのために専用のネイティブアプリ (irony-server) を使用します。

インストールはMELPAからironyを入れるだけで、この中にirony-serverのコードも含まれています。初回起動時にirony-serverのインストールが促され、"M-x irony-install-server" でビルド&インストール (インストール先は ~/.emacs.d/irony/bin/) されます。

設定は以下の通りです。clangに渡すコンパイルオプションとして "-std=c++11" を設定しているのと、companyのバックエンドとして登録しています。3行目は、前述のcmakeで生成されたJSONファイルを読み込むための設定です。

irony
(eval-after-load "irony"
  '(progn
     (custom-set-variables '(irony-additional-clang-options '("-std=c++11")))
     (add-to-list 'company-backends 'company-irony)
     (add-hook 'irony-mode-hook 'irony-cdb-autosetup-compile-options)
     (add-hook 'c-mode-common-hook 'irony-mode)))

ここまでで、何か適当なC/C++ソースコードを開いて自動入力補完ができることを確認できると思います。また、cmakeでcompile_commands.jsonが生成されていれば、ビルド設定が適切に反映された状態で補完が行われるはずです。

flycheck

flycheckについても改めて説明する必要はないと思いますが、設定方法だけ書いておきます。エラーのポップアップ表示やキーバインドはお好みで。

flycheck
(when (require 'flycheck nil 'noerror)
  (custom-set-variables
   ;; エラーをポップアップで表示
   '(flycheck-display-errors-function
     (lambda (errors)
       (let ((messages (mapcar #'flycheck-error-message errors)))
         (popup-tip (mapconcat 'identity messages "\n")))))
   '(flycheck-display-errors-delay 0.5))
  (define-key flycheck-mode-map (kbd "C-M-n") 'flycheck-next-error)
  (define-key flycheck-mode-map (kbd "C-M-p") 'flycheck-previous-error)
  (add-hook 'c-mode-common-hook 'flycheck-mode))

flycheck-irony

ironyをflycheckのチェッカーとして使用するための拡張です。ironyでcompile_commands.jsonを使ってビルド条件が適切に設定されていれば、それに従ったエラーチェックが行われるため、flycheckのC/C++用デフォルトチェッカーより正確にエラーチェックができます。

インストールはMELPAからflycheck-ironyを入れればOKで、設定は以下の通りです。

flycheck-irony
(eval-after-load "flycheck"
  '(progn
     (when (locate-library "flycheck-irony")
       (flycheck-irony-setup))))

注意点として、この設定を行うとC/C++用デフォルトチェッカーがflycheck-ironyに設定されるため、追加でチェッカーを登録したい場合はflycheck-ironyにchainするように設定する必要があります。例えば、flycheck-google-cpplintを追加のチェッカーとして登録するには、以下のように設定します。

flycheck-google-cpplint
(flycheck-add-next-checker 'irony '(warning . c/c++-googlelint))

rtags

rtagsは、GNU globalやcscopeのようなソースコードのタグ付けを行うためのツールです。libclangを利用することで、従来のツールより格段に精度の高いタグ付けを行うことができます。

rtagsはサーバ・クライアント型のネイティブアプリと各エディタ用のフロントエンドから構成されます。アプリはGitHubのリポジトリからソースを入手し、以下のようにビルド&インストールして下さい。

git clone --recursive https://github.com/Andersbakken/rtags.git
cd rtags
cmake .
make
sudo make install

これで、サーバアプリ (rdm/rp) とクライアントアプリ (rc) が/usr/local/bin/にインストールされます。root権限がない場合は、~/bin/など適当な場所にこの3つをコピーして下さい。インストールできたら、

$ rdm --daemon

でサーバアプリを起動した後、タグ付けしたいプロジェクトのディレクトリに移動して

$ rc -J .

でタグが生成されます。compile_commands.jsonを生成していない場合は、

$ rc -c gcc -I/foo/dir1 -DMACRO1=bar -c hoge.cpp

のように "rc -c" の後にコンパイルするコマンドを渡す必要がありますが、実際にrtagsを使用したいそれなりに大きなプロジェクトでは現実的でないので、cmakeによるJSONファイルの生成が前提と考えた方がよいでしょう。

Emacs用のrtagsのフロントエンドはrtags.elです。MELPAからrtagsをインストールするか、上記でmake installすると/usr/local/share/emacs/site-lisp/rtags/にインストールされています。設定はrequireするだけですが、デフォルトではキーバインドが設定されていないので、使いやすいキーに割り当てましょう。私はキーバインドをhelm-gtagsと共用して、rtagsのタグが生成されている場合だけrtagsのキーバインドが有効になるように以下の設定をしています。

rtags
(when (require 'rtags nil 'noerror)
  (add-hook 'c-mode-common-hook
            (lambda ()
              (when (rtags-is-indexed)
                (local-set-key (kbd "M-.") 'rtags-find-symbol-at-point)
                (local-set-key (kbd "M-;") 'rtags-find-symbol)
                (local-set-key (kbd "M-@") 'rtags-find-references)
                (local-set-key (kbd "M-,") 'rtags-location-stack-back)))))

最後に

個人的には、これで満足できるコーディング環境になりましたが、rtags.elのI/Fは若干慣れないのでhelm-gtagsのようなhelm I/Fを作ってみようかなと思っています。rtags.elには、既にhelm I/Fが用意されていました。

rtags-helm
(custom-set-variables '(rtags-use-helm t))

を設定しておくことで、選択バッファがhelm I/Fになります。

なお、上記の設定をまとめたものは、Gistに載せてありますので、ご参考まで。

おまけ

Emacs24.5まででは、C+11の新しい構文 (例えばenum class) を中心にインデントがうまく行かないケースがあります。これらのいくつかは開発中のEmacs25のcc-modeで改善されているので、利用可能な方はEmacs25のsnapshot版を使ってみることをおすすめします。Ubuntuなら以下の手順で利用可能です。

$ sudo add-apt-repository ppa:ubuntu-elisp
$ sudo apt-get update
$ sudo apt-get install emacs-snapshot

ただし、開発中で毎日更新されているため、時々不安定だったり自動ビルドに失敗していてインストールができなかったりすることがあるので、その点はご注意下さい。