Emacs C++ Setup

Published by Matthew on

Out of the box, Emacs is a very powerful text editor, but it isn’t an IDE. It misses a lot of features like checking your code for build errors, jumping to a symbol’s definition, renaming a symbol across your whole project, etc.

This post shows how I’ve set up Emacs to make it comfortable for me to edit C++ in, but it is not a beginner’s guide to Emacs so I assume that you are already comfortable with the basics. If you are stuck, try the excellent built-in help system or have a look at my cheatsheet.

There are a few ways to add these features, for example the ctags/etags system1 that comes with Emacs, but my favourite solution is using an LSP server like clangd. It runs outside of Emacs and communicates via HTTP, and can be instructed to inspect a file, or to rename or format something.

Before starting, you will need to have clangd installed. It is available in Debian Testing and in Debian Backports2 as clangd-8. In Gentoo Linux it comes as a part of the main sys-devel/clang package.


Installing use-package

The first thing in my ~/.emacs.d/init.el ensures that use-package is installed. It is a cool macro that allows you to configure packages in a tidy way, and also tell your package manager to install a package if it is absent.

(require 'package)
(setq package-user-dir (expand-file-name "elpa" user-emacs-directory)
      '(("gnu" . "https://elpa.gnu.org/packages/")
	("melpa" . "https://melpa.org/packages/"))
      use-package-always-ensure t)
(unless (bound-and-true-p package--initialized)
(unless package-archive-contents
(unless (package-installed-p 'use-package)
  (package-install 'use-package))
  (require 'use-package))

CMake Syntax Highlighting

For editing CMake scripts, I will use the cmake-mode package. It isn’t particularly smart, so I also install the cmake-font-lock package to make the syntax highlighting type aware.

(use-package cmake-mode
  :mode ("CMakeLists\\.txt\\'" "\\.cmake\\'"))
(use-package cmake-font-lock
  :after (cmake-mode)
  :hook (cmake-mode . cmake-font-lock-activate))

A screenshot of an Emacs window showing some nicely highlighted CMake scripts.

In-buffer Completion

Company is a completion framework for Emacs. It comes with some auto-completion for CMake scripts and for Emacs Lisp, and it can also get completions from our LSP client when installed later.

(use-package company
  :hook (prog-mode . company-mode))

The prog-mode hook means that company will be loaded in any buffer with a programming language mode (so not for Org or Markdown files). If you only wanted completions for C/C++, you could change this to c-mode-common.


Ivy is like Company, but for your minibuffer (e.g. finding files with C-x f, entering a command with M-x, etc.)

(use-package ivy
  :defer 0.1
  :config (ivy-mode))

Later on the lsp-ivy command can be installed so that Ivy’s fuzzy finding goodness can be used to search through all of the symbols in your project.

LSP Client

Finally, the LSP client lsp-mode can be installed and configured. lsp-ivy is also installed.

(use-package lsp-mode
  :hook (c-mode-common . lsp-deferred)
  :commands (lsp lsp-deferred)
  (lsp-idle-delay 1)
  (lsp-completion-provider :capf)
  (lsp-enable-file-watchers nil)
  (lsp-keymap-prefix "s-l"))
(use-package lsp-ivy
  :commands lsp-ivy-workspace-symbol)


For clangd to work properly, it requires a database of compile commands to be in your project’s root. CMake can be instructed to generate this file (and keep it up to date) if you set the CMAKE_EXPORT_COMPILE_COMMANDS variable.

Here’s how I would configure a project and symlink the compile commands database:

git clone https://git.some-project.org/project.git
cd project
ln -s build/compile_commands.json .
make -Cbuild

Note that this option is only implemented by the Makefile and Ninja generators.

After the compile commands database is generated, open one of the source files and press i to start the clangd server and set the project root.

Try searching for a symbol with M-x lsp-ivy-workspace-symbol, renaming a symbol with s-l r r, formatting the file with s-l = =, or writing some invalid code and observing the error.

Going Further

Performance Tweaks

Requests are sent to the LSP server as JSON, and also responses are also received as JSON. Emacs 27 includes a native JSON parser, so you should see a nice performance improvement by switching to Emacs 27.

Additionally, these responses can be very large. If you notice Emacs freezing a lot, try increasing gc-cons-threshold and read-process-output-max. Put these lines near the top of your init.el, or in early-init.el if on Emacs 27.

(setq gc-cons-threshold (* 512 1024 1024)
      read-process-output-max (* 1 1024 1024))

which-key Mode

There are a lot of key bindings to set and remember, so which-key-mode can show the possible key bindings as you type them.

Install the package and enable it globally:

(use-package which-key

Add hook to LSP mode:

(use-package lsp-mode
  :hook ((c-mode-common . lsp-deferred)
	 (lsp-mode . lsp-enable-which-key-integration))
;;; Rest of the configuration...

A screenshot of an Emacs minibuffer showing possible key combinations after a prefix has been entered.

Unclutter modeline

After installing all of these packages, your modeline might be full of junk. You can install the diminish package to hide some of the modes that aren’t useful to have there.

Firstly install diminish:

(use-package diminish)

Add the :diminish keyword to any modes that you want to hide, e.g. the use-package which-key call now becomes:

(use-package which-key

Compiling and Running Tests

For now I open an eshell buffer that I enter commands into like ninja, ./my_program, and ctest manually. If you know of any way that I can run my tests from Emacs and get a report, please contact me so that I can add it to this post.

  1. https://www.gnu.org/software/emacs/manual/html_node/emacs/Tags-Tables.html↩︎

  2. https://backports.debian.org/Instructions↩︎