我写了一个Emacs Lisp函数,它调用了一个shell命令来处理给定的字符串并返回生成的字符串。 这是一个简化的示例,仅调用tr
将文本转换为大写:
(defun test-shell-command (str)
"Apply tr to STR to convert lowercase letters to uppercase."
(let ((buffer (generate-new-buffer "*temp*")))
(with-current-buffer buffer
(insert str)
(call-process-region (point-min) (point-max) "tr" t t nil "'a-z'" "'A-Z'")
(buffer-string))))
此函数创建一个临时缓冲区,插入文本,调用 tr
,将文本替换为结果,并返回结果。
上面的函数按预期工作,但是,当我编写包装器时围绕此函数将命令应用于区域,有两个步骤是被添加到撤消历史记录中。 这是另一个示例:
(defun test-shell-command-region (begin end)
"Apply tr to region from BEGIN to END."
(interactive "*r")
(insert (test-shell-command (delete-and-extract-region begin end))))
当我调用M-x test-shell-command-on-region
时,该区域被替换使用大写文本,但是当我按 C-_
( undo
( 时,第一个撤消历史记录中的步骤是已删除文本的状态。 会后退两步,恢复原文。
我的问题是,如何防止中间步骤出现添加到撤消历史记录? 我已经阅读了关于撤消的Emacs文档,但据我所知,它似乎并没有解决这个问题。
这是一个函数,它通过调用内置的Emacs函数upcase
,就像以前一样:在结果上 delete-and-extract-region
结果被移交给 insert
:
(defun test-upcase-region (begin end)
"Apply upcase to region from BEGIN to END."
(interactive "*r")
(insert (upcase (delete-and-extract-region begin end))))
调用M-x test-upcase-region
时,只有一个步骤在按预期撤消历史记录。 所以,似乎情况是打电话 test-shell-command
创建撤消边界。 这是可以避免的吗不知何故?
键是缓冲区名称。 请参阅维护撤消:
在新创建的缓冲区中记录撤消信息通常从 开始;但如果缓冲区名称以空格开头,则最初禁用撤消记录。您可以使用以下两个功能显式启用或禁用撤消记录,也可以通过自己设置缓冲区撤消列表来显式启用或禁用撤消记录。
with-temp-buffer
创建一个名为 ␣*temp*
的缓冲区(请注意前导空格(,而您的函数使用 *temp*
。
若要删除代码中的撤消边界,请使用带有前导空格的缓冲区名称,或使用 buffer-disable-undo
显式禁用临时缓冲区中的撤消重新编码。
但一般来说,使用with-temp-buffer
,真的。 这是 Emacs 中处理此类操作的标准方式,让任何阅读您的代码的人都清楚您的意图。 此外,with-temp-buffer
会努力正确清理临时缓冲区。
至于为什么在临时缓冲区中撤消会在
当前缓冲区中创建撤消边界:如果之前的更改是可撤消的,并且在其他缓冲区(本例中为临时缓冲区(中进行,则会创建一个隐式边界。 从undo-boundary
:
因此,在每当在某个其他缓冲区中进行先前可撤消的更改时,所有缓冲区修改都会添加边界。这是为了确保每个命令在进行更改的每个缓冲区中建立边界。
临时缓冲区中禁止撤消也会删除当前缓冲区中的撤消边界:以前的更改根本无法再撤消,因此不会创建隐式边界。
在许多情况下,使用临时缓冲区是不切实际的。例如,很难调试正在发生的事情。
在这些情况下,你可以让绑定undo-inhibit-record-point
来防止 Emacs 决定在哪里放置边界:
(let ((undo-inhibit-record-point t))
;; Then record boundaries manually
(undo-boundary)
(do-lots-of-stuff)
(undo-boundary))
在这种情况下,解决方案是创建临时输出缓冲区使用 with-temp-buffer
,而不是显式创建一个 generate-new-buffer
. 以下替代版本的第一个函数不会创建撤消边界:
(defun test-shell-command (str)
"Apply tr to STR to convert lowercase letters to uppercase."
(with-temp-buffer
(insert str)
(call-process-region (point-min) (point-max) "tr" t t nil "'a-z'" "'A-Z'")
(buffer-string)))
我无法确定generate-new-buffer
是否确实是创建撤消边界,但这解决了问题。 generate-new-buffer
调用 get-buffer-create
,定义见C 源代码,但我无法快速确定是什么在撤消历史记录方面发生。
我怀疑这个问题可能与以下段落有关Emacs Lisp 手册条目用于undo-boundary
:
所有缓冲区修改都会在前一个时添加边界 在其他一些缓冲区中进行了可撤消的更改。 这是为了确保 每个命令在它使 变化。
即使with-temp-buffer
宏调用generate-new-buffer
就像在原始函数中一样,文档 with-temp-buffer
声明不会保存撤消信息(即使尽管在 Emacs Lisp 源代码中没有任何内容表明这一点会是这样(:
默认情况下,撤消(请参阅撤消(不会记录在由 创建的缓冲区中 此宏(但如果需要,正文可以启用它(。