Smarter clang-format In Emacs

I’m a big believer in using clang-format to automatically format C and C++ code. Typically this is done by adding a file called .clang-format to the root directory of a project. This file tells clang-format about how code in the project should be formatted. When clang-format is run, it will reformat source code according to the style you’ve specified. This is conceptually similar to gofmt in Go, or yapf in Python. I include a .clang-format file in all of my C/C++ projects.

I edit code using GNU Emacs, via Spacemacs. The C/C++ layer for Spacemacs has an option to automatically run clang-format on buffers when saving them. If you enable clang-format-on-save in the c-c++ layer, then whenever you save a C or C++ file Emacs will reformat the buffer using clang-format before actually persisting the buffer to disk.

I absolutely want this behavior for all of my personal projects, since it ensures that I don’t check in improperly formatted code. But I typically don’t want this behavior when hacking on third party C or C++ libraries. There are a lot of different styles for C/C++ coding, and most projects don’t include .clang-format files. If you try to use clang-format on a file from one of these projects, you’ll end up reformatting the entire file you’re editing. Thus I find the default behavior in Spacemacs to be kind of braindead, since it’s all or nothing. I suppose it would be OK if you were only hacking on personal (or work) projects, and never had to touch open source code. But what’s the fun in that?

I finally sat down this weekend and fixed this. The idea is to enable clang-format on buffers only when the project has a top level .clang-format file. That way my personal projects will all get formatted nicely, but I can also edit third party open source projects and not reformat the entire file when I’m making a small change.

This works using a function that reformats the current buffer, but only if a .clang-format file exists in the current Projectile project root:

(defun clang-format-buffer-smart ()
  "Reformat buffer if .clang-format exists in the projectile root."
  (when (f-exists? (expand-file-name ".clang-format" (projectile-project-root)))

I add this as a before-save-hook for C and C++ buffers:

(defun clang-format-buffer-smart-on-save ()
  "Add auto-save hook for clang-format-buffer-smart."
  (add-hook 'before-save-hook 'clang-format-buffer-smart nil t))

(spacemacs/add-to-hooks 'clang-format-buffer-smart-on-save
                        '(c-mode-hook c++-mode-hook))

In my actual Emacs configuration, this is implemented as a custom Spacemacs layer that extends the existing c-c++ layer. I’d like to contribute this code upstream, but there’s a valid use case for the existing behavior, and the existing layer already has two different configuration options related to clang-format, so adding a third doesn’t seem productive. Let me know if you find this useful.