如何防止Emacs设置撤消边界?

如何防止Emacs设置撤消边界?,emacs,elisp,undo,Emacs,Elisp,Undo,我已经编写了一个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

我已经编写了一个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命令时
,该区域将被替换 使用大写文本,但当我按
C-
undo
)时,第一个 撤消历史记录中的步骤是文本已删除的状态。去 后退两步,将恢复原始文本

我的问题是,如何防止中间步骤被忽略 是否添加到撤消历史记录?我读过, 但据我所知,它似乎没有解决这个问题

下面是一个函数,它通过调用 内置Emacs函数
upcase
,与以前一样:基于
删除并提取区域
,并将结果交给
插入

(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测试用例区域
时,在 按预期撤消历史记录。所以,似乎是这样的情况
testshell命令
创建撤消边界。这能避免吗
不知怎的?

本例中的解决方案是创建临时输出缓冲区 使用带有临时缓冲区的
,而不是使用
生成新缓冲区
。以下是 第一个函数不创建撤消边界:

(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)))
我无法确定是否确实需要
生成新缓冲区
创建撤消边界,但这解决了问题。
生成新缓冲区
调用
获取缓冲区创建
,在中定义 C源代码,但我无法快速确定是什么 就历史而言发生的

我怀疑这个问题可能与下面一段有关 委员会:

所有缓冲区修改都会在上一次修改时添加边界 在其他缓冲区中进行了可撤消的更改。这是为了确保 每个命令在其生成的每个缓冲区中生成一个边界 变化

即使带临时缓冲区的
宏调用
生成新缓冲区
与原始函数一样,用于 带有临时缓冲区的
表示不保存撤消信息(即使
尽管Emacs Lisp源代码中没有任何内容表明这一点
情况会是这样的):

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


关键是缓冲区名称。见:

在新创建的缓冲区中记录撤销信息通常从开始启用;但如果缓冲区名称以空格开头,则撤消录制最初将被禁用。您可以使用以下两个函数显式启用或禁用撤消录制,也可以自己设置缓冲区撤消列表

使用临时缓冲区
创建名为
␣*temp*
(注意前导空格),而函数使用的是
*temp*

要删除代码中的撤消边界,请使用带前导空格的缓冲区名称,或使用
buffer disable undo
在临时缓冲区中显式禁用撤消重新编码

但是一般来说,使用带有临时缓冲区的
,真的。这是Emacs中处理此类事情的标准方法,让任何阅读您代码的人都能清楚地了解您的意图。另外,
带有临时缓冲区
会努力正确清理临时缓冲区


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

所有缓冲区修改在某些其他缓冲区中进行上一次可撤消更改时添加边界。这是为了确保每个命令在其进行更改的每个缓冲区中创建一个边界


因此,禁止在临时缓冲区中撤消也会删除当前缓冲区中的撤消边界:以前的更改不再是可撤消的,因此不会创建隐式边界。

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

在这些情况下,您可以让bind
undo禁止记录点
,以防止Emacs决定在何处放置边界:

(let ((undo-inhibit-record-point t))
  ;; Then record boundaries manually
  (undo-boundary)
  (do-lots-of-stuff)
  (undo-boundary))

防止命令弄乱
(undo)
的通常方法是找到一种不同的方法,一种不这样做的方法。这里的例子是,您几乎不应该创建临时缓冲区,而应该使用对象。除了手动读取和写入临时文件之外,我不确定是否还有其他方法可以捕获流程输出。甚至像
start process
这样的异步进程命令似乎也希望将输出发送到缓冲区。@JasonBlevins区域上
shell命令的第五个参数是REPLACE,那么为什么需要包装它呢?@event\u jr好的一点:我这样写是为了遵循“不要重复自己”的原则,为了使一个函数返回字符串,另一个函数在区域上运行,但不重复程序名和参数。当然,有多种方法可以避免重复,您的评论指出了构建它的其他好方法。我验证了在原始函数中使用
(generate new buffer“*temp*”)
,在