33.34 变更钩子

这些钩子变量可以让你监听缓冲区(或特定缓冲区,若将其设为缓冲区局部变量)中的文本变更。 如需检测特定文本区域的变更,可参见 具有特殊含义的文本属性

这些钩子中使用的函数如果执行了涉及正则表达式的操作,必须保存并恢复匹配数据; 否则会以异常方式干扰调用它们的编辑操作。此外,钩子函数应避免修改缓冲区文本、 字体、属性、覆盖层以及其他缓冲区特有状态,除非这些修改是钩子函数自身创建并管理的, 否则 Emacs 的其他部分可能会因这些背后的变更而出现异常。

Variable: before-change-functions

该变量保存一组函数,Emacs 即将修改缓冲区时会调用这些函数。 每个函数接收两个参数:即将变更区域的起始位置与结束位置,均为整数。 函数被调用时,即将变更的缓冲区始终为当前缓冲区。

Variable: after-change-functions

该变量保存一组函数,Emacs 修改缓冲区后会调用这些函数。 每个函数接收三个参数:刚变更区域的起始位置、结束位置,以及变更前该区域文本的长度。 三个参数均为整数。函数被调用时,已变更的缓冲区始终为当前缓冲区。

旧文本的长度是变更前该文本在缓冲区中起始与结束位置的差值。 而变更后文本的长度,直接是前两个参数的差值。

*Messages* 缓冲区输出消息不会触发这些函数, 部分内部缓冲区变更也不会触发,例如 Emacs 为内部任务创建的缓冲区变更, 这类变更对 Lisp 程序不可见。

绝大多数缓冲区修改原语都会成对、平衡地调用 before-change-functionsafter-change-functions, 每次变更调用一次,且钩子参数能精确界定所做的变更。 但钩子函数不应依赖这一行为,因为部分复杂原语会在修改前仅调用一次 before-change-functions,随后根据实际修改次数调用零次或多次 after-change-functions。这种情况下,before-change-functions 的参数会包含所有实际变更所在的区域,但未必是最小区域; 而每次后续调用 after-change-functions 的参数会精确界定对应变更的文本区域。 通常建议只使用变更前或变更后钩子,而非同时使用两者。

Macro: combine-after-change-calls body…

该宏正常执行 body,但会在安全的前提下, 将一系列连续变更的变更后钩子合并为一次调用。

若程序在缓冲区同一区域执行多次文本修改, 在该段代码外层使用 combine-after-change-calls 宏, 可在启用变更后钩子时显著提升运行效率。 变更后钩子最终被调用时,其参数会覆盖 combine-after-change-calls 函数体内所有修改所在的缓冲区范围。

警告: 不得在 combine-after-change-calls 代码块内 修改 after-change-functions 的值。

警告: 若合并的变更分布在缓冲区中相距较远的位置,该宏仍可工作, 但不建议这样做,因为可能导致部分变更钩子函数执行效率低下。

Macro: combine-change-calls beg end body…

该宏正常执行 body,但其中的缓冲区修改不会触发 before-change-functionsafter-change-functions。 取而代之的是,针对 begend 包围的区域, 两类钩子各被调用一次,其中 after-change-functions 的参数 会反映 body 对该区域长度造成的修改。

该宏的返回值为 body 的执行结果。

当某个函数需要对缓冲区执行大量重复修改, 而逐次触发变更钩子会导致运行缓慢时,该宏十分有用。 Emacs 自身也会使用该宏,例如 comment-regionuncomment-region 命令。

警告: 不得在 body 内修改 before-change-functionsafter-change-function 的值。

警告: 不得在 begend 指定的区域之外 执行任何缓冲区修改。

Variable: first-change-hook

该变量是一个普通钩子,当原本处于未修改状态的缓冲区发生首次变更时会运行。

Variable: inhibit-modification-hooks

若该变量为非 nil,则所有变更钩子均被禁用,不会运行。 这会影响本节所述的所有钩子变量,以及绑定到特定特殊文本属性(see 具有特殊含义的文本属性) 与覆盖层属性(see 覆盖层属性)的钩子。

此外,运行上述钩子时,该变量会被绑定为非 nil, 因此默认情况下,在修改钩子内部修改缓冲区不会触发其他修改钩子。 若你希望在修改钩子执行的代码中再次触发修改钩子, 可将 inhibit-modification-hooks 局部重新绑定为 nil。 但这样做可能导致修改钩子递归调用,需提前做好处理(例如绑定某个变量使钩子不执行操作)。

我们建议仅在不会对缓冲区文本内容造成持久修改的场景下 临时绑定该变量(例如修改字体或临时修改)。 若需要在一系列修改期间延迟触发变更钩子(通常出于性能考虑), 请使用 combine-change-callscombine-after-change-calls