如何在Emacs中进行“撤销”后重新执行更改?

303

这篇文章说:"Emacs 有 redo 功能,因为你可以在撤销的同时反向操作,从而撤销撤销。"

这是什么意思?用户如何在 Emacs 中执行“redo”操作?


4
如果您正在查看此答案,那么接受的答案下面的答案会告诉您如何做到这一点,这也是大多数人实际上正在寻找的。鉴于它的写作方式,我认为这就是您寻找的原因。 - Russia Must Remove Putin
我只是按了空格键,然后撤销了这个空格,再重做你所做的撤销,直到它到达末尾,然后再开始往回走...我知道有更复杂的方法,但这种方式快速简便,而且这就是我的风格。 - nroose
2
Emacs 28 现在支持此功能,详情请参见:https://dev59.com/UHA75IYBdhLWcg3wAD_x#60163018 - ideasman42
11个回答

324

简短版:通过撤销撤销来实现。如果您进行了撤销操作,并执行了一个非编辑命令,比如C-f,那么下一个撤销将撤销上一个撤销,从而实现重做。

详细版:

您可以将撤销操作视为在操作堆栈上运行。如果您在一系列撤销操作之后执行某个命令(甚至是诸如C-f的导航命令),则所有撤销操作都会被推送到操作堆栈上。因此,下一个撤销将撤销最后执行的命令。假设您有以下操作序列:

  1. 插入 "foo"
  2. 插入 "bar"
  3. 插入 "I love spam"

现在,您撤销了上一个操作。它会撤销最后一个操作,从而使操作列表变成:

  1. 插入 "foo"
  2. 插入 "bar"

如果此时您执行的不是撤销操作,而是例如C-f,则操作堆栈将如下所示:

  1. 插入 "foo"
  2. 插入 "bar"
  3. 插入 "I love spam"
  4. 撤销插入 "I love spam"

此时,当您撤销操作时,第一件被取消的是撤销操作本身。结果,您的原始操作堆栈(以及文档状态)恢复了:

  1. 插入 "foo"
  2. 插入 "bar"
  3. 插入 "I love spam"

如果您执行修改命令来打破撤销序列,则该命令将添加到撤销操作之后,因此它将是随后要撤销的第一件事情。假设您删除了"bar"而不是按下C-f,那么您将拥有:

  1. 插入 "foo"
  2. 插入 "bar"
  3. 插入 "I love spam"
  4. 撤销插入 "I love spam"
  5. 删除 "bar"

这种添加/重新添加的过程会一直持续下去。需要花些时间适应,但它确实为Emacs提供了高度灵活和强大的撤销/重做机制。


133
万一这听起来很烦人,主要优点是您始终可以恢复到以前的状态。在大多数编辑器中,撤消几个更改然后意外输入一个字符会使您“陷入困境”,无法重做您已经撤消的内容。Emacs使此变得轻而易举。 - phils
3
我猜在你的设计中,如果一个人想要在纸上完成它,你需要两堆。因为例如,在你写“现在,你撤销。它会撤销最后一次操作,导致以下列表:”的那个点上(可以理解),如果他已经做了很多次撤销,你不会显示它们,直到有些东西打破了撤销序列,然后你才会添加它们。另一种展示它的方法是只有一个列表,并且即使在它们被打破之前就将撤销添加到列表中,但要注意光标在列表中的位置,这样我们就知道从哪里开始倒退执行撤销。 - barlop
1
@barlop 晚回复了,但至少从用户可见的角度来看,在序列被打破后才会将撤消操作放入堆栈中(否则,“撤消”重复执行将只切换到上一个动作)。这可能不是实现方式(例如,它可以使用某种指向队列的指针之类的东西),但我在这里试图解释用户可见的行为。 - Michael Ekstrand
21
不知道为什么这个答案被采纳了,因为它甚至没有告诉你如何做。 - Russia Must Remove Putin
18
遗憾的是,这个答案没有涉及到重做命令,即C-g C-/。 - vishless
显示剩余5条评论

239

撤销操作:C-_

在撤销后恢复操作:C-gC-_

多次按下C-_键来恢复被C-_键撤销的内容。 要多次执行emacs命令,请先执行您的命令,然后输入C-xz,接着多次按下z键来重复该命令(当您想要多次执行宏时很有用)。


