22.11 退出

在 Lisp 函数运行时键入 C-g 会使 Emacs 退出(quit) 当前正在执行的任何操作。这意味着控制权会返回到 最内层的活动命令循环。

在命令循环等待键盘输入时键入 C-g 不会触发退出;它会作为普通输入字符处理。 在最简单的情况下,你无法区分二者的差异,因为 C-g 通常会运行 keyboard-quit 命令,其作用就是退出。 但当 C-g 跟在前缀键之后时,二者会组合成一个未定义的按键。 其效果是取消前缀键以及任何前缀参数。

在迷你缓冲区中,C-g 有不同的定义:它会中止迷你缓冲区操作。 实际上,这意味着它会退出迷你缓冲区,然后执行退出操作。 (单纯的退出只会返回到迷你缓冲区 内部 的命令循环。) 命令读取器在读取输入时 C-g 不直接触发退出的原因, 是为了能以这种方式在迷你缓冲区中重新定义它的含义。 迷你缓冲区中不会重新定义跟在前缀键后的 C-g, 它仍会执行取消前缀键和前缀参数的常规操作。 如果 C-g 总是直接触发退出,这一行为也无法实现。

C-g 直接触发退出时,其原理是将变量 quit-flag 设置为 t。Emacs 会在合适的时机检查该变量, 若其值非 nil 则执行退出操作。因此,无论以何种方式将 quit-flag 设置为非 nil 值,都会触发退出。

在 C 代码层面,退出操作并非在任意位置都能触发;仅会在 检查 quit-flag 的特殊位置触发。这样设计的原因是, 在其他位置退出可能导致 Emacs 内部状态出现不一致。 由于退出操作会延迟到安全位置才执行,因此退出不会导致 Emacs 崩溃。

某些函数(如 read-key-sequenceread-quoted-char)即使在等待输入时,也会完全阻止退出操作。 此时 C-g 不会触发退出,而是作为请求的输入。 对于 read-key-sequence,这一设计是为了实现 命令循环中 C-g 的特殊行为。对于 read-quoted-char, 这一设计则是为了让 C-q 能够引用 C-g

你可以通过将变量 inhibit-quit 绑定为非 nil 值, 来阻止 Lisp 函数某一部分执行退出操作。此时, 尽管 C-g 仍会照常将 quit-flag 设置为 t, 但会阻止其常规结果—退出操作的执行。 最终,inhibit-quit 会恢复为 nil(例如在 let 形式结束时,其绑定被解除)。若此时 quit-flag 仍为非 nil,则会立即执行请求的退出操作。 当你希望确保程序的关键段不会触发退出时,这种行为非常理想。

在部分函数(如 read-quoted-char)中,C-g 会以不涉及退出的特殊方式处理。实现方式是:在绑定 inhibit-quitt 的状态下读取输入, 并在 inhibit-quit 恢复为 nil 之前将 quit-flag 设置为 nil。以下是 read-quoted-char 定义的节选,展示了具体实现方式; 同时也表明,输入第一个字符后允许执行常规退出操作。

(defun read-quoted-char (&optional prompt)
  "...documentation..."
  (let ((message-log-max nil) done (first t) (code 0) char)
    (while (not done)
      (let ((inhibit-quit first)
            ...)
        (and prompt (message "%s-" prompt))
        (setq char (read-event))
        (if inhibit-quit (setq quit-flag nil)))
      ...set the variable code...)
    code))
Variable: quit-flag

若该变量值非 nil,则 Emacs 会立即执行退出操作(除非 inhibit-quit 为非 nil)。无论 inhibit-quit 取值如何,键入 C-g 通常都会将 quit-flag 设置为非 nil

Variable: inhibit-quit

该变量决定了当 quit-flag 被设置为非 nil 值时, Emacs 是否应执行退出操作。若 inhibit-quit 为 非 nil,则 quit-flag 不会产生任何特殊效果。

Macro: with-local-quit body…

该宏会依次执行 body 形式,但即便在该结构外部 inhibit-quit 为非 nil,也允许在 body 内部(至少本地)执行退出操作。它会返回 body 中最后一个形式的值;若因退出而终止执行,则返回 nil

若进入 with-local-quitinhibit-quitnil,则仅执行 body,且设置 quit-flag 会触发 常规退出。但如果 inhibit-quit 为非 nil(导致常规退出被延迟), 非 nilquit-flag 会触发一种特殊的本地退出。 这会终止 body 的执行,并在 quit-flag 仍为非 nil 的状态下退出 with-local-quit 体, 因此一旦允许,就会立即执行另一次(常规)退出。 若在 body 开始时 quit-flag 已为非 nil, 则会立即触发本地退出,且完全不执行体内容。

该宏主要适用于从定时器、进程过滤器、进程哨兵、 pre-command-hookpost-command-hook 以及其他 inhibit-quit 通常被绑定为 t 的位置调用的函数。

Command: keyboard-quit

该函数会通过 (signal 'quit nil) 触发 quit 条件。 这与退出操作的效果完全相同。(参见 错误 中的 signal。)

若希望退出但不中止键盘宏的定义或执行,你可以触发 minibuffer-quit 条件。其效果与 quit 条件几乎完全相同, 区别在于命令循环中的错误处理会处理该条件,且不会退出键盘宏的定义或执行。

你可以指定除 C-g 之外的其他字符作为退出键。 参见 Input Modes 中的 set-input-mode 函数。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike