Hiding Buffers in Emacs
Emacs users can quickly become overwhelmed by the large amount of buffers which accumulate. Many of these are ancillary buffers created by various packages which govern aspects of the package, such as logging or process management. Some of these can be killed, but usually it's a good idea (or even necessary) to keep them around.
These ancillary buffers come in 2 flavors:
- The ones you never want to see
- The ones you occasionally want to see
Buffers You Never Want to See
In the first case, Emacs by default considers buffers with names starting with a space to be hidden.
So renaming a buffer using rename-buffer
(C-x x r
) to something with a leading space will mark the buffer as hidden.
This means that the buffer will not be completed/listed by commands such as switch-to-buffer
and ido-switch-buffer
.
It also won't be selected by previous-buffer
(C-x <left>
) or next-buffer
(C-x <right>
).
This is a fairly drastic measure, as the only real way to manually switch to a buffer hidden like this is by either typing its name precisely after calling something like switch-to-buffer
, or using a package like consult
and calling consult-buffer SPC
.
Buffers You Occasionally Want to See
Basics
In the second case, for buffers which you don't want to get in your way when switching between buffers quickly but do occasionally want to pull up, we have the variable switch-to-prev-buffer-skip
(and its regexp cousin).
This variable determines whether or not previous-buffer
and next=buffer
(and their cousins) will switch to a given buffer when it's "next" on the list.
It can take on a few specific values, but in the general case it points to a predicate function of the buffer being switched to.
For example:
(defun buffer-skip-p (_window buffer _bury-or-kill) (with-current-buffer buffer (eq major-mode 'rust-mode))) (setopt switch-to-prev-buffer-skip 'buffer-skip-p)
Here we define a function which takes in a window
, buffer
, and whether or not to bury-or-kill
the buffer being switched to.
It then evaluates to t
whenever the major mode of the buffer is rust-mode
.
Finally, we set switch-to-prev-buffer-skip
to our function's symbol.
Now, whenever we call e.g. next-buffer
, it will look up and call this function, and if it returns t
because the mode of the buffer is rust-mode
, it will not switch to it, but instead to whatever the next buffer on the list is which this function would return nil
on (phew!).
Not that this variable does not affect functions like switch-to-buffer
or ido-switch-buffer
.
Note also the function signature.
It expects 3 parameters, so it can incorporate info from the window
or the bury-or-kill
status in order to determine whether or not the buffer should be skipped.
The example above simply disregards that info.
Polish
This is all well and good, but there might not a be semantic rule that all buffers which you want to keep quasi-hidden obey (unless perhaps you're really not a rust fan 🦀).
You may instead have a bespoke list of buffers specific to your packages and how you use them which you want to hide.
But this is easy enough to do with swtch-to-prev-buffer-skip
.
Consider:
(defvar hidden-buffers '("blazing-webapp.rs" "rocket-cli.rs")) (defun buffer-skip-p (_window buffer _bury-or-kill) (member (buffer-name buffer) hidden-buffers))
Here we simply declare a list of "hidden" buffers by their names as strings, and have buffer-skip-p
check if the given buffer's name is in the list.
Now we can hide any buffers we want!
A couple of QOL improvements would be a method for quickly adding buffers to this list, and a way to ensure that this list var persists across emacs sessions.
Here is the final setup with these extra niceties1:
(defvar hidden-buffers '()) (with-eval-after-load 'savehist (add-to-list 'savehist-additional-variables hidden-buffers)) (defun hidden-buffer-p (_window buffer _bury-or-kill) (member (buffer-name buffer) hidden-buffers)) (setopt switch-to-prev-buffer-skip 'hidden-buffer-p) (defun hide-buffer () (interactive) (cl-pushnew (buffer-name) hidden-buffers :test #'string=)) (defun unhide-buffer () (interactive) (setq hidden-buffers (remove (buffer-name) hidden-buffers))) (keymap-global-set "C-c b h" 'hide-buffer) (keymap-global-set "C-c b u" 'unhide-buffer)
Now, whenever we find ourselves in a buffer we'd rather not deal with during our regular emacs-ing, we can simply call C-c b h
to hide the buffer by name.
If we decide that a buffer has been neglected for too long, we can switch to it with something like switch-to-buffer
, and unhide it with C-c b u
.
Conclusion
This totally changed how I do buffer management, from exclusively relying on switch-to-buffer
+ completion packages like vertico
and orderless
, to actually binding next-buffer
and previous-buffer
to nice keys and using them liberally to quickly jump around only the most relevant buffers.
I hope you found this helpful! You can find my entire emacs config here
Footnotes:
Thanks to u/789yuiop for helping clean up this code.