8
为了重复一些内容,您也可以使用 C-u,例如 C-u 8 *(在插入符号所在位置插入八个 *)。因此,您可以使用 C-u x C-_ 撤消 x 步操作。 - Patrick
11
这与原问题无关,但在执行键盘宏后(使用C-x e),您可以按e键多次以再次执行该宏。 - Christian Berg
7
此外,C-/C-x u 是替代的撤销快捷键,与 C-_ 等效。我更喜欢使用 C-/,因为这更容易操作。 - Rory O'Kane
12
警告: 当本答案说 C-g C-_ 将会 "在撤销后重做" 时,这仅适用于您第一次按下该组合键的情况。之后,只需重复您的撤销键而不需要 C-g。因此,要在撤销后连续重做三次,请键入 C-g C-_ C-_ C-_。如果您键入 C-g C-_ C-g C-_ C-g C-_,则将陷入死循环,即撤消您对撤销的重做... - Rory O'Kane
无关紧要的是,在 Android 平台上,标准的撤消按键(C-_, C-/)都无法使用:控制似乎被剥离了。 - Michael
显示剩余4条评论

75
  • 撤销一次: C-/
  • 撤销两次: C-/ C-/
  • 在撤销后立即恢复一次: C-g C-/
  • 在撤销后立即恢复两次: C-g C-/ C-/。注意,C-g 不需要重复。
  • 立即再次撤销一次: C-g C-/
  • 立即再次撤销两次: C-g C-/ C-/
  • 再次恢复相同的操作...
如果您在上一次撤销命令之后按下任何键(无论是输入字符还是移动光标),则在下一次撤销/重做之前无需输入C-gC-g只是一个安全的按键,它本身不起作用,但被计算为非撤销键以表示撤销序列的结束。按下其他命令,如C-f也可以;只是它会将光标从你放置的位置移动。
如果您在不想的情况下击中了C-g或其他命令,并且现在正在错误方向上撤销,请再次按下C-g以反转您的方向。您必须通过意外的重做和撤消全部撤消,然后才能到达您想要的撤消状态,但如果您继续按下C-/,最终将到达所需状态。实际上,缓冲区曾经存在的每个状态都可以通过按一次C-g,然后按足够多次C-/来到达。

除了C-/之外,撤销操作的其他备选快捷键包括C-_C-x uM-x undo

有关Emacs撤销系统的更多细节,请参阅Emacs手册中的Undo


44

如果你想要更常见的撤销/恢复功能,有人编写了undo-tree.el。它提供了非Emacs中撤销的外观和感觉,但提供了对整个撤销历史'树'的访问。

我喜欢Emacs内置的撤销系统,但发现这个包非常直观。

以下是文件本身的评论:

Emacs有强大的撤销系统。 与大多数软件中标准的撤销/重做系统不同, 它允许您恢复缓冲区的任何过去状态 (而标准的撤销/重做系统可以在您 重做时丢失过去状态)。然而,这种力量 代价是: 许多人发现Emacs的撤销 系统令人困惑和难以使用,导致一些软件包 将其替换为没有这么强大但更直观的撤销/重做系统。

