EMACS-DOCUMENT

=============>集思广益

在Elisp中退出当前调用栈并执行另一个函数的方法

Table of Contents

今天我想要分享一段有趣的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信息.

1 Sample application

假设我在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 的解决方案. 如果可以的话,我并不推荐使用这种方法. 不过如果你别无他法,那么这个宏也许能够帮到你.