Emacs as a C++ IDE
TL;DR -> take a look at the summary.
I have been using Emacs for a few years for development in different languages, including C++. Since I was working mostly on smaller C++ projects and I heard how notoriously hard it is to setup Emacs as a C++ IDE, I never took time to fully set it up. That was working fine for me until I got my hands on a bigger C++ project and figured out I am really missing some of the more advanced features. I decided to give it a go and realized it is not as hard as I expected!
Since it did take some time to try different setups and I feel like information about them is not accessible enough, I decided to share what I learned so others can have an easier start.
Some of the main features that we can expect from C++ IDE:
- Navigation (jump from reference to definition and vice versa).
- File outline (list of symbols in file for quick navigation).
- Code completion.
- Real-time syntax checking.
While there are solutions for C that work quite well (e.g. CEDET), and some of them also work pretty well for C++, none of them fully support C++, since it is more complicated than C. Thanks to release of libclang, which brought capabilities needed to create full support for C++, multiple tools/IDEs specialized for C++ were created. I am going to focus on two of them, Irony and RTags, which are among the most popular Emacs packages offering C++ IDE functionality.
Also, since I work on Linux and usually use CMake with make or ninja as generators, that is what I am going to focus on in examples. I found a lot of great advice and inspiration in blog post by tuhdo, so make sure to check it out for wider overview of available solutions!
Company
Company is a code completion frontend, meaning it handles all the logic regarding code completion while typing except for actually coming up with completions. So basically, Company does all the work with showing and handling completions, but it needs a brain (backend) to provide it with completions. It can use different sources (backends) of completions, including Irony and Rtags, which work really well for C++.
My basic configuration looks like this (without Irony or RTags added as a backend yet):
(req-package company
:config
(progn
(add-hook 'after-init-hook 'global-company-mode)
(global-set-key (kbd "M-/") 'company-complete-common-or-cycle)
(setq company-idle-delay 0)))
Flycheck
Flycheck is package for Emacs that brings on-the-fly syntax checking. It already comes with support for a lot of languages and can also use other packages as backend. In our case, we are going to use it together with Irony or Rtags as backend.
For me, this might just be the most important package of the ones listed here, as it helps you write code that will most likely compile in first try. I find it super useful, it saves me a lot of time.
My basic configuration looks like this (without Irony or RTags added as a backend yet):
(req-package flycheck
:config
(progn
(global-flycheck-mode)))
Compilation database
In order for both Irony and Rtags to work, we need to supply them with detailed information about our C++ project.
This is done by providing compilation database, which is a standard way to describe how your project is compiled. Compilation database is (usually) just a JSON file that contains compilation information for each translation unit and there are multiple ways to generate it.
If you are using Cmake to build your project, it is really easy to generate compilation database, since CMake has support for it.
You just provide CMake with correct flag (cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ...) and that is it! Later you can just call cmake . and it will update compilation database if needed.
This is what I used for my project, since it uses CMake.
In case tool that you are using to build your project does not have support for generation of compilation database, you can use bear tool, which records all calls to compiler by your build system and builds compilation database from it. It is easy to use and gives very good results.
Irony
Irony mode brings IDE functionality to C, C++ and Objective-C. It consists of Emacs package (client) and Irony server (which runs on your machine and uses libclang). Features are: code completion, real-time syntax checking, live documentation.
Irony is not hard to set up, just follow instructions from their documentation: you put configuration in your
init.el file and that is it, it will automatically run irony-server.
Irony is going to find compilation database on its own, as long as it is positioned in some logical place, so you only have to generate it and make sure it is up to date.
In my case, since I have an out of source build, compilation database was in directory build/ which is sibling of directory source/, and Irony had no problem picking it up.
What I really like about Irony is that even on big projects (10k files) it is still fast and does not consume noticable amount of resources.
Lacking support for header files
Bad side when using Irony is that it does not know how to work with header files (.h, .hpp), because compilation database provides compilation information for source files but not for headers, meaning you get all the nifty features only for source files! This was a pretty big deal for me, and at the time of writing of this post author of Irony said he is actively working on solution for this, which is great.
Luckily, Irony has fallback options when compilation database is not enough or not available, so we can use those while waiting for solution.
In my case, I used .clang_complete file as a fallback option, which results with Irony using compilation database for source files and .clang_complete file for header files.
.clang_complete is a file with special format, originally used by a Vim plugin to provide code completion. It is simpler than compilation database since it can not provide specific compilation commands for each source file, instead it provides compilation flags for all files in the project. In most cases that is enough for Irony to work with your header files.
Code completion
Irony code completion works really well and fast, and can be provided as backend for different code completion frontends (Company, AC). I use it with Company.
Real-time syntax checking
Irony integrates with Flycheck and works really well. Syntax checking is fast and on spot.
Configuration
Here is my configuration in init.el:
(req-package irony
:config
(progn
;; If irony server was never installed, install it.
(unless (irony--find-server-executable) (call-interactively #'irony-install-server))
(add-hook 'c++-mode-hook 'irony-mode)
(add-hook 'c-mode-hook 'irony-mode)
;; Use compilation database first, clang_complete as fallback.
(setq-default irony-cdb-compilation-databases '(irony-cdb-libclang
irony-cdb-clang-complete))
(add-hook 'irony-mode-hook 'irony-cdb-autosetup-compile-options)
))
;; I use irony with company to get code completion.
(req-package company-irony
:require company irony
:config
(progn
(eval-after-load 'company '(add-to-list 'company-backends 'company-irony))))
;; I use irony with flycheck to get real-time syntax checking.
(req-package flycheck-irony
:require flycheck irony
:config
(progn
(eval-after-load 'flycheck '(add-hook 'flycheck-mode-hook #'flycheck-irony-setup))))
;; Eldoc shows argument list of the function you are currently writing in the echo area.
(req-package irony-eldoc
:require eldoc irony
:config
(progn
(add-hook 'irony-mode-hook #'irony-eldoc)))
Projectile
Projectile is a really nifty package, that "teaches" Emacs the concept of project.
This means that when working on a certain source file, it will understand which project this file belongs to and offer some nice features based on that.
Projectile usually works out of the box, since it will detect your VCS (I use git) files automatically and figure out the project root from that.
If your case is more complicated, you can just create empty .projectile file in the root directory of your project and Projectile will detect it.
Setting it up is as easy as:
(req-package projectile
:config
(progn
(projectile-global-mode)
))
Features I found most useful are jumping to a file/buffer in project and switching from source file to corresponding header file.
Helm
Helm is Emacs incremental completion and selection narrowing framework. What that actually means is that if you use Helm, most of the searches/selections (finding file/buffer, browsing kill ring, executing command, …) you do will have the same interface: Helm interface. It also comes with some other useful features, and there are many integrations with other packages.
Helm does not bring any C++ specific features, but since it is useful and adds to making Emacs a C++ IDE, I thought it is worth mentioning it.
My setup for it goes like this:
;; Helm makes searching for anything nicer.
;; It works on top of many other commands / packages and gives them nice, flexible UI.
(req-package helm
:config
(progn
(require 'helm-config)
;; Use C-c h instead of default C-x c, it makes more sense.
(global-set-key (kbd "C-c h") 'helm-command-prefix)
(global-unset-key (kbd "C-x c"))
(setq
;; move to end or beginning of source when reaching top or bottom of source.
helm-move-to-line-cycle-in-source t
;; search for library in `require' and `declare-function' sexp.
helm-ff-search-library-in-sexp t
;; scroll 8 lines other window using M-<next>/M-<prior>
helm-scroll-amount 8
helm-ff-file-name-history-use-recentf t
helm-echo-input-in-header-line t)
(global-set-key (kbd "M-x") 'helm-M-x)
(setq helm-M-x-fuzzy-match t) ;; optional fuzzy matching for helm-M-x
(global-set-key (kbd "C-x C-f") 'helm-find-files)
(global-set-key (kbd "M-y") 'helm-show-kill-ring)
(global-set-key (kbd "C-x b") 'helm-mini)
(setq helm-buffers-fuzzy-matching t
helm-recentf-fuzzy-match t)
;; TOOD: helm-semantic has not syntax coloring! How can I fix that?
(setq helm-semantic-fuzzy-match t
helm-imenu-fuzzy-match t)
;; Lists all occurences of a pattern in buffer.
(global-set-key (kbd "C-c h o") 'helm-occur)
(global-set-key (kbd "C-h SPC") 'helm-all-mark-rings)
;; open helm buffer inside current window, not occupy whole other window
(setq helm-split-window-in-side-p t)
(setq helm-autoresize-max-height 50)
(setq helm-autoresize-min-height 30)
(helm-autoresize-mode 1)
(helm-mode 1)
))
;; Use Helm in Projectile.
(req-package helm-projectile
:require helm projectile
:config
(progn
(setq projectile-completion-system 'helm)
(helm-projectile-on)
))
As you can see from the comments, I have an issue with helm-semantic not doing syntax coloring, I haven't figured out yet how to fix that.
Summary
With few packages, Emacs can become a full-fledged C++ IDE!
I use Company, Flycheck, Helm, Projectile and Irony and/or RTags. Most of these packages require pretty simple setup, Irony and RTags being the most demanding but still manageable with medium effort.
Irony and RTags are the "brain", as they understand your C++ project (thanks to libclang) and provide code completion and other features. Although they are alternatives to each other and are not meant to be used together, I found that combination of the two is sometimes the best option since they have different strengths and weaknesses. In the future I hope to be able to use just one of them for projects of all types/sizes.
I hope this post will give you a good idea of how to get more out of Emacs for C++ development! I am also sure there is a lot that can be improved over my setup, so please write your suggestions and I will do my best to try them out and update this post. And again, big thanks to tuhdo for writing his blog post that helped me a lot while setting up Emacs for C++ development, check it out.