标准撤销/重做中数据的损失, 以及Emacs的撤销中的混乱,都源于 尝试将撤销历史视为线性序列的更改。 它不是线性的。这个包提供的 `undo-tree-mode' 将Emacs的撤销系统 替换为将撤销历史记录视为分支树形式的系统。 这个简单的想法允许标准撤销/重做系统 更直观的行为与永远不会丢失任何历史的强大相结合。 另一个附加优势是,撤销历史在某些情况下 可以存储更高效,允许Emacs在开始丢弃历史之前累积更多的更改。


6
太棒了!谁说Emacs的撤销功能难以理解? :-) - Peter.O
太棒了!概念上合理且用户友好。这应该是默认包中的一部分。一个评论:我希望在从分支点重做时也能显示一些警报消息,而不仅仅是在恢复到它时。例如,当用户调用重做时:“从分支点重做”或“使用默认重做分支” - 只是提醒用户存在多个分支。 - Basel Shishani
1
另一种解决这个问题的方法是更简单的实现,不需要可视化工具,只需保留最后一个分支点,并提供3个命令:撤销、重做、切换分支。当到达分支点时,撤销/重做将发出警报,而切换分支将在分支处切换,否则只会发出“未到达分支”消息。如果在任何时候发生分支,则会丢失先前的分支点。这将以最小的复杂度合理地解决状态丢失问题。 - Basel Shishani
1
请注意,这个问题在重做时存在长期的 bug,可能会随机失败:https://github.com/syl20bnr/spacemacs/issues/9903 - 自 2014 年以来已知。 - ideasman42
@ideasman42 看起来这是一个“非错误”:http://www.dr-qubit.org/Lost_undo-tree_history.html - Dalker
显示剩余3条评论

26

警惕一个对于redoundo-tree 的怪癖!

许多受欢迎的“入门套件”(prelude、purcell、spacemacs)都预装了undo-tree。大多数甚至自动启用它。如前所述,undo-tree是一种方便的方式来可视化和遍历撤消/重做树。Prelude甚至给它提供了一个键触组合(uu),同时还有C-x u

问题是:undo-tree似乎破坏了Emacs默认的且广为人知的绑定方式redo: C-g C-/

相反,你可以使用这些对称的按键进行撤销/重做操作

C-/     undo
C-S-/   redo

这些很有用,因为有时候你想要快速地重新执行而不需要打开可视化工具。


这个。我正在使用Spacemacs,但是我无法弄清楚为什么它对我不起作用。谢谢! - mghaoui

14

Emacs 28 增加了一个重做 命令(称为undo-redo)。

如果您想使用更典型的撤销/重做功能,可以使用以下命令。

(global-set-key (kbd "C-z") 'undo-only)
(global-set-key (kbd "C-S-z") 'undo-redo)

要访问此功能(同时具有使用非线性历史记录的能力,参见undo-fu,它是emacs内置撤消功能的薄包装器)。


11

我发现redo.el非常方便,可以用于执行“常规”撤销/重做操作。我通常将其绑定到C-S-z上,并将撤销操作绑定为C-z,就像这样:

(when (require 'redo nil 'noerror)
    (global-set-key (kbd "C-S-z") 'redo))

(global-set-key (kbd "C-z") 'undo)

只需下载文件,将其放入您的Lisp路径中,并将上述内容粘贴到您的.emacs中即可。


redo.el头部的注释说它是为XEmacs制作的。在GNU Emacs和其他相关的Emacsen中使用它会有任何问题吗? - A. Levy
我到目前为止还没有遇到任何问题,而且我已经使用它至少6个月了 :) - monotux
5
然而,redo.el 已经过时,并且经常破坏“缓冲区”的内容,因为自 Emacs 22 开始,‘primitive-undo’ 的行为与旧版本有些不同。RedoPlus 是 redo.el 的一个分支,在较新版本的 Emacs 中能够正确地工作。你可以在这里获取 RedoPlus: http://www.emacswiki.org/emacs/redo+.el - rofrol
我认为,默认情况下,它现在绑定到 M-_ - supercheetah

4

默认情况下,Emacs 中的重做操作需要按下 C-g,然后再执行撤消操作。

但是使用 Emacs 内置的撤消功能也可以实现重做命令。

undo-fu 使用 Emacs 内置的撤消功能来同时提供撤消和重做。


编辑:现在,undo-fu 已经集成到了 evil-mode 中(如果您是evil-mode用户)。

示例:

(use-package undo-fu)
(use-package evil
  :init
  (setq evil-undo-system 'undo-fu))

3
Doom Emacs的用户,希望您已经滚到了这里或者在页面上搜索了“doom” ... ...
Doom Emacs破坏了vanilla Emacs的重做快捷方式:C-g C-/ C-/ C-/等(或C-g C- _ C- _ C- _等)... ...而是只会不断地撤消。
此外,Doom Emacs也破坏了另一个答案中提到的对于spacemacs等有用的undo-tree重做快捷方式:S-C-/(又称C-?)... ...而且会抛出错误“没有定义C-?”。
你需要的是:
- 进入evil-mode(使用C-z在evil-mode之间切换)(在evil-mode下应该看到蓝色光标,而不是橙色光标),并且 - 进入"命令模式" AKA "正常模式"(而不是"插入模式")(使用Esc切换到命令模式)(应该看到块状光标,而不是行状光标),然后就是 - 使用u进行撤销 - 使用C-r进行重做

对于非邪恶模式用户,请查找交互函数“undo-fu-only-redo”。 - ning

2
如果你想重复上一个操作,请按照以下步骤进行:
  1. 按下ESC
  2. 执行撤销操作(C + /
“最初的回答”

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接