Buffer-associated window layouts in Emacs

I’ve recently spent some time configuring Emacs. It is a pastime of sorts in which I occasionally get really invested and then eventually lose interest again. Each time, though, I start my configuration afresh, and I always learn something new.

This time, my interest has been split between two packages. On the one hand EXWM, a fully working X window manager within Emacs, and on the other Wanderlust, an e-mail client for Emacs with built-in IMAP support. The two packages are obviously quite different from one another, but they do have one thing in common: they require a lot of manual window management.

In Emacs, there are two common window layouts: the single-window layout, which is what it normally starts in, and the two-window layout, with a file in the upper window and a help buffer in the lower window. Usually, when I’m editing a file, I use a two-window layout.

However, when using EXWM, I’ve often found myself wanting to go from editing a file in a two-window layout to viewing an X program in a one-window layout. The obvious solution is to close the second window and switch to the buffer containing the X program. The problem with this solution is that I lose the original two-window layout. When I switch back to the file I was editing, it will be displayed in a single-window layout.

This annoyed me to such a degree that I spent a lot of time thinking about how best to solve it. Eventually, I come up with the following solution, which is both very simple and very useful.

I call it buffer-associated layouts. The idea is that each buffer is associated with a certain window layout, which is restored whenever that buffer is switched to. The implementation looks like this:

(defvar *buffer-layouts* (list) "Buffer-layout associations")
(defvar *protect-buffer-layouts* nil "Temporarily protect buffer layouts")

(defun restore-buffer-layout ()
  "Restore the layout associated with the current buffer."
  (let ((conf (alist-get (current-buffer) *buffer-layouts*)))
    (if conf
	  (set-window-configuration conf)
	  (message "Restored buffer layout"))
      (setf (alist-get (current-buffer) *buffer-layouts*)
      (message "Set buffer layout"))))

(defun switch-to-buffer-with-layout ()
  "Switch to the window layout associated with a buffer. At the
same time, associate the original buffer with the original

If the new buffer has no associated layout, it is displayed as
the only window in the frame."
  (let ((*protect-buffer-layouts* t))
    (dolist (window (window-list))
      (setf (alist-get (window-buffer window) *buffer-layouts*)
    (call-interactively #'helm-multi-files)
    (let* ((buf (current-buffer))
	   (conf (alist-get buf *buffer-layouts*)))
      (when conf
	(set-window-configuration conf)
	(select-window (get-buffer-window buf))))))

(advice-add #'delete-other-windows :before
	    (lambda (&optional window)
	      (when (not *protect-buffer-layouts*)
		(dolist (window (window-list))
		  (setf (alist-get (window-buffer window) *buffer-layouts*) nil)))))
(advice-add #'delete-window :before
	    (lambda (&optional window)
	      (when (not window)
		(setq window (get-buffer-window)))
	      (when (not *protect-buffer-layouts*)
		(setf (alist-get (window-buffer window) *buffer-layouts*) nil))))
(advice-add #'quit-window :before
	    (lambda (&optional kill window)
	      (when (not window)
		(setq window (get-buffer-window)))
	      (when (not *protect-buffer-layouts*)
		(setf (alist-get (window-buffer window) *buffer-layouts*) nil))))

(set-keys "C-c b" switch-to-buffer-with-layout
	  "C-c n" restore-buffer-layout)

Basically, a global variable holds a list of all buffer–layout associations, which is updated and referred to whenever the switch-to-buffer-with-layout function is called. I’ve personally chosen to bind it to C-c b.

(Note that switch-to-buffer-with-layout manually calls helm-multi-files. If you don’t use Helm, you should replace this with switch-to-buffer.)

Here is a short demonstration of switching between a three-window file editing layout, a single-window EXWM layout and a two-window Wanderlust layout:

Video demonstration of switching between layouts by switching buffers.

As you can see, whenever I switch buffers (with C-c b), the window layout is updated too. The benefit with this approach is that you don’t need to learn and remember a separate system for keeping track of layouts. They’re automatically associated with the buffers, which you already know how to manage.

2 Responses to “Buffer-associated window layouts in Emacs”

  1. eric says:

    Note that Emacs comes with registers that allow you to do something similar out of the box: C-x r w B (where B is any letter) saves the current window configuration and C-x r j B (where B is the same letter) will restore that window configuration.

    What I don’t know is whether you can preload specific window configurations. I don’t think you can but nevertheless those two bindings are very helpful.

  2. eric replies to eric:

    Funnily enough, just after posting this, I found the announcement of bookmark-view, a new package that adds window configurations to things you can bookmark. Check it out on MELPA.

Leave a Reply