Comfortably Edit C with Emacs

Thursday, 13 August 2020

I recently got into Go (the game, not the language), and due to the circumstances as of writing, I mostly get to play by my own vs a Bot.

The weird thing about most Go-related software is that it’s either dead, barely or even non-functional or aggressively skeuomorphic. Because I had had some extra time on my hands, I decided to write my own client, using C and XCB1, that was about as complicated as it has to be, for a non-professional player like me2.

In this text, I’d like to write about the tooling I used, particularly revolving around GNU Emacs. The reason for this is that it’s somewhat hard to come by good, well-informed tips that still work. I hope to explain the technical background, my choices and possible alternatives one might consider.

Project Structure

It will be necessary to start by pointing out that I started my project from scratch, so I got to choose everything – build system, file layout, libraries, etc.

In my case, I ended up using a single portable Makefile, a flat file hierarchy and the XCB library. Locating the library and it’s header files was done using pkg-config.

All in all, it’s nothing spectacular. It provides a stable foundation for writing a clean program.

Emacs Subsystems

By default, Emacs comes with a major mode for C. It does syntax highlighting, indentation and a few basic operations on semantic units. But what I don’t want a basic environment, I want a comfortable environment.

If you ask most people online what they would recommend, you’d hear a lot of people say “lsp-mode”. Now I happen to think that lsp-mode is a bad emacs citizen, and would rather avoid it. Eglot is somewhat better (I’d use it if I had to), but in all in, I’d rather avoid LSP3.

So what am I looking for? My modest list consists of:

  1. Interactive syntax/error-checking: By having the editor alter me inline, the write-compile-test cycle is shorted, accelerating code development.
  2. Locating Definitions: Perhaps not that critical if you can remember where all the relevant code is, but if you come back to the project after not working with it for a while, or just browse a foreign code-base, having a simple way to find where something is defined is very welcome.
  3. Auto Completion: In the simplest case, it saves time because you have to type less, but in other situations smart auto completion prevents you from mixing up symbols and subtly reminds you of the structure of your code base.
  4. Project-wide operations

It turns out that Emacs provides all of these things, without having to install anything! Sadly it’s not always the best you could get, so I’ll also mention what alternatives you could consider.

Interactive Syntax Checking

This is probably the easiest to configure, but most people don’t know how. Usually people jump to Flycheck, but that requires additional configuration and is an external package anyway.

Instead, I suggest considering Flymake, the built in syntax checker. I suspect one of the reasons it’s mentioned less often, is that it’s not quite clear how the minor mode should be used: Just enabling it, provides no feedback, and if did, it’s not clear how to find out what’s wrong.

There are two steps to configuring Flymake:

  1. Setting up Flymake
  2. Setting up the “backend”

For the first part, you just have to add flymake-mode to whatever major-mode hook you’re using, in our case c-mode-hook, and when that’s done, optionally bind flymake-goto-next-error and flymake-goto-prev-error. I recommend user-keys M-n and M-p, if you’re not using them for anything else.

The backend is a bit trickier, or at least less intuitive. But all we have to do, is all a rule to our Makefile: check-syntax. Here’s what I used:

check-syntax:           # flymake support
    $(CC) -fsyntax-only $(CFLAGS) $(CHK_SOURCES)

.PHONY: check-syntax

How this works: By default, a backend tries to invoke make with the check-syntax target, and it sets the CHK_SOURCES macro to whatever file is being inspected. By adding this rule, Flymake will parse the diagnostics (note that nothing is compiled because of the -fsyntax-only), and display them in the current buffer. The CFLAGS are necessary, so that Flymake warns you of the same issue while writing as while compiling.

The last question is how to find out what the issue is. A squiggly it’s always enough to help. And placing the point on the highlighted area doesn’t do much either… You might have noticed that flymake-goto-{next,prev}-error displays the message in the mini buffer, but there’s no command in the Flymake package the just display the issue you’re currently looking at without having to do an unnecessary M-p M-n… The solution: display-local-help, bound to C-h . by default. Flymake integrates into the existing help system, and only displays it’s information when queried – you don’t want to spam the echo area after all.

That’s that. If you add another dependency, and update your Makefile, Flymake will immediately make use of it. In that case,

Locating Definitions

The classical method of finding where a symbol is defined, is by using tags files. Various programs can generate the TAGS file: By default, etags should come with every GNU Emacs installation, but Exubrant Ctags or Universal Ctags might work better.

Xref, the cross-referencing subsystem (hence the name), has a TAGS backend built in by default. This supports querying the definition at point (M-.), finding miscellaneous symbols (C-u M-.), listing all symbols that match a pattern (C-M-.) and even finding where a symbols is used (M-?).

