In our exploration of the ways to navigate back to previous buffer
positions, Registers can be seen as mark ring items with an assigned
name, so that they can be conveniently accessed by that name in an arbitrary order.
But in fact they are much, much more:
Playing Hansel and Gretel is just the excuse to happily slip into
yet another rabbit hole.
Let’s go!
As we saw in the previous installment, rings are a beautiful and powerful idea. Being cyclic LIFO data structures they are very suitable for linearly traversing back histories. For example:
undo-ring
.mark-ring
.There are instances, though, where you might prefer to mark a position
in a more absolute manner, assigning it a name, akin to pinpointing
a location on a map and labelling it Home
or Best pub in
town
.
In this case, there are better data structures than a ring. As a programmer, you might prefer a key-value lookup table.
Imagine you want to give each stored position a name, using a single
character such as a
or x
. This could be easily done with a hash table:
(setq buffer-positions (make-hash-table))
(puthash
'a
'((current-buffer) . (point))
buffer-positions)
The first command defines buffer-positions
as a hash-table
variable.
Then we use (puthash KEY VALUE TABLE)
to store the cons cell
'(CURRENT-BUFFER . CURRENT-POSITION)
at the key a
.
Applying what we learnt in Emacs: Let’s surround! - Prompt the user for input, we could conceive a function:
buffer-positions
with gethash
.switch-to-buffer
)
at the right position (with goto-char
).(defun jump-back-to (key)
(interactive "cPosition key: ")
(let* ((value (gethash key buffer-positions))
(buffer (car value))
(position (cdr value)))
(switch-to-buffer buffer)
(goto-char position)))
Congrats! You just invented the notion of registers!
As lookup tables, Hash Tables are amazingly fast,
especially with large data sets. Indeed, they offer O(1) constant time
reads. But in the humble case of recording buffer positions maybe we
are killing a fly with a cannon and perhaps a simpler
alist storing '(KEY . (BUFFER . POSITION)
would
suffice:
(setq buffer-positions '())
(push
(cons 'a (cons (current-buffer) (point)))
buffer-positions)
;; equivalently, but maybe more mysteriously
(push
`('a . (,(current-buffer) . ,(point)))
buffer-positions)
The function to jump back would be the same as before, only using
alist-get
instead of gethash
:
(defun jump-back-to (key)
(interactive "cPosition key: ")
(let* ((value (alist-get 'a buffer-positions))
(buffer (car value))
(position (cdr value)))
(switch-to-buffer buffer)
(goto-char position)))
Of course no. The dumb function we have just written is naïve in so many ways:
Registers are implemented in Emacs the same way we just described: a
single alist variable, called register-alist
, containing keys and
values. Plus, naturally, a large set of functions to operate on them.
Once you have stored a couple of buffer positions, one to key a
(ASCII 97
) and one to x
(ASCII 120
), the value of
register-alist
would be something like:
((120 . #<marker at 556 in *scratch*>)
(97 . #<marker at 3805 in 2024-03-30-emacs-registers.md>))
I guess you recognise this is the same idea we played with before.
The fact that registers are stored in an ordinary variable offers the
possibility to build on top of them re-using the whole arsenal of Lisp
and Emacs tools.
This is what never ceases to amaze me of Emacs: its building blocks
are just the simplest ideas you could think of, and the fact they are
orthogonal and composable puts no limits to what you can build with
them.
Many other editors also offer a notion of registers; it is very
likely, though, that they are implemented as a special feature, alien
to other building blocks. Vim, for example, has a notion of variables,
yet registers are something special, accessed with peculiar getreg
and setreg
functions.
On the contrary, Emacs registers are nothing special: just items in an
ordinary association list.
What makes them so useful is the set of functions built around them.
Akin to set-mark-command (C-SPC)
to store a position in a mark, and
its alternative C-u C-SPC
to pull it back — as we saw in Mark
Ring — you might expect 2 equivalent
functions for registers. And you are right:
Command | Functionality |
---|---|
point-to-register (C-x r SPC) |
Store current location in a register |
jump-to-register (C-x r j) |
Go to location stored in a register |
I bet that you can implement yourself both, in their basic form. Do this, please, only to appreaciate how beautifully designed Emacs is.
Registers are so useful that by default they have a whole area of key
bindings under C-x r
, where r
stands of course for Register
(actually, they share the area with bookmarks, which are kinda like
registers, and with rectangles). Keybindings in Emacs are completely
arbitrary and you can always redefine them, but I think the standard
ones for registers are fairly convenient:
Key binding | Mnemonic |
---|---|
C-x r SPC |
Akin to C-SPC for the mark ring |
C-x r j |
r egister j jump |
I’m biased, though: I think that learning Emacs by memorizing thousands of shortcuts is a pointless torture. Instead, as a programmer, I would rather focus on commands and use packages such as which-key to help my terrible memory. I like to learn shortcuts only when I get bored to type the same command over and over.
Ah, yes: of course, how to forget the amazing consult? Just
like there is a consult-mark
for the mark ring, run
consult-register
:
What would be life without the marvelous consult?
If register-alist
were to store not just a reference to buffers, but
to their underlying file names too, then the function
jump-to-register
would be able to navigate back to closed
files. That would come in very handy, wouldn’t it? And it would not be
that hard to develop.
But things are often a bit trickier than expected. Think to Dired:
with dired one can move or rename a file and when this happens, all
the registers mentioning that filename would need to be updated. There
is also a rename-file
functions, and who knows which package uses
it, and when.
So, there should be a way to keep the file names stored in registers
up to date.
In reality, Emacs implements a different approach: registers keep only
references to buffers (with the type marker
) while they are alive,
so they are indipendent from file names. Right before a buffer is
killed, its marker
registers are updated and converted to a
different type, called file-query
and containing a file name instead
of a buffer reference.
You can see this in action:
C-x r SPC RET a
.register-alist
with M-x describe-variable
RET register-alist RET
.You will see something like:
((97 . #<marker at 7197 in 2024-03-30-emacs-registers.md>))
Notice how the register a
is labelled as marker
and how it does
not mention the file name at all.
Now:
register-alist
again:((97 file-query
"/home/arialdo/prg/markdown/arialdomartini.github.io/_posts/emacs/2024-03-30-emacs-registers.md" 7197))
If you ask Emacs to jump back to register a
, it will find a register
of type file-query
and it will then ask if you desire to visit that
file again.
How did it come to pass that killing a buffer changed a register? It is all about hooks.
We already encountered hooks in the post How to activate the
functionality X for all files of type Y?
In short: a hook is a variable holding a list of parameterless
functions that are invoked when a specific event occurs. As you can
imagine, there is a hook called kill-buffer-hook
. Before a buffer is
killed, Emacs runs this hook’s functions: one of them converts all the
marker
registers to file-query
items.
If you have not created a register, yet, and you run M-x
describe-variable RET kill-buffer-hook RET
you might be puzzled not
finding any reference to registers. Indeed, Emacs here is a bit lazy:
the creation of hooks to convert registers from marker
to
find-query
is deferred.
If you are one of those horrible nosy people who like to open the hood
to look what’s inside, you might like to visit the source code of
point-to-register
and find out this:
(defun point-to-register (register &optional arg)
...
;; Turn the marker into a file-ref if the buffer is killed.
(add-hook 'kill-buffer-hook 'register-swap-out nil t)
where register-swap-out
declares:
(defun register-swap-out ()
"Turn markers into file-query references when a buffer is killed."
So, in short: the very moment you create a marker
element in a
register, that’s when Emacs subscribes to the buffer killing event, so
it gets ready to replace the reference to a buffer with its underlying
file name. A bit indirect, indeed. In one of the next posts we will
play with the different, maybe more convenient approach of directly
saving a file-query
item.
By the way, this is fine, but a bit incomplete. If you expect Emacs to update the file name in a register when you rename a file via Dired, get ready to be disappointed: this will not happen; renaming files breaks registers. Maybe in one of the future issues we will play with the elisp code to fix this. Stay tuned.
I can hear your inner-programmer’s voice suggesting:
Hey! If
register-alist
is an ordinary variable, if it is already used to storemarkers
andfile-queries
types, then why not to use it to store other kinds of elements too, such as keyboard macros, snippets of text and the like?”
And this is very legit ambition! There is indeed no reason not to do that. In fact, there are other groups of functions for storing and retrieving other kinds or elements with registers:
Kind of element | Command | Key binding | Functionality |
---|---|---|---|
Buffer positions | point-to-register |
C-x r SPC |
Store a position |
jump-to-register |
C-x r j |
Jump to a position | |
Texts | copy-to-register |
C-x r s |
Copy text in a record |
insert-register |
C-x r i |
Paste | |
append-to-register |
Append text to a register | ||
prepend-to-register |
Prepend text to a register | ||
Rectangles | copy-rectangle-to-register |
C-x r r |
Copy a reactangle area to a register |
insert-register |
C-x r i |
The ordinary paste | |
Keyboard macros | kmacro-to-register |
C-x C-k x |
Save a macro in a register |
jump-to-register |
C-x r j |
Jump is smart enough to know it’s dealing with a macro | |
Files (with unspecified position) | (set-register r '(file . name)) |
Save a file name in a register | |
jump-to-register |
C-x r j |
Jump to that file | |
Windows layouts | window-configuration-to-register |
C-x r w |
Save a frame layout to a register |
jump-to-register |
C-x r j |
Oh yes, Jump to a position is smart enough<br>to know it’s dealing with a frame state |
|
Numbers | number-to-register |
C-u number C-x r n |
Store a number |
increment-register |
C-u number C-x + |
Increment a number | |
insert-register |
C-x r i |
insert-register is smart enough to understandit’s dealing with a number |
Wow, that’s a mouthful of keybindings to remember, isn’t it?
But I insist: don’t be intimidated by shortcuts. Think to commands
instead. They are way more intuitive. M-x
— and consult
— are your best friends. If, and only if, if happen to use C-u
number C-x r n
so many times that you will find it convenient to
learn the key sequence, then memorize it. Otherwise, just enjoy Emacs
and forget its shortcuts.
You are a lazy and a short-memory person like me, you could appreciate
consult-register-store
. It is a smart enough to understand what you
want to store in a register from the context. If you have an active
region, of course it will store a region.
In case of multiple possibilities, it will ask you how to proceed:
In the case of storing a region, it asks you whether you want to append or prepend it to a register:
Then, browse your registers with consult-register
or directly load
them with consult-register-load
.
Neat, isn’t it? I love Consult: [Daniel Mendler][minad] would deserve a monument.
Here is another tempting though: if register-alist
is an ordinary
variable, why not to take its value and save it to a file so Emacs can
restore it the next time it runs? Why not to make registers
persistent? Why not to do the same with the mark-ring? After all, we
don’t want our breadcrumbs to be eaten by birds, and we rather prefer
using more durable white stones, don’t we?
This is a legit desire, and indeed there are techniques to achieve this result. It’s about the Save Hist and the Session Management functionalities.
The short, canonical answer to saving registers is to add
register-alist
in the Save Hist’s variable
savehist-additional-variables
, which is the collection of variables
you want to be persisted from one session to another:
(add-to-list 'register-alist savehist-additional-variables)
Then, enable savehist-mode
executing (savehist-mode)
. Your
registers will be saved in the history
file and restored at the
beginning of the next session.
Unfortunately, there are some pitfalls with this approach. But this
is another rabbit hole: it deserves a separate post and I promise I
will write about this soon.
First, we need to talk about bookmarks.
(Thanks to Protesilaos for the kind review).