Extending Emacs’s Dictionary Library

Introducing new customization options in the Emacs package dictionary.el

Created on [2023-05-26], last updated [2023-06-11]

Yesterday [2023-05-26] 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.