在 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-sequence 或
read-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-quit 为 t 的状态下读取输入,
并在 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))
若该变量值非 nil,则 Emacs 会立即执行退出操作(除非
inhibit-quit 为非 nil)。无论
inhibit-quit 取值如何,键入 C-g 通常都会将
quit-flag 设置为非 nil。
该变量决定了当 quit-flag 被设置为非 nil 值时,
Emacs 是否应执行退出操作。若 inhibit-quit 为
非 nil,则 quit-flag 不会产生任何特殊效果。
该宏会依次执行 body 形式,但即便在该结构外部
inhibit-quit 为非 nil,也允许在
body 内部(至少本地)执行退出操作。它会返回
body 中最后一个形式的值;若因退出而终止执行,则返回
nil。
若进入 with-local-quit 时 inhibit-quit 为
nil,则仅执行 body,且设置 quit-flag 会触发
常规退出。但如果 inhibit-quit 为非 nil(导致常规退出被延迟),
非 nil 的 quit-flag 会触发一种特殊的本地退出。
这会终止 body 的执行,并在 quit-flag 仍为非
nil 的状态下退出 with-local-quit 体,
因此一旦允许,就会立即执行另一次(常规)退出。
若在 body 开始时 quit-flag 已为非 nil,
则会立即触发本地退出,且完全不执行体内容。
该宏主要适用于从定时器、进程过滤器、进程哨兵、
pre-command-hook、post-command-hook
以及其他 inhibit-quit 通常被绑定为 t 的位置调用的函数。
若希望退出但不中止键盘宏的定义或执行,你可以触发
minibuffer-quit 条件。其效果与 quit 条件几乎完全相同,
区别在于命令循环中的错误处理会处理该条件,且不会退出键盘宏的定义或执行。
你可以指定除 C-g 之外的其他字符作为退出键。
参见 Input Modes 中的 set-input-mode 函数。