Good Style in modern Emacs Packages

Thursday, 9 April 2020

With the release of Emacs 24.1, as of writing almost 8 years ago (or just 8 years ago) a packaging system was introduced. I wasn’t using Emacs back then, so all I can use is old documentation, wiki pages, mailing lists and forum posts, but it all seems like it was more of a mess.

For example: A list of packages, found here has mostly broken links – which is not too surprising for a 20 year old page, especially if it’s just an index, linking to other web pages. But other concepts like the Emacs Wiki Elisp Area or el-get have also decreased in popularity.

In it’s place GNU’s ELPA and the community-maintained MELPA have established themselves as the way to install packages, and keep them up-to-date12. In fact this has also been pushing the way forward for Emacs to shed-off code and programs that have been included into the Emacs core over the years.

With the development I have summarised above, the concept of a package has crystallised. Since I have started contributing packages and contributing to packages, I have recognised good and subpar patterns I would like to comment on here, and suggest a few rules of thumb(s?) on what makes a good Emacs packages.

Fall into expected categories

While there are certainly legitimate exceptions, I think that most cases an Emacs package should be one of the following:

  1. A function or set of functions

    Just provide a function. These can be anything from interactive commands meant for the user, or functions that should be added to hooks. These shouldn’t be bound automatically, but instead the user gets to decide where and when the function is called.

  2. A minor mode

    Strictly speaking a minor mode is also just a function, but because it has a state (enabled or disabled). It should be preferred for a minor mode to be local, then for it to have a global variant and unless neither makes sense, then just the global variant.

    A minor mode modifies the behaviour of a buffer. It changes variables, adds new keybindings. But activating it shouldn’t immediately have visible changes, just the behaviour. And if possible, these changes should be generic: Within the bounds of reason and good style one shouldn’t have to depend on major modes.

    And of course, unless there’s a good reason to do otherwise, use the define-minor-mode macro.

  3. A major mode

    Opposed to minor modes, a major mode is a state. It describes what kind of a buffer one is working on, and how. Anyone who has used Emacs for an extended period knows that this means it has a significant (or major) influence on the user-interface.

    As such, a major mode should not be surprising, accumulated experience should be re-usable. One good way to do this is to know subsystems and have the major mode integrate into these: Use font-lock, use imenu, use xref, use completion-at-point, define what a symbol, word, defun is, etc.

    Another way to annoyingly subvert expectations is with unusual keybindings. Consider AucTeX, Org and markdown-mode. All three have keybindings to mark text as bold, italics, etc. but all three disagree how (respectively C-c C-f C-b, C-c C-x C-f * and C-c C-s b for bold). How avoidable this was in the past is debatable, but for the future a developer should consider what is already customary and build on that.

    And just like with minor modes, use define-derived-mode and at the very least derive text-mode or prog-mode.

  4. A special major mode (ie. deriving special-mode)

    For most of (at least GNU) Emacs history, major modes that upgrade the programs behaviour from “regular text editing” to “whatever one wants” have existed (Rmail and Gnus were first written in the 1980s, Dired already existed before GNU Emacs, and help-mode is critical infrastructure).

    Yet because the developer has the most flexibility, designing based on expectation is even more important (as an anti-example, try M-x mpc — just to prove that not all core-emacs code is good).

    Furthermore, because “special major mode” programmes often use more than one buffer, and especially more than one window, a designer should put thought into how and when new windows are created or reused, and what window is focuses3. Also: don’t mess up window configurations.

The order of categories is intentional: A package providing a function should be preferred over a package providing a minor mode, etc. This doesn’t mean not developing special modes – just that one should consider if it’s worth re-inventing the wheel4.

Minimise the User-Interface

A package with hundreds of entry points is confusing and harder to integrate into a workflow. To minimise the user-interface means thinking how to reduce complexity without making singular commands to complex. A solution to these kinds of problems have traditionally been DWIM (“do what I mean”) commands.

A notable exception would be something like Avy that has a very regular interface (avy-goto-word, avy-goto-char, avy-goto-line, …).

