33.9 撤销

大多数缓冲区都拥有一个撤销列表(undo list),用于记录对缓冲区文本所做的所有修改,以便将这些修改撤销。 (不具备撤销列表的缓冲区通常是专用缓冲区,Emacs 认为对其执行撤销操作没有意义。 特别地,任何名称以空格开头的缓冲区默认关闭撤销记录;详见 缓冲区名称。) 所有修改缓冲区文本的基本函数都会自动向变量 buffer-undo-list 所保存的撤销列表首部添加元素。

Variable: buffer-undo-list

这个缓冲区局部变量的值为当前缓冲区的撤销列表。若值为 t,则禁止记录撤销信息。

撤销列表可以包含以下类型的元素:

position

此类元素记录光标之前的位置;撤销该元素会将光标移动至 position。 普通的光标移动不会生成任何撤销记录,但删除操作会使用此类条目记录命令执行前的光标位置。

(beg . end)

此类元素说明如何删除已插入的文本。插入时,该文本占据缓冲区中从 begend 的范围。

(text . position)

此类元素说明如何重新插入已删除的文本。被删除的文本本身为字符串 text, 重新插入的位置为 (abs position)。若 position 为正数, 表示删除前光标位于文本开头;否则位于文本末尾。该元素后会紧跟零个或多个 (marker . adjustment) 元素。

(t . time-flag)

此类元素表示一个未修改状态的缓冲区变为已修改状态。 若 time-flag 为非整数型 Lisp 时间戳,则代表上次访问或保存时所访问文件的修改时间, 格式与 current-time 相同;详见 时刻。 若 time-flag 为 0,表示缓冲区不对应任何文件;−1 表示所访问的文件此前不存在。 primitive-undo 使用这些值判断是否要再次将缓冲区标记为未修改状态; 仅当文件状态与 time-flag 匹配时才会执行此操作。

(nil property value beg . end)

此类元素记录文本属性的变化。可通过如下方式撤销该修改:

(put-text-property beg end property value)
(marker . adjustment)

此类元素记录标记 marker 因周围文本被删除而发生了重定位, 并移动了 adjustment 个字符位置。若标记位置与撤销列表中位于其前方的 (text . position) 元素一致,则撤销该元素会将 marker 反向移动 − adjustment 个字符。

(apply funname . args)

这是可扩展的撤销项,通过以参数 args 调用 funname 完成撤销。

(apply delta beg end funname . args)

这是可扩展的撤销项,记录了作用于 begend 范围、使缓冲区大小增加 delta 个字符的修改。 通过以参数 args 调用 funname 完成撤销。

此类元素允许针对指定区域的撤销操作判断该元素是否属于该区域。

nil

该元素为边界标记。两个边界之间的元素称为一个修改组(change group); 通常每个修改组对应一次键盘命令,撤销命令会将整个修改组作为一个单元进行撤销。

Function: undo-boundary

该函数在撤销列表中插入一个边界元素。撤销命令会在该边界处停止, 连续执行撤销命令会依次撤销更早的边界之前的操作。本函数返回 nil

显式调用该函数可将一条命令的效果拆分为多个撤销单元。 例如,query-replace 在每次替换后调用 undo-boundary, 使用户可以逐个撤销单次替换操作。

不过多数情况下,该函数会在合适的时机被自动调用。

Function: undo-auto-amalgamate

编辑器命令循环会在执行每个按键序列前自动调用 undo-boundary, 因此每次撤销通常会撤销一条命令的效果。少数特殊命令属于可合并(amalgamating)类型: 这类命令通常只对缓冲区做微小修改,因此仅每执行 20 条此类命令才插入一个边界, 允许多次修改合并撤销。默认情况下,self-insert-command(用于输入字符,see 用户级插入命令) 和 delete-char(用于删除字符,see 删除文本)属于可合并命令。 若一条命令影响多个缓冲区的内容(例如 post-command-hook 中的函数影响非当前缓冲区时), 则会在所有受影响的缓冲区中调用 undo-boundary

可在执行可合并命令前调用该函数。若已连续执行多次此类调用,该函数会移除上一个 undo-boundary

可合并修改的最大次数由变量 amalgamating-undo-limit 控制。 若该变量为 1,则不合并任何修改。

Lisp 程序可通过调用 undo-amalgamate-change-group 将一系列修改合并为单个修改组(see 原子变更组)。 注意 amalgamating-undo-limit 对该函数生成的修改组无效。

Variable: undo-auto-current-boundary-timer

部分缓冲区(如进程缓冲区)即使在未执行命令时也可能发生变化。 这类情况下,undo-boundary 通常由该变量中的定时器定期调用。 将该变量设为非空值可禁止此行为。

Variable: undo-in-progress

该变量通常为 nil,但撤销命令会将其绑定为 t。 各类修改钩子可借此判断自身是否因撤销操作而被调用。

Function: primitive-undo count list

这是撤销撤销列表元素的基础函数。它撤销 list 中前 count 个元素,并返回列表剩余部分。

primitive-undo 在修改缓冲区时会向缓冲区撤销列表添加元素。 撤销命令会在一系列撤销操作开始时保存撤销列表的值以避免混乱, 后续撤销操作使用并更新该保存值。撤销操作新增的元素不属于该保存值,因此不会干扰后续撤销。

该函数不会绑定 undo-in-progress

Macro: with-undo-amalgamate body…

该宏会移除执行 body 期间插入的所有撤销边界,使整体可作为一步撤销。

部分命令执行后会保持选区激活状态,从而干扰该命令的选择性撤销。 若要让 undo 在该命令后立即执行时忽略激活选区, 可将该命令函数符号的 undo-inhibit-region 属性设为非空值。See 标准符号属性