Switching to Selectrum for incremental narrowing in Emacs
My Emacs tweaking tends to go in waves. I keep an eye on the Emacs subreddit and the Doom Discord channel on a fairly regular basis, but I try not to jump on every cool new package I see discussed there. Every now and again though, I see something interesting which coincides with an itch to tinker with Emacs, and away I go… This time, it was reading discussions about a constellation of relatively new packages concerned with incrementally narrowing lists (and other related functions) that caught my eye. This sounds very niche, but for many people (me included) being presented with a list of things and being able to type to incrementally narrow the list and then select something is a core part of the Emacs UI. Since I use Doom, and it offers you an easy way to choose either Ivy or Helm, I had been using Ivy, for the sake of easy configuration. Both are fine packages, but having tried both, I preferred Ivy’s more minimal interface, and the fact that it used the minibuffer rather than a buffer for completions. However, it — and the related packages, Counsel and Swiper — are somewhat complex and difficult to get to grips with. I was also not using all the features that they provided, so was curious if I would enjoy using something even simpler. That’s why I tried out Selectrum.
The Selectrum README page on GitHub gives an admirably thorough and clear
explanation about the niche that it is trying to fill, and how it compares to
other similar packages. What I like about it is that it tries to stick as
closely as possible to using Emacs’ standard APIs, so that once you enable it,
many standard Emacs commands (like find-file
) automatically use Selectrum. It
also doesn’t try to do everything, so it really only provides incremental
narrowing, leaving sorting and filtering (and actions to operate on the selected
candidate) to other packages. This means that you have a bit more package
installation and configuration to do up front, but it also means that you can
pick and choose which elements you need and leave the others, so you end up with
a simpler system. I chose to use the following additional packages:
Prescient.el
prescient.el sorts choices in a more intelligent way, remembering your recent
selections and bubbling those to the top of the list. It also enables you to
toggle on and off different ways of matching a candidate (for example by the
initial letter of each word, or by regular expression). I have found prescient
to work very well in practice. It also works with Ivy and Helm and other
incremental narrowing packages, so you can use it even if you don’t use
Selectrum.
Consult
consult works with any function that works with Emacs’ built-in completing-read
function (including Selectrum). It is intended as a rough replacement for
counsel
, and provides some useful functions for searching a buffer interactively
for text, searching headlines (in Org or Markdown files, or any mode that
implements outline-mode
). It has a very handy function consult-buffer
. Despite
the name, it is a multi-purpose function which enables you to search and select
among a list of buffers, files and bookmarks by default, so you don’t need
separate commands to search each of those categories. If you do want to filter
the list because you know you are searching for a buffer, not a file, for
example, you can prefix your search with ‘b’ (or ‘f’ for file, or ’m’ for
bookmark). I’ve found this so handy that I have bound it to Doom’s SPC SPC
binding for easy access. I previously often found myself searching for a buffer
first, before realising that I needed to search for a file as the buffer
containing that file wasn’t yet open. This stops me having to think about the
distinction, but allows me to narrow the list if I am sure.
Marginalia
marginalia adds ‘annotations’ to some of the lists presented by Selectrum. For example, if you use M-x it can add key-bindings and also docstrings to the list of commands. Ivy did this too, and I found it extremely useful.
Embark
embark is the package I am least sure about whether I will keep, but it is
handy. It enables you to hit a key-binding when you have a candidate selected in
a list and apply some action to the item. It knows whether the item is a file or
buffer or some other item, so it presents a list of appropriate actions for
each, like opening a buffer in the other window, or renaming a file. There are
of course many other ways to achieve this in Emacs, but I find that the method
of selecting something first, then choosing how to act on it works well for me.
It is also really simple to add new actions, and I added one to jump to the
dired buffer for a file. That was as simple as the snippet below in my config.el
file (swap use-package!
for use-package
if you are not using Doom):
(use-package! embark
:after selectrum
:bind (:map minibuffer-local-map
("C-o" . embark-act)
("C-S-o" . embark-act-noexit)
:map embark-file-map
("j" . dired-jump)))
Is it better than Ivy/Counsel/Swiper?
This setup doesn’t do anything that that Ivy and friends didn’t. It does some
things a bit differently, and in ways I prefer. The consult-outline
command
presents Org headings in a way I find much easier to navigate, for example.
I am going to stick with these packages because I find the smaller,
more focused packages easier to understand and configure, and potentially to
extend as I need.
The other impressive thing about them is that the authors of each of the packages are talking to each other and trying to make their packages as inter-operable as possible and also as simple and discrete as possible. The Marginalia package was extracted from the Consult and Embark packages (which are maintained by different people), to reduce the overlap between them and ensure that you can mix and match the different components like Lego bricks to build your ideal setup. I find this an admirable position, which helps to keep users’ options open and improve all the tools. I’m going to watch the development of all these packages with interest.