Another aspect of user-interfaces, is naturally what one can see. I would want to think that Emacs developers know to avoid visual clutter, but in recent years it seems there has been a push to lure people from “more modern interfaces” using visual approximations of pop-up dialogues and related concepts5. I have little more to say than “Don’t”. It’s a mistake because it’s very hard to get right (eg. when a line has slightly larger text than the rest of the text, or the text in the dialog has non-mono spaced text) and usually a unnecessary burden on the garbage collector. As a package developer don’t force or assume that users use these packages.

Related to this, I’d also suggest against polluting the mode-line. There usually isn’t enough space for a lot of meaningful information, and that means space shouldn’t be wasted on meaningless information. If unsure, make it an option and consider the suggestions below.

What I would rather want to focus on is the mini-buffer. It’s a peculiarly scarce resource, because it usually just displays one message at a time. Avoid spamming messages when it might not be necessary, especially if the process that generates them is asynchronous or member of a hook.

I’d say there are two cases where messages should be generated:

  1. As a result of a specific command, that’s stated intention is to generate a message.
  2. By a package that’s point is to generate messages on specific events (such as Eldon).

Minimise Dependencies or don’t extend extensions

One of the strengths of package.el is that dependencies can be automatically managed. Installing necessary and removing obsolete code is just one command away. Nevertheless, dependencies is something a Emacs programmer should be cautious about.

The reason isn’t technical. I don’t plan to argue that one should write everything one needs by oneself, again and again. On the contrary, most of what most packages need is already incorporated into Emacs6.

A few examples for unnecessary dependencies are f, s and dash. For those not familiar, f is a library with auxiliary functions for working with files and file paths, s is the same for strings and dash the same for lists and other sequences. As alternatives I suggest: Using Emacs already existing file functions7, using buffers, and using seq or cl-lib.

The reasons I suggest this is that over the last Emacs versions Emacs has accumulated and improved many of it’s utility functions, that were missing in previous versions. Furthermore, using thinly veiled aliases (compare f-exists-p to file-exists-p – actually look at their definitions) is a unnecessary annoyance when reading code. The main reason is that it promotes a bad style.

One has to understand that Elisp is not functional. Many people fall for this, and the misconception is fuelled by the popular myth that Lisp is a functional language8. Take for example f-read-text, that can be compared to

(defun dont-use-this (path)
  (with-temp-buffer
    (insert-file-contents (expand-file-name path))
    (buffer-string)))

It returns the contents of a file as a string. If one now continues to go on and work with this string, splitting, joining, adding, filtering, and stops to consider that the above function created a temporary buffer and destroyed it, it might be worth reconsidering if this is the best strategy. Instead, one might use the text editing primitives that Elisp has built in9 to do all of the above, without constantly creating temporary buffers in the background, and needless string allocation.

A different and perhaps more striking example of avoiding dependencies is, as mentioned above, not assuming that others use the same packages as you do. For example, no package should have to depend on company-mode. (Even the company-mode developers recommend using the built-in completion-at-point, as compare to creating special company-mode bookends.). I recently tried to install Elpy, a Python development environment, and was extremely unpleasantly surprised when I found out it installed company and highlight-indentation – despite being not-at-all necessary.

Another example is packages providing an option for what completion system to use, instead of just using completing-read, et al., especially when there is little point in doing so besides dispersing an existing configuration and being an unpleasant Emacs citizen.

Disable-by-Default

This suggestion ties in with many of the previous points. Evaluating a buffer should just declare new functions and variables. If you ask me, it’s even debatable if a major mode should add itself to auto-mode-alist. In the same way, enabling a major mode should change fontification, and perhaps rewire subsystem integration (CAPF, IMENU, …). A minor mode should, by default be as quite as possible. Don’t surprise, act when asked.

It’s understandable that not everyone wants this, at least not all the time. There’s a reason you can add minor modes to hooks after all. The important thing is that the user has to explicitly wish for this.

In the same way, a feature – any feature – should not be turned on by default. This doesn’t mean that they should be hidden, documentation is encouraged to point out what is possible. This can be done by implementing optional features as minor modes, or having a user option that declares what features should be enabled (eg. using a set type). Either way, don’t decide for the user. Don’t be clever, do what I mean.

