10

我编写了一个 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) 时,撤消历史记录中的第一步是删除文本的状态。后退两步,恢复原文本。

我的问题是,如何防止将中间步骤添加到撤消历史记录中?我已经阅读了有关 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创建撤消边界的情况。可以以某种方式避免吗?

4

3 回答 3

7

关键是缓冲区名称。请参阅维护撤消

在新创建的缓冲区中记录撤消信息通常可以开始;但如果缓冲区名称以空格开头,撤消记录最初是禁用的。您可以使用以下两个函数显式启用或禁用撤消记录,或者自己设置 buffer-undo-list。

with-temp-buffer创建一个名为␣*temp*(注意前导空格)的缓冲区,而您的函数使用*temp*.

要删除代码中的撤消边界,请使用带有前导空格的缓冲区名称,或者使用buffer-disable-undo.

但一般来说,使用with-temp-buffer,真的。这是 Emacs 中此类事情的标准方式,让任何阅读您的代码的人都清楚您的意图。此外,with-temp-buffer努力正确清理临时缓冲区。


至于为什么临时缓冲区中的撤消会在当前缓冲区中创建撤消边界:如果先前的更改是可撤消的并且是在其他缓冲区(本例中为临时缓冲区)中进行的,则会创建隐式边界。来自undo-boundary

每当在其他缓冲区中进行先前的可撤消更改时,所有缓冲区修改都会添加一个边界。这是为了确保每个命令在它进行更改的每个缓冲区中都有一个边界。

因此,在临时缓冲区中禁止撤消也将删除当前缓冲区中的撤消边界:先前的更改根本无法再撤消,因此不会创建隐式边界。

于 2013-02-27T11:36:31.160 回答
4

在许多情况下使用临时缓冲区是不切实际的。例如,很难调试正在发生的事情。

在这些情况下,你可以 let-bindundo-inhibit-record-point阻止 Emacs 决定在哪里放置边界:

(let ((undo-inhibit-record-point t))
  ;; Then record boundaries manually
  (undo-boundary)
  (do-lots-of-stuff)
  (undo-boundary))
于 2015-09-10T08:57:05.500 回答
2

在这种情况下,解决方案是使用创建临时输出缓冲区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-buffergenerate-new-buffer 在原始函数中调用得很多,但文档 with-temp-buffer声明没有保存撤消信息(即使 Emacs Lisp 源代码中没有任何内容表明会出现这种情况):

默认情况下,撤消(请参阅撤消)不会记录在此宏创建的缓冲区中(但如果需要,主体可以启用它)。

于 2013-02-26T19:52:49.947 回答