今天我想要分享一段有趣的Elisp代码. 想象一下这么一段代码:
(defun useful-command ()
(interactive)
(do-thing-1)
(do-thing-2 (funcall callback-function))
(do-thing-3))
有时候会有这种需求, 你在执行 callback-function
的时间点上,想跳出正在执行的这个函数(本例中指的是那个 useful-command
),在保持当前环境的情况下转而调用另一个函数.
我是这样来实现这个需求的:
(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)))
让我们仔细分析一下:
minibuffer-keyboard-quit
会展开调用栈,并直接退出到command loop,从而阻止了do-thing-3
的执行. 这里的调用栈可以任意深,比如useful-command
可能又被utility-command
所调用,诸如此类.- 带nil参数的
run-at-time
会尽可能快的运行后面的代码,几乎就在我们回到command loop的那一刻就会运行. - 最后要留意的就是不要在minibuffer上显示Quit信息.
假设我在ivy-mode激活的情况下调用了 find-file
函数. 一般情况下, 我会选择一个文件,然后按下 C-m
打开这个文件. 然而, 有时候我只是想用 dired
看一下这个文件而不是想打开这个文件.
下面这段代码来至于ivy multi-action interface, 它是 ivy-minibuffer-map
中绑定的一个命令:
(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")))
那么,在 C-:
被按下的那一刻,调用栈是这样子的: is pressed, the call stack is:
C-x C-f
调用find-file
函数.find-file
调用completion-read-function
函数,不过实际上调用的是ivy-completing-read
函数.ivy-completing-read
调用了ivy-read
函数.ivy-read
调用了read-from-minibuffer
.
宏 ivy-quit-and-run
让我能够展开所有的调用栈,确保从 read-from-minibuffer
退出后,不会再运行剩下的代码.
换句话说,Emacs不会打开该文件,反而会打开一个dired buffer,并选中这个文件.
我上面所描述的场景应该蛮普遍的, 你可以用类似的方法改造 helm
, avy
,以及 projectile
. 基本上,所有包含某种形式的补全(例如那些会等待输入的命令)并提供可定制keymap的东西,都能够应用这种方法.
美中不足的是: 这种方法只是一种 quick-and-dirty 的解决方案. 如果可以的话,我并不推荐使用这种方法. 不过如果你别无他法,那么这个宏也许能够帮到你.