2017-06-12 smart-next-window-or-buffer

Some time ago, I wrote about an Emacs command I started to use to switch between windows (if there is more than one of them) or buffers (otherwise). While it has proven very useful, since then I started to use multiple frames. It turned out that my command wasn’t all that useful then: it couldn’t switch to a buffer in another frame.

I then decided to bind f6 to next-multiframe-window from windmove.el, and it worked better. However, there was still one gotcha: sometimes I wanted it to switch to another window in the same frame, and it switched to another frame instead.

Well, how to teach Emacs what I really wanted? First I had to formalize it myself. Turns out, it was pretty simple. What I wanted was this:

Quite unexpectedly, the task of coding that was more difficult than I thought. The reason for that turned out to be the fact that other-buffer behaves strangely when the same buffer is visible in more than one frame (by default, it avoids such buffers). Of course, this being Emacs, by giving suitable parameters this behavior could be overridden. See the source and the relevant docstrings for details.

(defvar snwob-starting-window-or-buffer nil)

(defun snwob-single-buffer-p ()
  "Return non-nil if the current frame has one buffer only."
  (null (cdr (buffer-list (selected-frame)))))

(defun snwob-single-frame-p ()
  "Return non-nil if the selected frame is the only one."
  (null (cdr (frame-list))))

(defun snwob--current-window-or-buffer ()
  "Return the current buffer if there is a single window.
Otherwise, return the selected window."
  (if (one-window-p)
	  (current-buffer)
	(selected-window)))

(defun snwob--other-frame-or-window-or-buffer ()
  "Switch to the other frame if there is more than one.
Otherwise, call `snwob--other-window-or-buffer'."
  (if (snwob-single-frame-p)
	  (snwob--other-window-or-buffer)
	(other-frame 1)
	(setq this-command #'other-frame)))

(defun snwob--other-window-or-buffer ()
  "Switch to another window if there is one.
Otherwise, switch to the other buffer."
  (cond ((one-window-p)
	 (switch-to-buffer (other-buffer (current-buffer) t (selected-frame))))
	(t
	 (other-window 1))))

(defun smart-next-window-or-buffer ()
  "Switch to the other buffer if there is one window only.
Otherwise, switch to another window.  After a full cycle of two
buffers (or as many windows as there are in the selected frame)
switch to another frame."
  (interactive)
  (cond ((eq last-command #'smart-next-window-or-buffer)
	 (if (eq snwob-starting-window-or-buffer (snwob--current-window-or-buffer))
		 (snwob--other-frame-or-window-or-buffer)
	   (snwob--other-window-or-buffer)))
	(t
	 (setq snwob-starting-window-or-buffer
		   (snwob--current-window-or-buffer))
	 (snwob--other-window-or-buffer))))

(global-set-key (kbd "<f6>") #'smart-next-window-or-buffer)

I have changed f6 to this command in my init.el and I’ll see whether it’s going to stick.

CategoryEnglish, CategoryBlog, CategoryEmacs