Some people say it’s inconvenient to maintain a TAGS file, which I understand. What helped me was adding a TAGS rule to the Makefile:

TAGS: $(SRC)
      ctags -Re $(SRC)

that I invoke every time I re-compile the program (more on that later). In this example, the macro SRC would list all source files of the project. A non-portable GNU makefile, might generate this using

SRC != find . -name '*.c' -or -name '*.h'

Another idea would be to add a function to after-save-hook that updates the TAGS file for a single file.

But TAGS have their limitations, for example when projects are too large and loading the TAGS file becomes too inconvenient. In that case, some people like to use Dumb Jump (MELPA). It’s idea is that it uses grep-like tools (git-grep, the silver searcher, ripgrep, …) to find patterns that might be the definitions of whatever symbol one is looking for. That way, you get to trade in maintaining a TAGS file, for searching the code-base every time you need a definition.

Dumb Jump has also recently been extended, by yours truly, to implement part of the Xref interface, that improves the UI and makes it easier to switch from one backend to another.

Auto Completion

Some might be surprised to find out, that after setting up a TAGS file, Emacs can automatically use that file for auto completion. Sadly, it’s not too clever, and just suggests every tags it knows of, every time. Depending on your standards, this might not be enough.

For that reason, I wrote clang-capf, that implements the bridge between Clang‘s completion mechanism, and Emacs’ completion-at-point subsystem. After it has been set-up, M-<Tab> provides a list of context-sensitive completions4.

Sadly, the results could be even more context sensitive, but clang doesn’t seem to support this by default. For example, if a function requires a FILE pointer as an argument, clang won’t just return visible file pointers as suggestions, but usually everything it knows – which is usually more than a TAGS file knows.

Project Wide Operations

When a project has more than one file, it would be nice to easily move between them. Depending on your Emacs usage, a simple C-x C-f and C-x b might suffice, but for a while now, Emacs also bundles “project.el”, a framework to operate on the current project. It’s commands will be bound under the C-x p prefix by default, but you can already use it via ELPA if you’re using Emacs 26.3 or newer.

I use project-find-file indirectly via a little helper function bound to C-x C-r5:

(defun find-file-recursive ()
  "Open a file in current subtree."
  (interactive "P")
  (if (project-current)
      (project-find-file)
    (let ((files (process-lines "find" "."))
       (find-file (completing-read "Find file: " files nil t))))))

Again, using Ivy, I get a list of files in the project, even sub-directories, and can easily switch between them.

Another useful command, is project-compile that start compile in the project’s root directory. I invoke this once, give it the command

make all TAGS

and then re-run the command later on using recompile, bound to a key such as F2. Warnings and erros can be navigated using next-error (M-g M-n) and previous-error (M-g M-p).

And when done, project-kill-buffers keeps the buffer list clean.


That’s it. A comfortable development environment, at least if you ask me. A lot of these things are built in, and feel native in Emacs. This feel ultimately beats anything that wants to smuggle in the Workflow of some other modern editors such as IntelliJ, Atom or VSCode. I’m often sad to see how many people train Emacs into behave like these, when you arguable should not want this – for your own and your editors sake.

One thing I didn’t go into was debugging, but if you know GDB, running M-x gdb should give you everything you want. Another thing thing I’m still looking out for is a good way to use eldoc, to get the signatures of functions and types of variables. It might be worth looking into clang for that.


  1. Before anyone cries out that C is dangerous and must not be used, I would like to defend myself, by pointing out, that:

    1. I like to write C – for it is fun
    2. This is an educational “experiment”, using low-level X API, and getting familiar with the primitive vocabulary of the system.
    3. I teach C at university, so I have to stay in form.
    ↩︎
  2. I’m familiar with sites like online-go.com, but I wanted an offline tool.

    In case anyone is interested, my preliminary status can be found here, but that’s not what this text is about.↩︎

  3. It might get better, but currently LSP is dragging a lot of tools down. I’ve seen Go and Haskell (GHC) abandon their existing systems for auto-completion, for self-declared “alpha” software, that has mainly been tested against VSCode. In it’s implementation, it should also be clear that LSP is biased towards web-based editors, such as (unsurprisingly) VSCode, as it’s based on a HTTP-like protocol, with no ability to negotiate the form the data is transmitted with, besides JSON.↩︎

  4. By default, the results are diplayed in their own buffer, which is somewhat visually unpleasing. I personally use Ivy to get a prettier list.↩︎

  5. By default, find-file-read-only is bound to this key, but I have never used it.↩︎