Quitting to command loop in Elisp16 Jul 2015
Today I'd like to share an interesting bit of Elisp that comes up every now and then for me. Imagine that you have this:
(defun useful-command () (interactive) (do-thing-1) (do-thing-2 (funcall callback-function)) (do-thing-3))
Sometimes, when you are in
callback-function, you might want to
abandon the function that called you,
useful-command in this case,
and call a different function, with the current context.
Here's what I've come up with to do just that:
(defmacro ivy-quit-and-run (&rest body) "Quit the minibuffer and run BODY afterwards." `(progn (put 'quit 'error-message "") (run-at-time nil nil (lambda () (put 'quit 'error-message "Quit") ,@body)) (minibuffer-keyboard-quit)))
To break it up into parts:
minibuffer-keyboard-quitwill unwind the call stack all the way to the command loop, preventing e.g.
do-thing-3from being called. Note that the call stack can be as deep as you like, e.g.
useful-commandmight be called by
run-at-timewith the argument
nilwill run the code as soon as possible, which is almost exactly after we're back into the command loop.
The final trick is to prevent
Quitfrom being displayed in the minibuffer.
Suppose that I've called
active. Typically, I'd select a file and press C-m to open
it. However, sometimes I just want to see the selected file in
instead of opening it. This code is from before
ivy multi-action interface,
it plainly binds a command in
(define-key ivy-minibuffer-map (kbd "C-:") 'ivy-dired) (defun ivy-dired () (interactive) (if ivy--directory (ivy-quit-and-run (dired ivy--directory) (when (re-search-forward (regexp-quote (substring ivy--current 0 -1)) nil t) (goto-char (match-beginning 0)))) (user-error "Not completing files currently")))
So at the moment when C-: is pressed, the call stack is:
- C-x C-f called
completion-read-functionwhich is set to
Thanks to the new macro, I can unwind all of that stuff, making sure
nothing extra will be executed that was supposed to be executed after
read-from-minibuffer had returned. In other words, the file will not
be opened. Instead, a
dired buffer will be opened, centered on the
What I described above can actually be a pretty common scenario, you
could adapt it to
projectile. Basically to
anything that includes some form of completion (i.e. commands that
wait for input) and offers you a customizable keymap. Small
disclaimer: the above code falls into the quick-and-dirty category, I
don't recommend it if you can do something smarter instead. But if you
can't do anything smarter due to being constrained by an interface you
don't control, this macro could help you out.