My Custom Emacs Setup
It seems to be widely accepted that creating a powerful, useful Emacs setup "by hand" is just too much trouble, and you should choose a "distro" like Doom Emacs. But is it really all so bad? If you go the route of "hand-made", will you suffer through endless nights of fixing your setup? The answer is: probably not, but read on for more details! 1
→ Why Custom?
I've got a lot of respect for the various Emacs "distros" out there, but my inclination for DIY as well as for understanding my tools keeps me from going that route. I'd rather spend some time reading and researching than running a prefab setup that's effectively a mystery to me.
At the end of the day I do spend time on my configuration, but it's hardly the part-time job folks make it out to be. And throughout the years, I've learned a substantial amount about how Emacs actually works.
Prefab setups are usually nice until you hit some usability wall, or find a leak in the abstraction. Sometimes, you end up putting as much effort into learning your abstraction as you would have put into just learning the thing you tried to abstract. This is why I'm fine with getting my hands dirty with Emacs.
→ Desired Features
Before getting into the details of customizing Emacs, it's good to lay out some goals. This will help keep the project focused and prevent you from bloating your setup with things you don't really need. For me, the list looks something like this:
- Automated, idempotent setup that can be repeated easily
- Declaratively install packages
- LSP support for languages that have a supporting server, powerful autocompletion for those that don't
- Quality-of-life packages and modes
- A wealth of custom keybindings to make life better
I personally don't need a full-on project management system, or deep IDE-like integration, but indeed all of that is available if you'd want it. Anyways, with these goals in mind, let's begin to discuss implementation.
→ Automatic, Idempotent Setup
I want to be able to erase my entire setup, clone it down from a git repo, run Emacs, and be back to where I was before I nuked it all (assuming I've got no local, uncommitted changes). Additionally: I want to have a lockfile-like setup for freezing package versions.
The straight.el package manager is extremely powerful and allows for extreme reproducibility. It does so via:
- Installing packages via git clone versus a tarball or some other archive 2
- Easy integration of customized packages into your own setup, if desired 3
- A lockfile for controlling versions of each package 4
use-package(more on that later) 5
The straight.el bootstrap process allows for a totally self-installing setup. For example, with my configuration you just clone my repo to
~/.emacs.d and run Emacs; straight.el bootstraps itself and handles installing everything that I need.
→ Declarative Package Installation
As described above and in the project README, straight.el brings a nice element of reproducibility and control over packages and versions, but to take it even further I handle all package installations and their related config with use-package.
Integrating use-package with straight.el does require some configuration; again: see the straight.el documentation or my own init.el file for specifics. I strongly advise giving the project README a good look, it is high-quality documentation. 7
→ use-package: Installing
use-package is extremely powerful and also very simple to use. The straight.el integration is done well too; consider this example for setting up OCaml support:
(use-package tuareg :defer t :straight t)
The use-package syntax is simple and explicit, but what's happening here?
I'm installing the
- I'm telling use-package to not load this package until it's needed
- I'm telling straight.el to handle the installation
- By default, straight.el will try to find the package on Github
Of course that's a very simple example; the use-package readme describes all the various features you can use to tune your setup.
→ use-package: Configuring
use-package allows configuring packages before and after they are loaded via the
:config keyword arguments, respectively.
(use-package flycheck-status-emoji :straight t :config (flycheck-status-emoji-mode))
(use-package marginalia :straight t :init (marginalia-mode))
→ use-package: Key Binds
use-package also allows for configuring key binds on package load:
(use-package windmove :straight t :bind ("M-e" . windmove-left) ("M-u" . windmove-right) ("M-k" . windmove-up) ("M-j" . windmove-down))
All keybinds then live in your configuration, nicely inside the package declarations themselves.
→ use-package: Hooks
The last feature I'm going to discuss is use-package's syntax for hooks:
(use-package html-mode :no-require t :hook (web-mode . skewer-html-mode))
This should be starting to feel familiar, and again it's nice to have hooks defined with the related packages in an idiomatic way.
→ Language Server
But enough about the awesomeness of straight.el and use-package! One of the main things I wanted when I moved to Emacs was a powerful system for auto-completing code. Emacs has several ways to achieve this, but what I've come to use for almost every mode is lsp-mode for integrating language server capabilities into Emacs.
I'm using lsp-mode with almost all of the languages I work with regularly, except Lua. At least for now and for me, the current LSP experience with Lua wasn't as good as using company-mode. I'll definitely revisit that in the future though.
There's isnt much else to say that isn't better said on the project documentation: but you can also refer to my configuration for usage examples if desired.
→ Quality Of Life
Aside from that, there are a number of other things I've tuned to make my Emacs experience more to my liking:
- A dark theme for the editing area 9
- A dark theme for the mode line 10
- A dashboard or "startup page" 11
- Emoji icons for Flycheck status 12
- A handful of helper functions
- ws-butler, to clean up whitespace in edited files 13
- "TODO" keyword highlighting 14
- Rainbow parenthesis 15
- Yasnippet for inserting various snippets 16
- And many many more
Software that's as endlessly customizeable as Emacs allows for all kinds of personal tweaks and touches. Over the year's I've built up my own collection of these and it continues to grow as I learn new things about Emacs.
→ Custom Keybindings
Last but not least: most folks will end up wanting to change a key binding at some point. Some go as far as to change the entire paradigm of the keybinds to do something like offer Vim bindings.
I keep most of the default Emacs bindings but have changed more than a handful to suit my own needs. Some of these are defined with their related packages, others are just general additions or changes to some default Emacs binding. 17
If you've never done so, I highly encourage taking a look at
M-x describe-bindings RET. It's a high-level view of all bindings in your setup.
→ No Abstractions?
Someone on reddit has pointed out that straight.el and use-package are in fact both abstractions. That's a fair point, but in this case I am talking about tools versus kitchen sinks. The abstractions I mentioned in this entry's intro are the kitchen sink variety, whereas straight.el and use-package are more like the tools you would use to build the kitchen sink. 18
I don't intend to make the argument "abstractions are bad"; they are not in and of themselves a bad thing, of course. Opinions may vary on when one crosses that line, though.
This was definitely not written for the unitiated, but I hope it was somewhat useful as a reference to "modding" Emacs.
In particular, I'd like to highlight the power and usefulness of managing the editor configuration with Lisp code rather than clicking endlessly through some user interface (the general experience of most other editors).
Having Lisp specifically and having it so tightly integrated with Emacs is where the majority of this power comes from. Learning to use it and work with it has been and continues to be a really great experience for me.