even more emacs advice

In my last emacs advice post, I explained what Emacs advice is for, and showed a simple example of advising a package installation function so that fresh package metadata would always be downloaded before it ran. (If you haven’t read that post, you’ll get a lot more out of this one if you read that one first. It’s ok. Go ahead, we’ll wait for you.)

screenshot of emacs dired buffer
dired with all-the-icons

Okay, so I said this next post would involve a more complex example. Let’s set up the problem first: I make pretty frequent use of an Emacs package called dired. Dired lets you interact with the file system from inside Emacs — it lists files and directories, and supports operations like copying, deleting, and renaming. It’s pretty nifty.

I also use a second package called all-the-icons. It adds pretty file icons to dired buffers. The screenshot at the left is from a dired listing of the top level of the repo for this blog. The icons on the left side come from all-the-icons. Not at all essential, but they make things a little more pretty, which is also pretty nifty.

One of the best parts of dired is a special mode called wdired, which gives you a writable dired buffer. Essentially, you can batch rename a whole bunch of files, using all the text-editing abilities of Emacs, and then when you exit this special mode, Emacs applies all the changes for you. I find it super useful (and extremely nifty).

But, there’s an issue with how these three modes interact with each other — those pretty icons are actually present as special characters in the dired buffer, and when you flip into and out of wdired mode, Emacs gets confused about the filename changes you’re trying to make.

Clearly, what we need to do is disable all-the-icons-mode when we enter wdired and then turn it back on after we exit wdired. What’s that look like?

First, we need a couple utility functions to turn all-the-icons-mode on and off. (Technically, we don’t need these; they’re simple enough they could just be lambda functions inlined into the advice — but we will get a minor benefit from them before we’re done). Those utility functions look like this:

(defun genehack/disable-all-the-icons ()
"Disable all-the-icons."
(all-the-icons-dired-mode 0))
(defun genehack/restore-all-the-icons ()
"Restore all-the-icons."
(all-the-icons-dired-mode 1))

Now, how do we figure out what functions we need to advise to solve our problem? The documentation mentions using the C-x C-q binding in dired buffers, which runs dired-toggle-read-only — but checking the documentation for that function shows us that the real work is done by wdired-change-to-wdired-mode, so that’s where we’ll put the “turn off icons” advice.

When we’re in wdired mode, the screen shows a little bit of help text at the bottom: “Press C-c C-c when finished or C-c ESC to abort changes”. So to figure out which functions exit wdired, we can just activate the mode, and then use the built-in documentation capabilities of emacs to investigate. Pressing C-h k will prompt for a keybinding, and then show you what function is bound to it. Using that shows that in wdired mode, C-c C-c is bound to wdired-finish-edit and C-c ESC is bound to wdired-abort-changes. We need to advise both those functions to handle both ways wdired mode might be exited. (That’s where having those utility functions comes in handy — we avoid some code duplication.)

Here’s the advice:

(advice-add 'wdired-change-to-wdired-mode
:before #'genehack/disable-all-the-icons)
(advice-add 'wdired-finish-edit
:after #'genehack/restore-all-the-icons)
(advice-add 'wdired-abort-changes
:after #'genehack/restore-all-the-icons)

These tiny little bits of code do the trick: right before wdired is activated, all-the-icons is turned off; right after wdired is exited — via either route — all-the-icons is turned back on. Yet another problem solved with a little advice.

Speaking of advice, an emacs pro-tip for the folks that read this far: make your lives easier, and your wdired use more convenient, by setting up a better keybinding for entering the mode. Personally, I use E (for “Edit”), which happens to be unbound in dired by default. This code will set up that binding for you:

(defun genehack/bind-key-for-wdired ()
"Add a keybinding for wdired in 'dired-mode'."
(local-set-key (kbd "E") 'wdired-change-to-wdired-mode))

(add-hook 'dired-mode-hook 'genehack/bind-key-for-wdired)

All the code in this post is available in my emacs config repo, specifically in the etc/builtins.el file.

What are you using advice for?