New Ivy multi-action exit
23 Jul 2015So 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:
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.