Webpresence of Philip Kaludercic

Structuring an Emacs configuration

17 March, 2021

Summary: I have recently rethought the way I configure Emacs. In this text I explain why I have replaced use-package with my own configuration macro and went back from using a literate Org mode file to a regular init.el.

Playing with Emacs is certainly an unusualy hobby, but I like it. From time to time, that is. Just like with any hobby, it’s not fun if you have to do it. So one important aspect is always having a stable configuration I can play with, but don’t have to worry about.

This wasn’t always given. I recently found a “old” configuration of mine from around July of 2015, which was to my knowledge a few months after I started using Emacs. It is easy to recognzie that I had no idea what I was doing. Just consider indicate-empty-lines: I first disable indicate-empty-lines via customize, toggle it, if it was previously disabled (in effect turning it on), and then activate it again using setq-default.

In my case, most of it was copied from random blogs, fora or wikis. My guess is that this is the most common kind of configuration there is. You start with a few lines a friend or colleges might have recommend, and add whatever is necessary with some vague conception of causality between the code and what Emacs actually does.

On the spectrum between a ball of mud and a diamond, this approach probably resembles a stack of paper: It does the job, won’t break if you don’t touch it but a breeze might make if fall over, and it isn’t easy to fix if afterwards.

Some time later I decided to clean things up, as I had heard a lot of people liked John Wiegley’s use-package. In case you are unfamiliar with the package, it basically provides a macro that simplifies common tasks: Installing packages, configuring variables, binding commands, managing hooks, etc.

Around Feburary of 2018, I made a second step: Using org-mode to write a literate configuration. The idea is to have a configuration that is text-first, code-second. And instead of your regular init.el, you’d just “tangel” (extract) out the code and evaluate it.

Eventually I started using Git, to keep my configuration under version control. One advantage is that I can measure how my conf.org grew over time:

Lines of code of my literate configuration

I regularly exported my configuration, the last export from April 2020 can be found here.

About a year ago I got fed up with all of this. It might have been because I had some spare time on my hands, but I decided to restructure everything again, now with more Elisp experience than I had before.

Dropping use-package

The first change was dropping use-package. This was mainly motivated by it’s internal complexity, partially by it’s restrictiveness. The package has changed a lot, and requires a lot of code to maintain backwards compatibility and syntactic flexibility. A priority I don’t care about it speed, as long as nothing is slowed down. Another annoyance is the difficulty adding new keywords. But it is still a great idea for structuring a configuration and making it easier to automatically install everything you need.

After a few experiments I managed to create my own alternative, simply called setup. Instead of the keyword approach that requires normalization, it just defines a set of local macros that can be used in a setup form. This drastically simplifies the implementation, as most of the functionality is reduced to macro expansion.

(I apologize for the advertising) Here are a few examples from my current configuration:

(setup shell
  (:option (append display-buffer-alist)
  (let ((shell-key "C-c s"))
    (:global shell-key shell)
    (:bind shell-key bury-buffer)))

This appends a rule to display-buffer-alist. Then I binds a global key and a mode-local (the mode is automatically guessed from the feature name) to some commands. Note that let is just a regular Elisp let, and shell-key a regular lexical variable.

(setup (:package modus-themes)
  (:only-if (display-graphic-p))
  (:option modus-themes-fringes 'subtle
           modus-themes-mode-line '3d
           modus-themes-variable-pitch-ui t
           modus-themes-region 'bg-only)
  (load-theme 'modus-operandi t)
  (:global "C-c t" modus-themes-toggle))

This installs Modus Themes, but only if this is being evaluated by a graphical Emacs session. It then configures a few user options using customize-set-variable. The theme is loaded and finally a global keybinding is installed.

(setup (:package go-mode)
  (:needs "go")
  (:option gofmt-command "goimports")
  (:local-hook before-save-hook gofmt-before-save)
  (:local-set compile-command "go build")
  (:hook subword-mode))

This last one configures go-mode, a major mode for the Go programming language. A single option is set, a hook and a variable are configured to be modified whenever go-mode is loaded, and finally a function is added to go-mode-hook.

Of course this is not a 1:1 replacement for use-package, but it has managed to provide a very satisfactory replacement for my needs.

For those interested in trying the package out for yourself, version 0.1 can be found on GNU ELPA. I have to mention Stefan Monnier’s name at this point, to thank him for his invaluable input and suggestions on how to improve both the user-interface and the implementation.

Dropping org-mode

The idea of using org-mode is to explain what you are configuring, as is done with literate programming. And by using Org, you also get simple folding, easy exporting and the ability to link sections for free.

But after using it for over two years, I had come to the conclusion that it is usually more of a gimmick than a feature. I say “usually”, because there are exceptions. The most prominent example would probably be Protesilaos Stavrou’s configuration. He goes into great detail to explain his options and decisions.

Most literate configurations I see don’t even try. All you see is header, code block, header, code block, …. While I tried better, it didn’t make a lot of sense to just copy the manual in describing what a hook or user option does. The higher complexity, the overhead and the loss being able to jump to definitions doesn’t seem to be worth it.

It turned out that in my case, a simple ~/.emacs.d/init.el was totally sufficient. I didn’t need the force myself into explaining what I am doing. outline-minor-mode has replaced Erg’s structuring folding functionality.

Personally I don’t think I have lost any functionality or advantage that org-mode provided.

There were a few other changes I have made (switching to default completion, trying to prefer ELPA over MELPA, making more use of customize, auto-configuring local packages) that are not that interesting here. The main intention here was to explain what I think I did wrong, and how I managed to “improve” it.

I’ll be uploading my configuration from time to time here.