(or emacs irrelevant)

New Ivy multi-action exit

So many Emacs commands and bindings, but little time to learn them all. That's why many-in-one solutions like hydra are often a good deal: you only remember the base binding, and get more info along the way if you get lost.

I've wanted to optimize this approach when it comes to completion as well. In last month's post I described how you can get help / jump to definition / jump to info for the current F1 v (counsel-describe-variable) candidate with C-m / C-. / C-,. While that approach can be viable, it has a few problems. The first problem is that it's not discoverable: you may be using C-m option for ages and not know that C-. and C-, exist. The second problem is that it's not extensible: if you wanted more actions, there are hardly any good bindings left in the minibuffer to bind those actions.

In a more recent post, I described a solution that alleviates both problems. When you press C-o, you see how many actions are available, and you can cycle them with w and s. However, while discoverable, this approach is cumbersome if you already know what you want to do. Especially cycling can't be great when there are a lot of actions.

So today I've added a new approach in addition to the previous one. When you press M-o (ivy-dispatching-done), you get a hint showing you which actions are available. It's like C-m (ivy-done), only allows you to quickly select the action. Conveniently, the default action is bound to o, so M-o o is equivalent to C-m.

Here's how I add one more action to the default one of ivy-switch-buffer:

(ivy-set-actions
 'ivy-switch-buffer
 '(("k"
    (lambda (x)
      (kill-buffer x)
      (ivy--reset-state ivy-last))
    "kill")))

So now:

  • M-o o will still switch to selected buffer.
  • M-o k will kill the selected buffer.

If you're familiar with hydra, the format is the same: (key cmd hint). This approach is very extensible: you can add actions and bindings to commands without changing the code of the original command. It's really fast: you're typing just one extra key to select an action. And you'll hardly run out of keys to bind, with 25 lower case keys at your disposal (other bindings work as well, I just think that lower case keys are the fastest to press).

In case you wonder what (ivy--reset-state ivy-last) does, it's used to update the list of buffers, since one of them was deleted. This way, you can delete e.g. 4 buffers with C-x b C-o s gggg, and have the buffer list update after each g.

New functionality in action

Let's look at one package that has a multitude of actions available for each candidate - projectile:

(defvar helm-projectile-projects-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map helm-map)
    (helm-projectile-define-key map
        (kbd "C-d") #'dired
        (kbd "M-g") #'helm-projectile-vc
        (kbd "M-e") #'helm-projectile-switch-to-eshell
        (kbd "C-s") #'helm-find-files-grep
        (kbd "M-c") #'helm-projectile-compile-project
        (kbd "M-t") #'helm-projectile-test-project
        (kbd "M-r") #'helm-projectile-run-project
        (kbd "M-D") #'helm-projectile-remove-known-project)
    map)
  "Mapping for known projectile projects.")

Here's the basic Ivy function for selecting a project:

(defun ivy-switch-project ()
  (interactive)
  (ivy-read
   "Switch to project: "
   (if (projectile-project-p)
       (cons (abbreviate-file-name (projectile-project-root))
             (projectile-relevant-known-projects))
     projectile-known-projects)
   :action #'projectile-switch-project-by-name))
(global-set-key (kbd "C-c m") 'ivy-switch-project)

And now let's add all those actions:

(ivy-set-actions
 'ivy-switch-project
 '(("d" dired "Open Dired in project's directory")
   ("v" helm-projectile-vc "Open project root in vc-dir or magit")
   ("e" helm-projectile-switch-to-eshell "Switch to Eshell")
   ("g"
    (lambda (x)
      (helm-do-grep-1 (list x)))
    "Grep in projects")
   ("c" helm-projectile-compile-project "Compile project")
   ("r" helm-projectile-remove-known-project "Remove project(s)")))

Here's what I get now after pressing M-o:

ivy-multiaction.png

Looks pretty good, I think. I hope you find the new approach useful. The multi-action exit is currently enabled by default for ivy-switch-buffer, counsel-locate and counsel-rhythmbox, but you can add actions yourself to almost any command that uses ivy-read. In the rare case when ivy-read isn't in the tail position, you can use ivy-quit-and-run inside the added action functions.