Write a useful commentary section

Each Emacs package should contain a “Commentary” section. This briefly describes what the package offers. Sadly a lot of people keep it too short, and instead use a README for general documentation. I think this is a mistake, as it harms Emacs’ self-documentation. Usually, if you want to learn about a package, you could use C-h P and a Help buffer would show everything you have to know. External documentation is just that little bit more annoying.

This goes in hand with a general misunderstanding of README files and their abuse as hompages for projects hosted on code forges such as GitHub. My take is that a README should be readble anywhere, rendered or plain text, and give the reader some pointers: How to install, where to contribute, etc.

Use the Customize-System

Despite being over 20 years old, the easy customization interface doesn’t get as much attention as it should. It’s ironies are that it’s user interface is too complicated for new users, but at the same time too boring for the so-called advanced users. It’s more complex features such custom initialisation, or variable getters/setters can’t be properly used, because too many people still prefer setq10 – and because it isn’t used, people don’t see the problem with setq11.

Thought should also be put into specifying customization types. This is usually better done, but I’m still disappointed when someone decides to say a type is just a sexp, when the value clearly has a structure. Knowing one’s simple types (string vs regex or file) and composite types (not confusing list and repeat) is extremely helpful. Designing your configuration interface should be given at least as much thought as designing the function interface to one’s package.

The Usual

Document your functions, use checkdoc, prefix packages, and read your documentation. You can’t have a good package without good code.


These were a few annoyances I have been seeing for a while now. I don’t intend to insult the works of other developers and designers, there are usually good explanations for such mistakes.

And I guess I have to add the obligatory disclaimer that everyone has right to do the exact opposite of what I have suggested – I just emphasise that in my opinion, these measures improve the user experience by building on expectation, are more future-proof and cooperate with the existing system, instead of fighting it.


  1. For now – the “eco-system” around Emacs has changed and reconstituted itself many times over. Who knows how the future will look like?↩︎

  2. (07Oct21) Since writing this article, another major archive has appeared: NonGNU ELPA.↩︎

  3. Yes, in 26.1 and beyond this can be modified by display-buffer-alist, but that doesn’t mean that designers shouldn’t have to think about it.↩︎

  4. For example: If one wants to display a list of entries, should one write everything from the ground up, or implemented the tabulated-list-mode? Or perhaps as a Gnus backend?

    That being said, it might not be possible to do so elegantly, and one should fall into the opposite trap of forcing the wrong abstractions.↩︎

  5. I’d spontaneously think of company-mode, popup, or lsp-mode/lsp-ui as prime examples.↩︎

  6. As an Emacs Koan goes:

    A novice of the temple once approached the Master Programmer with a question: “Master, does Emacs have the Buddha nature?” the novice asked.

    The Master Programmer had been in the temple for many years and could be relied upon to know these things. He thought for several minutes before replying: “I don’t see why not. It’s bloody well got everything else.”

    ↩︎
  7. Eg. you don’t need (f-join dir file), when you have (expand-file-name file dir)↩︎

  8. I try to explain this by distinguishing a functional style from a functional language. The former is characterised by an avoidance of global state, composition of smaller functions, etc. The latter is a language that enforces it. Elisp and Lisp in general supports a functional style – functions/procedures are first-class citizens. But because it isn’t enforced, the environment cannot optimise and work with programs under the same assumptions as one would with Haskell or ML. And when performance is relevant, as is more often than not with Elisp, one should, for example, have a feeling for when memory is being allocated and when it can be avoided.↩︎

  9. It is a Lisp Evaluator with a Text Editor as a side effect after all.↩︎

  10. If one insists on a setq-like function, one might consider using abo-abo’s csetq.↩︎

  11. The only cases in which setq-people do realise issues, is when a developer is brave enough to use some of the slightly more advanced features of customize, and something cryptically breaks.

    On this note, if you use use-package, don’t do :config (setq ...) but instead consider using :custom (...). It’s cleaner and will be more stable.↩︎