C++ Programming in Emacs
Making a lightweight and fast C++ IDE in Emacs
C++ is my primary programming language since – I can’t believe it myself – 1991 (yes, it was Turbo C++). As I’m going to do almost everything within Emacs 29.4 now – no doubt I want to have the C++ IDE in Emacs, in the same way I’m using Emacs as the Python IDE.
Thankfully, eglot, the Emacs client for the Language Server Protocol (LSP), is now – since version 29 – the part of Emacs. It remains to choose the appropriate LSP.
Choosing a Language Server
I’ve settled on clangd, a powerful LSP for C++ based on the Clang C++ compiler. It provides an impressive set of features including error checking, code completion, cross-references, navigation, and others.
clangd
is the part of the LLVM project and it can be
downloaded from LLVM Download Page.
Which version to download?
It depends on OS/version you are using. I’m using
Pop!_OS 22.04 (Ubuntu 22.04 fork); after researching, I came upon
clang+llvm-17.0.6-x86_64-linux-gnu-ubuntu-22.04.tar.xz
from this repository
that contains pre-build binaries for my OS. It supports C++17 standard
– that’s good enough for me.
I’ve unpacked the archive to ~/bin/LLVM-17.0.6/
then added
$HOME/bin/LLVM-17.0.6/bin
to the $PATH
envvar.
$ clangd --version
clangd version 17.0.6 (https://github.com/llvm/llvm-project 6009708b4367171ccdbf4b5905cb6a803753fe18)
Features: linux
Platform: x86_64-unknown-linux-gnu
Configuring clangd
Clangd stores its configuration in YAML files. I’m using per-project
configuration file .clangd
– clangd searches for it in all parent
directories of the active file.
Below is my simple and straightforward .clangd
for some of my projects.
# Enable all warnings, use C++17 standard, specify include directories.
CompileFlags:
Add: [-xc++, -Wall, -std=c++17, -I/home/magnolia/workspace/devel, -I/home/magnolia/lib/grpc/include, -I/home/magnolia/lib/spdlog/include]
Remove: [-W*] # Strip all other warning-related flags
Compiler: clang++ # Use `clang++` explicitly
It is placed in the top level directory of my project.
You can find more about clangd
configuration here.
Now we can return back to the Emacs configuration.
Emacs Configuration for C++
Using Eglot
(require 'eglot)
(add-hook 'c-mode-hook 'eglot-ensure)
(add-hook 'c++-mode-hook 'eglot-ensure)
(add-hook 'c-or-c++-mode-hook 'eglot-ensure)
The Eglot session will start automatically every time you open the C/C++ file in a buffer. If the Eglot session for the current C/C++ buffer has started successfully it displays the following message in the Emacs minibuffer:
[eglot] Connected! Server `clangd' now managing `(c++-mode c-mode)' buffers in project ...
Visual Improvements
Highlighting symbols and numbers in the source code.
;; Highlights the word/symbol at point and any other occurrences in
;; view. Also allows to jump to the next or previous occurrence.
;; https://github.com/nschum/highlight-symbol.el
(use-package highlight-symbol
:ensure t
:config
(setq highlight-symbol-on-navigation-p t)
(add-hook 'prog-mode-hook 'highlight-symbol-mode))
;; Emacs minor mode that highlights numeric literals in source code.
;; https://github.com/Fanael/highlight-numbers
(use-package highlight-numbers
:ensure t
:config
(add-hook 'prog-mode-hook 'highlight-numbers-mode))
C++ Headers
Please, treat C headers as C++ ones.
;; .h files to open in c++-mode rather than c-mode.
(add-to-list 'auto-mode-alist '("\\.h$" . c++-mode))
Code Styles
(setq c-default-style "stroustrup")
(setq c-basic-indent 4)
(setq c-basic-offset 4)
I’m using the Stroustrup C++ code style that is very similar to the K&R (Kernighan & Ritchie) code style.
The c-style-alist
Elisp variable contains a list of all predefined code
styles. Pick a style that suits you.
Indentation Inside of Namespaces
;; emacs-fu: don’t indent inside of C++ namespaces
;; http://brrian.tumblr.com/post/9018043954/emacs-fu-dont-indent-inside-of-c-namespaces
(c-set-offset 'innamespace 0)
I prefer to have the code unindented inside of a namespace as shown below,
namespace tec {
class Client {
public:
Client() {
// ...
}
int get_id() {
// ...
}
}; // ::Client
} // ::tec
rather than
namespace tec {
class Client {
public:
Client() {
// ...
}
int get_id() {
// ...
}
}; // ::Client
} // ::tec
Spaces vs Tabs
Yes, I’m using spaces instead of tabs for indentation.
;; Spaces instead of tabs when indenting.
(setq-default indent-tabs-mode nil)
Trailing Tabs and Spaces
A builtin package
that shows/deletes useless trailing tabs and spaces.
You can also use M-x delete-trailing-whitespace
to remove all
trailing white-spaces in a buffer interactively.
;; http://www.reddit.com/r/emacs/comments/2keh6u/show_tabs_and_trailing_whitespaces_only/
(use-package whitespace
:config
;; Commented since there are too many 'valid' whitespaces in some modes.
;; (setq-default show-trailing-whitespace t)
(setq whitespace-style '(face tabs trailing))
(set-face-attribute 'whitespace-tab nil
:background "red"
:foreground "yellow"
:weight 'bold)
(add-hook 'prog-mode-hook 'whitespace-mode)
;; Delete trailing tabs and spaces on save of a file.
(add-hook 'before-save-hook 'whitespace-cleanup)
)
Clangd with Flymake
This chapter is identical to the corresponding chapter in my previous post Python Programming in Emacs. I put it here for clarity.
Flymake is a minor Emacs mode performing on-the-fly syntax checks. I’m
using the flymake
built-in Emacs package as an Eglot front-end for
syntax checks.
(require 'flymake)
My custom my/flymake-toggle-diagnostics-buffer
function toggles the
Flymake diagnostics buffer window.
If there is the Flymake diagnostics buffer associated with a file in the current buffer, it shows the diagnostics buffer in the new window and switches to it. If the current buffer is the Flymake diagnostics buffer, it closes it. Otherwise it just shows the error message.
(defun my/flymake-toggle-diagnostics-buffer ()
(interactive)
;; Check if we are in the diagnostics buffer.
(if (string-search "*Flymake diagnostics" (buffer-name))
(delete-window)
(progn
;; Activate the Flymake diagnostics buffer.
;; and switch to it
(flymake-show-buffer-diagnostics)
(let ((name (flymake--diagnostics-buffer-name)))
(if (get-buffer name)
(switch-to-buffer-other-window name)
(error "No Flymake diagnostics buffer found")
)))))
I’ve bound this function to the F7
key globally since I’m using F7
in other programming modes as well.
(global-set-key [(f7)] #'my/flymake-toggle-diagnostics-buffer)
;; Additional bindings.
(global-set-key (kbd "C-c f b") #'flymake-show-buffer-diagnostics)
(global-set-key (kbd "C-c f p") #'flymake-show-project-diagnostics)
The screenshot below shows what happens if you press F7
while
editing a C++ file managed by Eglot + Flymake.

Click or tap to view the full-size picture.
Treemacs
From looking at the screenshot above, you can see using of Treemacs, a tree layout file explorer for Emacs.
I can highly recommend to use it for any project you’re working on in conjunction with the excellent Projectile and Ivy packages.
(use-package ivy
:config
(ivy-mode)
(setq ivy-use-virtual-buffers t)
(add-hook 'after-init-hook (lambda () (setq ivy-height (/ (window-height) 2))))
)
(use-package projectile
:ensure t
:config
(projectile-global-mode)
(setq projectile-enable-caching t)
(setq projectile-completion-system 'ivy)
)
(use-package treemacs :ensure t)
(use-package treemacs-projectile :ensure t)
I’ve bound Treemacs to F12
globally.
(global-set-key [(f12)] #'treemacs-select-window)
Press ?
when in the Treemacs window to see all commands available.
Press q
to close the Treemacs window.
Code Completion
This chapter is identical to the corresponding chapter in my previous post Python Programming in Emacs. I put it here for clarity.
I’m using the company
package, a
modular in-buffer completion framework for Emacs, as an Eglot
front-end for code completion; it integrates with Eglot seamlessly.
;;;; `COMPANY'
(use-package company
:ensure t
:config
(setq company-idle-delay 0)
(setq company-minimum-prefix-length 2)
(setq company-show-numbers t)
;; To prevent default down-casing.
;; https://emacs.stackexchange.com/questions/10837/how-to-make-company-mode-be-case-sensitive-on-plain-text
(setq company-dabbrev-downcase nil)
;; 2023-01-13 From a Reddit post on mixed case issue.
(setq company-dabbrev-ignore-case nil)
(setq company-dabbrev-code-ignore-case nil))
;; Use `company' everywhere.
(add-hook 'after-init-hook 'global-company-mode)

Click or tap to view the full-size picture.
Code Navigation
My favorite tool for code navigation in the current buffer is the
excellent imenu-list
package.
Powered by Eglot, it shows almost everything you want to see.

Click or tap to view the full-size picture.
Now your Emacs looks like a full-featured C++ IDE – and it is, actually.
;; Show the current buffer's imenu entries in a separate buffer
(use-package imenu-list
:ensure t
:config
(setq imenu-list-focus-after-activation t)
(global-set-key (kbd "C-.") #'imenu-list-minor-mode)
)
I’ve bound imenu-list-minor-mode
to C-.
; this key chord will
toggle the Imenu buffer on/off.
Alternatively, you can use Treemacs to navigate through the code and files on a per-project basis.

Click or tap to view the full-size picture.
“Classic” default navigation keys will also work with all files in your project.
xref-find-definitions
M-.
to find the definition of the identifier at point.xref-go-back
M-,
to return back to where you invoked thexref-find-definitions
command.xref-find-references
M-?
to find references to the identifier at point.
These functions are now powered by Eglot.
Appendix A
All the Icons
As you can see from the screenshots above, I like to see icons
wherever possible – in treemacs
, dired
, ibuffer
, company
and
other modes. To achieve that, all you need is to install the
corresponding packages.
;; https://github.com/domtronn/all-the-icons.el
(use-package all-the-icons :ensure t)
(use-package all-the-icons-dired :ensure t)
(add-hook 'dired-mode-hook 'all-the-icons-dired-mode)
(use-package all-the-icons-ibuffer :ensure t
:init (all-the-icons-ibuffer-mode 1)
:hook (ibuffer-mode . all-the-icons-ibuffer-mode))
(use-package treemacs-all-the-icons :ensure t)
(use-package all-the-icons-ivy :ensure t
:after all-the-icons
:config (all-the-icons-ivy-setup))
To install icons font, please follow these instructions.
Appendix B
A Note for Tree-Sitter Users
I do not use Tree-Sitter for coding in C++. It seems it has its own rules for indentation that break down the code style I prefer.
Happy emacsing!
– The Emacs Cat.
P.S. Emacs 30.1 has been released on February 23. Thanks for everyone involved with this great project!
P.P.S. Mickey Petersen has published his excellent review on what’s new in this release.