Extending Emacs’s Dictionary Library
Introducing new customization options in the Emacs package dictionary.el
Yesterday Eli Zaretskii applied a patch that I’ve submitted to the
Emacs master
branch which introduces new customization options to Emacs’s
dictionary.el
library.
dictionary.el
is a library that lets Emacs query RFC2229 dictionary servers
for word definitions, and display these definitions to the user upon request.
This package was initially written by Torsten Hilbrich to support both GNU Emacs
and XEmacs, and was added as a library to Emacs core in version 28.
I’ve started using dictionary.el
slightly over a month ago after reading
Bozhidar Batsov’s post Looking Up Words in a Dictionary. Beforehand, I’ve been
using the define-word
package by Oleh Krehel which achieves a similar goal of
obtaining and displaying word definitions, but instead of querying RFC2229
dictionary servers like dictionary.el
does, define-word
performs HTTP
requests to web-based dictionary services and parses their HTML responses.
define-word
served me pretty well, but since dictionary.el
is built into
Emacs I thought I’d give it a try. Another notable difference between these
packages is that define-word
displays word definitions as transient messages
in the echo area, while dictionary.el
pops up a dedicated *Dictionary*
buffer.
My first impression of dictionary.el
was quite positive. I consult the
dictionary quite often, and I liked how dictionary.el
puts the definition in a
buffer that stays visible while I write rather than a message that disappears on
my first keystroke. But then there were also some quirks. First, I found that
whenever dictionary.el
displays a definition in the *Dictionary*
buffer, it
also selects that buffer. That’s inconvenient when I’m in the middle of writing
a sentence and just want to quickly glance at a word’s definition to make sure
I’m using it correctly. This is similar in nature to consulting a function’s
definition or docstring when writing code, it should be a frictionless process.
Having to switch windows from the *Dictionary*
buffer back to whatever buffer
I was in the middle of editing means unnecessary friction, especially if I have
multiple open windows to navigate between.
I can imagine that at this point you’re thinking “well, this is Emacs after all, can’t you customize it not to switch buffers or something?”, and no, I couldn’t really. But now you can. More on that later though, for now let me rant just a bit longer.
The other problem I came across when trying to adopt dictionary.el
was its
“cool” feature of restoring the window configuration you had while searching the
dictionary when you quit the *Dictionary*
buffer. The way this works is that
dictionary.el
saves the current window configuration when creating the
*Dictionary*
buffer, and binds q
to the command dictionary-close
which,
among other things, tries to restore the saved window configuration. This is,
in my view, plain harmful. I can understand the idea behind it –
dictionary.el
wants to let you quit the *Dictionary*
buffer, so it needs to
do something with the window it inhabits. It doesn’t want to make an arbitrary
decision for the user so it takes the seemingly safe choice of reverting to how
things were when the buffer was created. The intention is admirable, but the
implementation is unfortunately shortsighted. The problem is that a lot of time
can pass between the moment you perform a dictionary search and the moment you
quit the *Dictionary*
buffer, and in that time you may have moved on to
working on something else entirely. Perhaps you’ve even set up a perfect window
configuration that shows you exactly the right context for the task at hand.
And then you quit the *Dictionary*
window. And your carefully crafted context
is gone, nowhere to be found. You’re left looking at a bunch of random buffers
that happened to be open when you looked up “palaver” or some other word you’ve
read on Prot’s blog.
This behavior basically assumes that you quit the *Dictionary*
window right
after performing a search, without doing anything else with Emacs in the
meantime. In all other cases–it’s a footgun. What dictionary.el
should have
done instead, is to bind q
to quit-window
as is the case in special-mode
and its derivatives. This command is specifically designed for getting the
current buffer out of view: paraphrasing from the docstring, it “deletes the
window or shows some other buffer in it”. And it’s standard Emacs stuff,
tweakable via standard Emacs mechanisms.
This brings me to my subtler, broader issue with dictionary.el
, which is that
it doesn’t make good use of existing Emacs facilities, and instead it tries to
reinvent the wheel in too many places for my taste. Maybe it’s because it was
originally supposed to also support XEmacs which had some differences compared
to GNU Emacs, I’m really not sure. The result is a library that’s harder for me
as a user to customize and extend.
In face of this dissatisfaction, I decided to write my own dictionary lookup
package for Emacs. I’ve put together a minimal yet powerful RFC2229 dictionary
client package for Emacs in less than 300 lines of Elisp, including headers and
docstrings. I called it Dict, because it was shorter than dictionary.el
.
I’ve proposed my Dict package for inclusion in GNU ELPA over the emacs-devel
mailing list earlier this month, noting that I wouldn’t mind trying to modify
the built-in dictionary.el
instead of adding this new package, if the Emacs
maintainers would be open for that kind of change. Eli responded that
augmenting dictionary.el
would indeed be preferable, which led after some more
back and forth to the patch this post is really all about.
My patch adds a few new user options to dictionary.el
that affect the behavior
of the dictionary-search
command. The most influential new option is
dictionary-display-definition-function
. It controls how dictionary-search
displays dictionary definitions, by specifying a function that responsible for
taking a definition and displaying it. We now also have a new function called
dictionary-display-definition-in-help-buffer
that you can use as the value of
dictionary-display-definition-function
to have definitions appear in the
standard Emacs *Help*
buffer. The way I see it, the *Help*
buffer is ideal
for this task as it is the same interface I use for viewing transient reference
information in Emacs–such as variables’ docstrings or active keybindings–all
the time. In the *Help*
buffer we can click on references to other dictionary
definitions to display them instead, just like we do with references to Elisp
functions. We can than press l
to go back and r
to go forward, and q
naturally invokes quit-window
rather than a bespoke
window-quitting-and-restoring command. Other notable new options are
dictionary-read-word-function
and dictionary-read-dictionary-function
that
control how dictionary-search
prompts you for a word and a dictionary to
search in, respectively.
On Eli’s advice, I’ve also added a more high-level user option called
dictionary-search-interface
that uses the :set
property of defcustom
to
modify the values of all three aforementioned options together when you set it.
The idea is that this option acts as a bundle that users can customize once
instead of configuring several separate options. If you customize
dictionary-search-interface
to the symbol help
, it makes the
dictionary-search
command behave similarly to the corresponding command from
my short-lived Dict package. Crucially, definitions are displayed in a *Help*
buffer. It also enables completion when prompting for a word to search for
based on matching dictionary definitions and improves the default word choice.
With this patch in place, my settings for dictionary.el
are now as follows:
(setopt dictionary-search-interface 'help dictionary-default-strategy "prefix" dictionary-default-dictionary "gcide" dictionary-server "dict.org") (keymap-global-set "M-#" #'dictionary-search)
If you also use the dict.org
dictionary server, I strongly suggest setting
dictionary-default-strategy
(which designates the word-matching strategy to
use) to something other than the default "."
. Otherwise, the dictionary
server uses a matching strategy of its choosing, and this server’s default
choice was reported to misbehave for some words. As you can see above, I
currently use the "prefix"
matching strategy. Other strategies I’ve found
useful are "substring"
for matching words that contain the string we’re
searching for, and "re"
for regular expression matching. There’s also
"soundex"
for matching words that sound similar to the input string, which is
helpful when I’m not totally sure how to spell the word I have in mind.