Step-by-step guide to C++ navigation and completion with Emacs and the Clang-based rtags

If you want C++ completion and navigation (jump to definition, jump to declaration, and so forth), there are several good options for Emacs. For a QtQuick / C++ project I’m working on, I needed the best Emacs has to offer.

This turned out to be the Clang-based rtags system.

Rtags is not the easiest of the options to get going, hence this short tutorial. I initially configured irony-mode, which is also Clang-based and was significantly easier to get going, but it soon started hanging on the completion of for example QStringList methods in my project. Because it also doesn’t support navigation, I decided to try rtags. So far, rtags has been working quite well on a Qt 5.x project of slightly under 200K lines of C++.

Pre-requisites

On Ubuntu 14.04, I tested this with both libclang-3.6-dev from the standard repos and with libclang-3.8-dev from the LLVM repos. CMake should also be installed.

On OSX 10.11 (El Capitan), homebrew took care of the dependencies for me (see next step).

Get and build rtags

From the documentation, simply clone the rtags repo and build it:

git clone --recursive https://github.com/Andersbakken/rtags.git
cd rtags
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 .
make

Make a compilation database for your project

rtags needs a compilation database file, usually called compile_commands.json, to figure out what compile options were used to compile which source files. If your project is using cmake, you only have to add the -DCMAKE_EXPORT_COMPILE_COMMANDS=1 to the cmake invocation to generate that file.

In my case, and perhaps in yours, you have to use something else like Qt’s qmake system. In this case, you can use a tool like Build Ear (BEAR) or the Python-rewrite of scan-build. In both cases, you invoke the utility with your normal build command. In my Qt project, I do:

bear --append make -j16

As your project builds, bear will capture all of the compilation commands and put them in compile_commands.json. With my project, the compile_commands.json finds itself in the out-of-source (shadow) build directory.

(The first time, you have to do this after a clean, or a make clean, so all of the compile commands can be captured.)

BEAR is preferable, but on the latest OSX with its new system integrity protection (SIP) and on some linuxes, it won’t work and you’ll end up with an empty compilation database. In these cases, use the intercept-build command from the scan-build package linked above.

Start the rtags daemon RDM

You can do this from emacs, but due to better system process handling, I invoke this from a normal terminal:

cd /where/you/built/rtags/bin
nohup ./rdm &

When working with rtags, always make sure that RDM is running.

Tell RDM about your project using RC

rc is the rtags command one uses to communicate with the rtags daemon RDM.

Importantly, make sure that rc is in your path.

We only have to tell RDM once about the location of any new project. Do this as follows:

cd /your/project/source
rc -J /dir/containing/compile_commands.json/

Note that the -J parameter is the directory containing the compile_commands.json file.

Configure your Emacs

Install the Emacs rtags package:

M-x package-install RET rtags RET

Make sure that you also have the company package installed.

Next, add the following to your Emacs init.el:

;; ensure that we use only rtags checking
;; https://github.com/Andersbakken/rtags#optional-1
(defun setup-flycheck-rtags ()
  (interactive)
  (flycheck-select-checker 'rtags)
  ;; RTags creates more accurate overlays.
  (setq-local flycheck-highlighting-mode nil)
  (setq-local flycheck-check-syntax-automatically nil))

;; only run this if rtags is installed
(when (require 'rtags nil :noerror)
  ;; make sure you have company-mode installed
  (require 'company)
  (define-key c-mode-base-map (kbd "M-.")
    (function rtags-find-symbol-at-point))
  (define-key c-mode-base-map (kbd "M-,")
    (function rtags-find-references-at-point))
  ;; disable prelude's use of C-c r, as this is the rtags keyboard prefix
  (define-key prelude-mode-map (kbd "C-c r") nil)
  ;; install standard rtags keybindings. Do M-. on the symbol below to
  ;; jump to definition and see the keybindings.
  (rtags-enable-standard-keybindings)
  ;; comment this out if you don't have or don't use helm
  (setq rtags-use-helm t)
  ;; company completion setup
  (setq rtags-autostart-diagnostics t)
  (rtags-diagnostics)
  (setq rtags-completions-enabled t)
  (push 'company-rtags company-backends)
  (global-company-mode)
  (define-key c-mode-base-map (kbd "<C-tab>") (function company-complete))
  ;; use rtags flycheck mode -- clang warnings shown inline
  (require 'flycheck-rtags)
  ;; c-mode-common-hook is also called by c++-mode
  (add-hook 'c-mode-common-hook #'setup-flycheck-rtags))

You can either just eval this new code, or restart your emacs.

Using rtags

I mostly use M-. to jump to any symbol’s definition. Pressing M-. again will jump to and fro between definition and declaration. Use =M-,= to see all references to the symbol under the cursor. This is where Helm comes in especially useful.

Use C-c r [ and C-c r ] to jump backwards and forwards through the position stack that rtags builds up as you jump between symbols. You can use C-c r I to get a list of rtags-extracted symbols in the current file, although semantic-mode with helm (C-c h i) gives you slightly less accurate but more colourful results. I use both.

As you type, company-mode will usually interrupt after you’ve entered the first three characters of a symbol or member. I can’t wait that long, so I press C-tab to get a list of completions immediately.

As an additional bonus, you’ll see Clang warnings indicated in bright red (usually) in your code window!

As you’re working, remember to do builds with bear --append as shown above. I do this via M-x compile. rdm will pick up all changes and re-analyse files as you work!

Conclusions and other rambling

I am quite impressed by how well rtags works on complex C++ projects! It is indeed clang under the hood, but rtags has overcome the considerable challenge of exposing this to Emacs in a robust and usable fashion.

With this setup, I am able to take longer breaks from using QtCreator, which I don’t like very much. QtCreator has great potential, but unfortunately its Emacs keyboard emulation is sorely lacking (this could be forgiven) and as a general C++ IDE it still has a long way to go in the usability department. I remain optimistic that with competition like JetBrains (when I’m not in Emacs, I’m in something by JetBrains; their Emacs emulation is bearable!) out there, QtCreator will improve over time.

Feel free to join the discussion in the comments below or on the reddit topic!