C++ Programming in Emacs

Making a lightweight and fast C++ IDE in Emacs

Page content

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 the xref-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.