(or emacs irrelevant)

Bookmark the current window layout with Ivy

Today's post is about the newest feature related to the ivy-switch-buffer command. If you use ivy-mode, you're probably already using ivy-switch-buffer since it overwrites the built-in switch-to-buffer.

The cool thing about ivy-switch-buffer is that it's not only buffers that are offered for completion. Other buffer-like entities can be there as well: bookmarks, recently opened files (recentf), and finally, window layouts. Since all of those are relatively the same concept, it's very convenient to have them all in one place available for completion.

Here are the relevant settings:

;; Enable bookmarks and recentf
(setq ivy-use-virtual-buffers t)

;; Example setting for ivy-views
(setq ivy-views
      `(("dutch + notes {}"
         (vert
          (file "dutch.org")
          (buffer "notes")))
        ("ivy.el {}"
         (horz
          (file ,(find-library-name "ivy"))
          (buffer "*scratch*")))))

I did mention ivy-views before in the ivy-0.8.0 release post. But now, instead of setting ivy-views by hand, you can also bind ivy-push-view to a key and store as many window configurations as you like, really fast.

What gets stored:

  • The window list - all windows open on the current frame.
  • The window splits relative to each other as a tree. Currently, the size of the split isn't saved, all windows are split equally.
  • The point positions in each window. If you use just one window, you've got something similar to bookmark-set.

Here's what I use currently:

(global-set-key (kbd "C-c v") 'ivy-push-view)
(global-set-key (kbd "C-c V") 'ivy-pop-view)

Typical workflow

Suppose I have two files open: the file 2016-06-23-ivy-push-view.md and the _posts directory. By pressing C-c v I am prompted for a view name with the default being e.g. {} 2016-06-23-ivy-push-view.md _posts 2.

I can still name the view however I want, but I typically just press RET. The parts of the automatic view name are:

  • {} - this is a simple string marker to distinguish the views in the buffer view. If I enter only {} into ivy-switch-buffer prompt, the candidates will normally filter to only views, since very rarely will a file or a buffer name match {}.
  • 2016-06-23-ivy-push-view.md _posts is the list of buffers stored in the view. This view has only two buffers, but ivy-push-view can handle as many windows as you can cram into a single frame.
  • 2 means that I already have two views with the same buffers, each new view with the same buffers gets an increased number for the suggested name. And it's not useless to have many views for the same buffers, since the views also store point positions, not just the window list.

Here's the beauty of it for me: when I type _posts into ivy-switch-buffer I can chose to open the _posts directory in a variety of ways:

  • If the buffer is currently open, I can just switch there.
  • If the buffer is currently closed, I can re-open it, thanks to recentf.
  • I can open the buffer as part of a stored view(s) in ivy-views.

Finally, if I decide that I don't need a particular view any more, I can delete it with C-c V (ivy-pop-view). It's possible to delete many views at once by pressing C-M-m (ivy-call), as usual with most ivy completion functions.

Breaking API change

While implementing ivy-set-view I decided that the current way alist collections are handled together with actions is sub-optimal. Here's the new way of working:

(let (res)
  (ivy-with
   '(ivy-read "test: "
     '(("one" . 1) ("three" . 3))
     :action (lambda (x) (setq res x)))
   "t C-m")
  res)
;; =>
;; ("three" . 3)

Previously, the return result would be 3, i.e. the cdr of the selected candidate. Any code using ivy-read with an alist-type collection will break. I fixed all instances in counsel.el, and there actually aren't too many uses in the published third party packages.

A simple fix to the problem is to use cdr in the action function. Additionally, having more information available in the action function will serve to improve the code.