在数据库术语中,原子(atomic) 变更是不可分割的变更—要么完全成功,要么完全失败, 不会出现部分成功的情况。Lisp 程序可以将对一个或多个缓冲区的一系列变更, 作为一个 原子变更组(atomic change group) 执行,即要么整套变更全部应用到缓冲区, 要么在出错时全部不生效。
若只对当前缓冲区执行此操作,只需在执行变更的代码外层包裹
atomic-change-group 调用即可,示例如下:
(atomic-change-group (insert foo) (delete-region x y))
如果在 atomic-change-group 函数体内部发生错误(或其他非本地退出),
该函数会撤销函数体执行期间对该缓冲区所做的所有变更。
此类变更组对其他缓冲区无影响—这些缓冲区的变更会保留。
若需要更复杂的操作,例如让多个缓冲区的变更属于同一个原子组,
则需要直接调用 atomic-change-group 所使用的底层函数。
该函数为缓冲区 buffer 创建一个变更组,buffer 默认为当前缓冲区。 它返回一个代表该变更组的句柄。你必须使用此句柄来激活变更组,并在之后完成它。
要使用变更组,必须先对其进行 激活(activate),且必须在修改 buffer 文本之前完成激活。
该函数激活由句柄 handle 标识的变更组。
激活变更组后,你对该缓冲区所做的任何修改都会归入该组。 在缓冲区中完成所有期望的修改后,必须 完成(finish) 该变更组。 有两种方式:接受(并最终确定)所有变更,或全部取消。
该函数接受由句柄 handle 指定的变更组中的所有变更,并使其最终生效。
该函数取消并撤销由句柄 handle 指定的变更组中的所有变更。
你可以使用 undo-amalgamate-change-group,
将变更组中的部分或全部变更合并为 undo 命令的单个操作单元(see 撤销)。
将自 handle 标识状态以来,变更组中所做的所有变更合并。
该函数会移除 handle 所描述状态之后所有变更的撤销记录之间的撤销边界。
通常 handle 是 prepare-change-group 返回的句柄,
此时变更组开始以来的所有变更会被合并为单个撤销单元。
代码中应使用 unwind-protect 确保变更组始终被正确完成。
activate-change-group 调用应放在 unwind-protect 内部,
防止用户在其执行后立即按下 C-g。
(这也是 prepare-change-group 与 activate-change-group
分离为两个函数的原因之一,因为通常你会在 unwind-protect 开始前
调用 prepare-change-group。)
变更组完成后,请勿再次使用该句柄——尤其不要尝试两次完成同一个组。
要创建多缓冲区变更组,需对每个目标缓冲区分别调用一次 prepare-change-group,
然后使用 nconc 合并返回值,示例如下:
(nconc (prepare-change-group buffer-1)
(prepare-change-group buffer-2))
之后只需调用一次 activate-change-group 即可激活多缓冲区变更组,
并通过单次调用 accept-change-group 或 cancel-change-group 完成操作。
对同一缓冲区嵌套使用多个变更组可按预期正常工作。 对同一缓冲区非嵌套地使用变更组会使 Emacs 出现混乱,因此应避免; 对任一缓冲区启动的第一个变更组,应是最后一个被完成的。
Emacs 跟踪变更组的方式是:沿着 buffer-undo-list 逐个遍历 cdr,
最终应能找到调用 prepare-change-group 时设置的 cons 单元。
如果 buffer-undo-list 中不再包含该 cons 单元,
Emacs 会丢失对变更组的跟踪,导致取消变更组时报错。
为避免此问题,在变更组激活期间,不要调用任何可能修改撤销列表的函数,
尤其是像 delete-char 这类会调用 undo-auto-amalgamate 的 “合并(amalgamating)” 类命令。