When I leave the office, I generally close all work-related things – I sign out of Slack, shut down the virtual machine, and unmount the encrypted filesystem with work data. Obviously, I also want to kill all buffers “related to” that filesystem, for example all buffers visiting files there.
This is similar but different from project-kill-buffers
– that command kills the buffers belonging to the current project (for example, the current Git repository), and I usually have more than one Git repo in the encrypted filesystem.
There seems to be a few ways of doing this. Probably the simplest one uses Ibuffer. You can just press * e
to mark all buffers whose associated file doesn’t exists and then D
to kill the marked buffers. This is a clever thing (since the filesystem is unmounted, all buffers visiting files there will get marked), but sadly is not enough. For example, Magit buffers are not associated with files, so they won’t be selected.
Another idea is to use % f
to mark buffers by their filename, and enter a regex matching the directory name you need. For some reason, the Magit buffers are also selected with this approach, although I’m not sure why. I think the magic happens in the ibuffer-buffer-file-name
function, but I’m not really sure if I want to dig into how exactly this works, since this solution is not complete, either. For example, *Diff*
buffers are not marked with this approach, and I often diff similar fragments of code.
It turns out that Ibuffer has yet another facility which can help here. / F
can filter the buffers by their directory; once I only have buffers in a given directory, I can press t
to mark all of them (actually, toggle all the marks, but if nothing is marked, this does what I need) and then D
to kill them.
This, of course, is just a workaround – what I’d really need is a simple command, let’s call it kill-all-buffers-in-directory
, which would do exactly this, but automatically. It is still just a proxy – I could open a file in my encrypted directory and M-x cd
to another directory in it – but as this is something I never do, closing all buffers whose “default directory” is a subdirectory of the mount point of my filesystem is enough for me.
As usual, the hard thing is to create a reasonable UI. In this case, this means notifying the user about the result of the buffer killing spree. The user must know if not all buffers were killed (this may happen if for example a buffer was visiting a file modified since the last save, and the user answered “no” when asked whether to save that buffer). I decided that just showing how many buffers were killed out of how many found is fine; as an added bonus, I used error
if at least one buffer was not killed.
(defun kill-all-buffers-in-directory (directory) "Kill all buffers whose current directory is DIRECTORY." (interactive "D") (let* ((buffers (seq-filter (lambda (buffer) (string-prefix-p directory (expand-file-name (with-current-buffer buffer default-directory))) ) (buffer-list))) (killed-status (mapcar #'kill-buffer buffers)) (buffer-count (length killed-status)) (not-killed-count (seq-count #'null killed-status)) (killed-count (- buffer-count not-killed-count)) (message (format "%s/%s buffer(s) killed" killed-count buffer-count))) (if (zerop not-killed-count) (message message) (error message))))
Note that I used the built-in seq-filter
to avoid depending on cl
. (I could have used seq-map
too, but there is nothing wrong with mapcar
.)
Also, I had to use expand-file-name
. The docstring for default-directory
says:
It should be an absolute directory name; on GNU and Unix systems, these names start with “” or “" and end with “”.
It turns out that for buffers visiting files, default-directory
starts with /
(at least this is what I have observed), but for Dired buffers under my home directory it starts with ~
. Go figure.
That’s it for today. As usual, Emacs proves to be a great environment for tinkering and molding your tools into exactly what you need – even if sometimes an unexpected quirk pops up. Of course, I cannot not mention here the late Robert J. Chassell’s excellent Introduction to Programming in Emacs Lisp – and also my book about Emacs Lisp, designed as a “next step” after that. That book shows how to build three utilities in Emacs Lisp, starting from a very simple prototype and arriving at a useful tool for editing prose (reorder-sentence
), complete with a pretty polished UI.