Declarative configuration in Emacs Link to heading
In Emacs, it is often easiest to install a package in one’s
initialising configuration file; also known as init.el
, usually
located at ~/config/emacs/init.el
, but also historically at
~/.emacs.d/init.el
as under a different name .emacs
.
This file is responsible for reading your persistent configuration and is often written to by your resident Emacs binary, on first launch and after you’ve made some customisations. It is configured in Lisp, which is both a data representation format (Symbolic expressions), and a programming language.
Some of the customisations pertain to the Emacs built-in
functionality, and when we come to eglot
we’ll see how that goes,
but some packages, specifically, lsp-mode
are not included in the
somewhat large binary that is Emacs these days, so they need to be
installed externally. That is basically what use-package
is doing.
It is not a package manager, but more of a method to keep your configuration file tidy. It allows you to declare configuration that is local to a specific package, so that you could, in theory at least, remove the entire form and have a clean and working configuration file.
use-package
for vim
users is like both lazy
and a weird
package-specific configuration template syntax. It is quite weird by
Emacs standards as well, and not universally
well-regarded.
My personal thoughts are that it is standard enough, that people
should default to it, and only under some circumstances opt for
something else. Unlike vim
’s audience which is split down the
middle on vimscript versus lua, and then fragmented further into
specific package managers, with lazy
being my personal favourite,
Emacs is remarkably centralised. This has benefits, you can usually
grab code directly from the README and it would work.
The main drawback is that you do need some baseline proficiency in Emacs. I will try to be as newcomer friendly as possible, targeting the level of knowledge that I developed when I was determined to use Emacs full time, but before obtaining any major skills.
Basic installation Link to heading
Suppose you found this neat little package X on the web. And it says
in installation that you can install it from GNU ELPA. Well, now you
have a classic case for using use-package
.
(use-package X
:ensure t)
is all you need. You can write this anywhere in Emacs, yes even in
your current Rust code project, and hit C-x C-e
when you’re at the
closing parenthesis (or C-M-x
if you’re inside the form. What will
happen, is that Emacs will show that it contacted some internet
resources, with the intention of downloading the package. At some
point it will have installed and you’re golden.
Now obviously, you may want to know what :ensure t
means. It is the
rough equivalent of "ensure": true
in a Python dictionary, or JSON.
t
is the conventional value for true
. Though others as we shall
see, are acceptable values for that form, using them is usually a bad
idea, unless you know precisely what it does. The reason is that Lisp
is loosely typed. You can have polymorphic values like t
, or a
list, or a structure, or many other things.
Now, ensure
in this case should be read is “if it’s not installed
already, install it”. As you can expect, this will install the latest
version that is available in the package archives. This does not
usually result in upgraded packages, and it’s highly recommended to
keep them up to date manually.
So what packages can you install that way? See for yourself, M-x list-packages
. All the known package archives are scraped, indexed
and all the available packages are listed. From this interface you
can install the packages that you want, but if you do it that way,
your configuration will become messy. Nothing wrong with it, but most
people prefer to do it the other way.
Expanding the scope of available packages Link to heading
There are two major ways to extend the scope of packages available for installation on Emacs. The first and up until very recently the main way is to enable the MELPA and NON-GNU ELPA (Emacs Lisp Package Archive).
The way to do it that works in my configuration file is
(require 'package)
;; Any add to list for package-archives (to add marmalade or melpa) goes here
(add-to-list 'package-archives
'("MELPA" .
"http://melpa.org/packages/"))
(package-initialize)
This is normally enough for 99% of the packages that you would want to
install. I try to post mine to MELPA as soon as they’re ready.
Another good reason to stop at this is because MELPA packages go
through a thorough vetting process and will not get accepted if they
have major problems such as gathering user data, doing something
nefarious with one’s machine, misuse the Emacs APIs or just contain
plain low-quality code with poor naming conventions. I’ve gone
through this process more than once, and I trust most of my
use-package
forms a lot more because of it.
I will say that this is not the only form that I have. To test my own
packages, I often use the :vc
keyword, which is fairly
straightforward.
(use-package disposable-key
:ensure t
:vc
(:url "https://github.com/Greybeard-Entertainment/disposable-key.git"
:branch "barba"))
The :vc
lisp form is equivalent to
"vc": {
"url": "https://github.com/Greybeard-Entertainment/disposable-key.git",
"branch": "barba"
}
which as you can already tell is quite easy to grasp. Most modern
languages differentiate between elements in a list [ ... ]
and
fields in a structure { ... }
, while Lisp uses parentheses for
both. Sometimes, you will see an odd square bracket, but that’s a
rough equivalent of a list, it’s just stored differently. So for any
package that you fancy, including your own code, you can simply
install it from a github
repo, same as with vim
. The major
upside, being that MELPA offers a high standard of baseline quality.
Recommended zero configuration packages Link to heading
This is a curated list of packages which in my opinion do not require any configuration beyond the basics.
rmsbolt
Link to heading
Most packages are incredibly easy to install, and here are some
examples. rmsbolt
adds a function that you can call with M-x
that
gives you the local disassembly of the binary program that you’re
viewing. It’s not as sophisticated as the compiler explorer, but if
you know how to read assembly, you get most of the benefit, without
also having to strip your project down to compile in the window. That
is, it can be used for production code, and not some examples. This
is quite an important distinction, because I often have to argue about
compiler optimisations for things which are specifically exposed to
the compiler.
key-quiz
Link to heading
Quite a useful package. It is not as impressive as the others, but you will start feeling its presence if you get into the habit of doing a drill or two a day. I did not have it, when I got started with Emacs, and I wish packages like it were considered more early in development. Obviously the Emacs tutorial and books such as Mastering Emacs are great at giving you a more holistic understanding of the core essentials, but I also have many packages which provide far too many functions to list.
vundo
Link to heading
Just a powerful reminder of the difference between Emacs and other
editors. This package provides a command vundo
, which would let you
traverse the various states that your current file is in, based on the
undo
history. In situations where something like a version control
system e.g. git
are not reasonable, this package helps quite a
bit.
simple-httpd
Link to heading
This is not as useful if you plan to use python
, and probably a lot
less documented. But I like it.
diminish
Link to heading
It is not useful on its own and it doesn’t provide a function that you
can call with M-x
but instead it is a package that will be useful
for the rest of this tutorial.
It removes a small notification at the bottom panel of Emacs, known as
the mode line. These are initially very useful, but you will soon
find that many packages, including mine, don’t provide a meaningful
interaction with the mode-line “lighter”, though some do. To
differentiate between those modes that provide a lighter by
convention, I will add the :diminish
keyword in the use-package
forms.
Configuration basics Link to heading
The only other two keywords from use-package
that you need are
:config
and :init
. There is a subtle difference, but for the time
being, consider them as universal keywords that state that everything
after them, is to be executed verbatim, when the package is loaded.
There are a few typical cases for how this is supposed to be operated.
There are some packages that need to be enabled in addition to being
installed to take effect. A good example of this is dirvish
.
(use-package dirvish
:ensure t
:config
(dirvish-override-dired-mode))
We already know that :ensure
installs the package if it’s not
installed, and for the package to take effect, you call
dirvish-override-dired-mode
. By convention, functions that end in
-mode
toggle the behaviour if called without any arguments. To have
more certainty, you could modify the call to look like
(dirvish-override-dired-mode +1)
. This, conventionally modifies the
mode to be enabled if not already, and to stay enabled. Similarly,
(dirvish-override-dired-mode -1)
would unconditionally disable that
mode.
Quite useful I must add.
Almost everything that you would want to do with use-package
you can
accomplish with the :config
keyword, though obviously using the
purpose built keywords has its advantages, one key benefit that is not
replicated in any other editor, is that you can evaluate the forms
written in :config
regardless of the surrounding context and they
will still work as intended. Specifically, you can put the cursor
after (dirvish-override-dired-mode)
but before the overall closing
parenthesis (again Lisp convention), and hit C-x C-e
. This will,
show you messages in the *minibuffer*
about the mode being enabled,
then disabled, then enabled again, depending on how many times you
evaluate it.
One key benefit of not writing this code outside the use-pacakge
forms, is that if you add :disable
keyword to it, that code will not
be evaluated. This allows you to keep historical configurations for
older packages, without them necessarily slowing the startup time of
your Emacs (which is probably symptomatic of an XY problem, but we’ll
talk about optimal usage later).
Now in some cases, you might want to have a few variables set within the configuration, for example
(use-package cape
:ensure t
:init
(add-to-list 'completion-at-point-functions #'cape-file))
For the package cape
to work, which is a completion package, you
need to add cape-file
to the list of
completion-at-point-functions
, i.e. functions that get called to
get completion candidates for the current cursor (point) position.
But, that doesn’t mean that you cannot, for example.
A similar form is for dumb-jump
(use-package dumb-jump
:ensure t
:config
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate))
This adds a package-provided function to another list, list of
functions that get executed once the xref
built-in mode is activated
and loaded.
In fact, -hook
lists are quite common and quite useful, used
extensively enough to have their own keyword that we shall talk about
later.
Incidentally, what this does is also important to understand.
dumb-jump
is a very basic provider for the jump-to-definition
facilities that Emacs comes with. Instead of providing its own
dumb-jump
to definition, it instead relies on the fact that you will
use xref-find-definitions
and other related functions and all that
this package, dumb-jump
provides is a way to obtain the information
in cases where you might have no other source, for example, if you use
a very esoteric language. I found it very useful for dealing with
Rust projects, because the heuristic needed to jump to definitions
locally is actually quite simple thanks to the language design.
The :hook
keyword
Link to heading
This is a very easy to understand keyword. Suppose you want some
function to be executed right after a mode is enabled. For example,
you want to enable an auto-format-on-save package that is extremely
fast, called apheleia
. You know that you are mostly going to work
with Rust, C, C++, Emacs lisp and don’t know if there are good
autoformatters for other languages, so even though you could
theoretically autoformat Python, you’d rather do it manually.
Well, this is the right job for the hook
keyword.
(use-package apheleia
:ensure t
:delight
:hook
((rust-mode . apheleia-mode)
(emacs-lisp-mode . apheleia-mode)
(c-mode . apheleia-mode)
(cc-mode . apheleia-mode)))
Now can lisp go five minutes without introducing weird syntax?
Unfortunately no. But mostly for good reasons. Remember I told you
that :ensure t
can be considered the rough equivalent of "ensure": true
? While I didn’t exactly lie, that roughly is doing a lot of
work. Lisp is largely unlike any other language, while some features
were eventually adopted into other languages, most were not.
The thing that goes after :hook
in this case is a list of lists.
The lists are all two element wide, and the first element is the hook,
the second element is the function that is to be called when that hook
is triggered. So, specifically, we run the same mode, apheleia-mode
for each of the rust-mode
emacs-lisp-mode
and so on.
Now, we’ve seen how to add-hook
in the :config
form, and it’s
quite different, but why is that? use-package
is a macro. It takes
things that look like code and converts them into actual code. It
just so happens that the code that you would write in order to install
a package if it’s not already installed is always the same, so it
makes sense to hide it inside a macro. So does it make sense to hide
the common boilerplate for the actual invocation of adding a function
to a list of hooks.
However, this is a teachable moment. Notice that when we called
add-hook
we called it for xref-backend-functions
. If we tried to
use the :hook
keyword in use-package
, because of an annoying
architectural decision, you would get an error. The use-package
macro, by default, assumes that when you say
:hook (x-mode . y-function)
that you mean to attach a hook to x-mode-hook
,
(add-hook 'x-mode-hook 'y-function)
because that is the convention. Well, sometimes, the convention is
not followed, so you kinda have to use :config
. Obviously this
behaviour can be disabled by configuring use-package
itself, but I
think that that is an even worse architectural decision:
use-pacakge
forms must universally mean the same thing.
Now, you probably know that each major mode, conventionally provides a
hook
to be called once the major mode is fully loaded, but there are
other useful hooks.
after-init-hook
is useful when you want to run a function after
Emacs is instantiated. This is useful when you don’t want to block
the startup with an expensive function. suspend-hook
is useful
because it runs the function before Emacs is suspended, when you want
Emacs to suspend. kill-emacs-hook
is useful because it runs the
function before Emacs is exited, when it is exited gracefully. Do
with that information what you will, but I will caution against using
these hooks extensively, without testing.
The :bind
keyword
Link to heading
Suppose your package provides a function that is used far too
frequently and randomly to be called from M-x
. This is quite
common. You can bind these functions to keys, using the :bind
keyword.
(use-package avy
:ensure t
:bind
("C-l" . avy-goto-word-0)
("M-l" . avy-goto-char)
("M-g c" . avy-goto-char)
("M-g M-g" . avy-goto-line)
("M-g m" . avy-pop-mark))
This is a bit different from the previous keyword. Well, not quite,
this kind of syntax without the overarching parenthesis is still
accepted by the use-package
macro. The main difference is that now,
each line is evaluated sequentially, and added to a list of lists,
just as before. Except now this list of lists is explicit. If you
ever saw a documentation string that mentioned the dreadful &REST
this is what that looks like on the calling end.
The fun thing is that this is easy to understand and read, much more
so than the :config
form would be. Specifically,
(global-set-key (kbd "C-l") 'avy-goto-word-0)
Notice, that it sets the key in the global key-map. This can be a problem, if the function that you’re binding is dependent on a mode being activated.
avy
is a package that by all accounts should replace keyboard
navigation. It has for me. It can operate in situations where no
minor mode is enabled, and the function avy-goto-word-0
is simply
loaded, if and when the key combination C-l
is invoked. Remember I
told you that I think use-package
is far superior to lazy
, that’s
why. But sometimes, there must be other setup that needs to happen
before a function can be invoked. The relationships are quite
complicated, so it is usually a good idea to make use of another
convention:
(use-package lsp-treemacs
:ensure t
:bind
(:map lsp-mode-map
("C-`" . lsp-treemacs-errors-list)))
This is equivalent to
(define-key lsp-mode-map (kbd "C-\`" 'lsp-treemacs-errors-list))
And this key binding is only valid, for as long as the lsp-mode-map
is active. Meaning that if lsp-mode
is disabled, this binding has
no effect. The key combination does what other bindings specify and
only overridden if and when lsp-mode-map
is activated.
Incidentally, this form allows me to hit that convenient key to list
all errors as reported by the resident Language server. This is quite
equivalent to running compile
, a built-in function but we will cover
that in a separate topic.
I will assume that you know how to write key bindings, but just in
case, if you need a complex key combination, angle brackets are your
friend. If you end up adding a rogue space somewhere, C-q
allows
you to enter the character regardless of what it is bound to, and I
strongly suggest never rebinding this key under any circumstances.
The :custom
keyword
Link to heading
This is probably as advanced as you may need to go, anything more in-depth is probably best suited to a time when you’re simply comfortable reading the README from the official GitHub. This piece of information can save you hours of debugging, so I recommend that you don’t skip over it.
The final special case that is handled by its own keyword and for good
reason is the :custom
keyword and it interacts with the Emacs'
customisation user interface.
Long ago, it was envisioned that Emacs was a graphical text editor.
Indeed, it still has many of the advantages of one, particularly when
it comes to rendering multiple typefaces of different sizes and
proportions, having graphical dialog windows, handling the clipboard
in a civilised fashion, and many other things.
Before the GTK version became the predominant version of Emacs to be shipped, there was an attempt at creating a Graphical user interface with only Emacs Lisp and the text editing facilities. This gaves rise to many packages, some good, e.g. tetris, some bad, e.g. “simple calculator” and some which are just plain ugly, and unbecoming of a text editor with the legendary status that Emacs has achieved.
The customize
interface is one such package, which sadly, became
standard in Emacs. To invoke this, simply call M-x customize
and it
will present to you what looks like a sensible GUI, that is until you
try to type text into what appears to be a text box. The only reason
to ever touch it, is to find what customisable variables exist withing
certain packages. But what constitutes a customizable
variable. To
put it simply, when you call setq
on a variable, the only thing that
can happen is the change in state of that particular variable. The
changes propagate up until the functions that are invoked later that
read that variable. In their infinite wisdom, the people designing
Emacs, have decided that it is a great idea to institute another way
to set those same variables, that will trigger other code to execute.
The customize
interface, will conveniently place any customised
variable at the end of your init.el
, for you to know what other
differences between your regular out-of-the-box vanilla Emacs
experience and your personalised experience exist.
It is perfectly acceptable to keep those variables in a giant block of
symbolic expressions at the end of your init file. It is perfectly
acceptable to even setq
most of those variables instead of going
through the customize
interface. However, for the perfectionists
among you, that see this as a grand violation of the celestial
mechanics of Emacs, there is a specialised keyword, called :custom
that accepts yet another form of argument.
(use-package eldoc
:ensure t
:custom
(eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly)
(eldoc-echo-area-prefer-doc-buffer t)
(eldoc-idle-delay 0.2)
(eldoc-minor-mode-string nil))
This simply put sets the variable on the left, to the value on the
right. The keen eyed observer will see that there isn’t a .
in
between the variables, that is because the customize
function which
is invoked here, can accept an optional third parameter in each list
in the list of lists. That parameter is almost never used, because
the only difference between it and a regular comment is that if the
value is further changed via the customize
interface, the interface
itself will display that comment. Given that I would recommend only
using customize
for exploratory purposes, I don’t find it
particularly useful. Given the nature of Emacs it is universally
better to read the source code for the package, and find the
customisable variable, and then to decide what value is appropriate.
It is not that when the authors provide meaningful choices, that
having a drop-down menu is worse than typing the choice by hand; it’s
that most authors don’t bother.
Truth be told, any difference between setq
and customize
is an
anti-pattern, that relies on the assumption that a half-baked, semi
textual interface is better than nothing. Technically, if utilised
correctly, it would, allow people who not only don’t know lisp, but no
programming in general, to successfully use Emacs. If all the
packages provided sensible customisation facilities, and that means
proper documentation, proper formatting, proper choices, and frankly,
proper channels to clarify some points without leaving the interface,
I would say, let’s deprecate setq
. But, this is a half-feature that
only half the community cares about and even fewer people want
properly implemented in their own packages.
Creating an Elisp package is already a gargantuan task, for people that are essentially doing it with no compensation, this is a standard of practice that I’m willing to let go.
Conclusion: when to use-package
Link to heading
Honestly, I don’t believe that use-package
is a panacea, and that it
should be applied indiscriminately to anyone’s workflow. It is
considered the industry standard, and there are good reasons, it
automates and abstracts the concept of a package, it allows
configuration to be local, and thanks to Emacs’ excellent editing
facilities this means easy translation, refactoring, etc.
While it has its benefits, there are some key considerations to keep in mind, when you as a new user will decide on whether to make heavy use of this facility.
By avoiding the native Emacs lisp facilities for installing and using
pacakges, you as a new user learn less. When I was getting started,
use-package
was already prevalent. As a consequence of never having
to install a package manually, that is downloading the lisp files and
extracting them to a place and then require
-ing them, I’ve missed
out on quite a bit of organic learning. When I started writing my own
Elisp, I found that I had not had the right exposure to the right
concepts, and thus had trouble. In fact use-pacakge
’s keywords are
the main reason why I had no idea that global-set-key
was deprecated
in favour of keymap-global-set
. This seems small, but in hindsight
it really does result in a large amount of re-learning that could have
been subsumed into the configuration stage.
A slightly less important aspect of use-package
is that you can not,
mostly, see the functions that are being invoked. For example, you do
not know the exact mechanics of what :custom
does. You cannot
differentiate between custom-set-variable
and
customize-set-variable
unless you cheat and lookup.
Yet another problem, is that you cannot evaluate forms incrementally. Given that Emacs is unlike most modern development environments where “dirty” state is persisted, and the image of a machine can be different from a linear sequence of steps, you miss out on a lot. Batch processing is a standard, but largely because of convention, not because that approach is superior. In fact, incremental evaluation of nested code, allows one to build up complex functions much more rapidly and reliably, thanks to knowing exactly what each step is going to produce. This is not unlike the reason why it is customary to create plain-text streams between programs in a conventional UNIX shell script.
Finally, over-reliance on use-package
has made it difficult for me
to move on to other editors. I do not plan to remain an Emacsian
forever. I find that it is worth exploring other ideas, and I would
not be able to do that, if I were bound by the same limitations as
Emacs imposes, even subliminally.
If I were just starting out, I would use use-package
extensively to
see what packages are available. But at the moment I had enough
interest so as to write custom Emacs lisp (which comes later than you
can imagine), you should drop use-package
entirely, and attempt to
replicate your workflow in a purely function and not macro-driven
approach.