A Hydra for ivy/swiper
26 Mar 2015Today I'll share a Hydra that I've been working on that's similar to Lit Wakefield's original idea for a helm hydra.
The Code
(defhydra hydra-ivy (:hint nil
:color pink)
"
^^^^^^ ^Actions^ ^Dired^ ^Quit^
^^^^^^--------------------------------------------
^ ^ _k_ ^ ^ _._ repeat _m_ark _i_: cancel
_h_ ^✜^ _l_ _r_eplace _,_ unmark _o_: quit
^ ^ _j_ ^ ^ _u_ndo
"
;; arrows
("h" ivy-beginning-of-buffer)
("j" ivy-next-line)
("k" ivy-previous-line)
("l" ivy-end-of-buffer)
;; actions
("." hydra-repeat)
("r" ivy-replace)
("u" ivy-undo)
;; dired
("m" ivy-dired-mark)
("," ivy-dired-unmark)
;; exit
("o" keyboard-escape-quit :exit t)
("i" nil))
Here's how I bind it:
(define-key ivy-minibuffer-map (kbd "C-o") 'hydra-ivy/body)
And here are the auxiliaries:
(defun ivy-dired-mark (arg)
(interactive "p")
(dotimes (_i arg)
(with-ivy-window
(dired-mark 1))
(ivy-next-line 1)
(ivy--exhibit)))
(defun ivy-dired-unmark (arg)
(interactive "p")
(dotimes (_i arg)
(with-ivy-window
(dired-unmark 1))
(ivy-next-line 1)
(ivy--exhibit)))
(defun ivy-replace ()
(interactive)
(let ((from (with-ivy-window
(move-beginning-of-line nil)
(when (re-search-forward
(ivy--regex ivy-text) (line-end-position) t)
(match-string 0)))))
(if (null from)
(user-error "No match")
(let ((rep (read-string (format "Replace [%s] with: " from))))
(with-selected-window swiper--window
(undo-boundary)
(replace-match rep t t))))))
(defun ivy-undo ()
(interactive)
(with-ivy-window
(undo)))
The dired operations
There's actually an outstanding issue to make the hydra
heads appear conditionally. This would be quite useful for the m and , bindings, since
they don't work outside a dired
buffer. Maybe I'll get to it on the weekend.
Meanwhile, here's a screenshot for marking files in dired
using swiper:
Since the input is "mar 10"
, swiper
transforms it into the regex "(mar).*(10)"
.
What I do next:
- C-o to get into the hydra state.
- 99m to mark everything. Normally, it would mark 99 candidates, but since there are only 17, that means all of them.
- h to go to the first candidate.
- j, to to skip one candidate and unmark, then unmark some more, using this method.
If I wanted to move two candidates down at once, I could press 2j...... The . will repeat the previous command with the previous argument. You can also set the argument later, e.g. j.2.3.. etc.
The exit points
There are two:
- i will bring you back to
ivy
, so that you can edit the input. - o will quit everything and bring you to the
dired
buffer.
So you could first mark Mar 10
, exit with i, edit the input to Mar 17
, press
C-o and mark some more. Then finally exit with o.
The replace and undo operations
These two I've added the latest, so they are still a bit off. The ivy-replace
option is similar
to vim's r (I did vimtutor
yesterday). It lets you replace the selected candidate. And
u simply calls undo. Strangely, at the moment it will undo several ivy-replace
operations at once, even though I call undo-boundary
in ivy-replace
.
Outro
I think ivy
and the hydra
docstring blend in together quite nicely, like
old dogs and new tricks. I don't know which is which.