跳到主要内容

第10章 撤销

所有人都会犯各种各样的输入错误。因此对于任何一个现代的软件来说,撤销都是一个很基本的功能。 Vim 的撤销系统不仅支持撤销和取消撤销任何修改,而且支持存取不同的文本形态,让你能控制你输入的所有文本。在本章中,你将会学会如何执行撤销和 取消撤销文本,浏览撤销分支,反复撤销, 以及浏览改动时间线。

撤销(undo),重做和行撤销(UNDO)

对于一个基本的 undo 操作,你可以执行 u 或者 :undo

假设你有如下文本(注意"one"下面有一个空行):

one

然后添加另一个文本:

one
two

如果你执行 u,Vim 会删除 “two”。

Vim 是如何知道应该恢复多少修改呢? 答案是,Vim每次仅恢复一次修改,这有点类似于点命令的操作(和 点命令不同之处在于,命令行命令也会被算作一次修改)。

要取消上一次的撤销,可以执行 Ctrl-r 或者 :redo。例如上面的例子中,当你执行撤销来删除 “two” 以后,你可以执行 Ctrl-r 来恢复被删除掉的文本。

Vim 也有另一个命令 U 可以实现 行撤销 (UNDO) 的功能,执行这个命令会撤销所有最新的修改。

那么,Uu 的区别是什么呢?首先,U 会删除 最近修改的行中所有的 的修改,而 u 一次仅删除一次修改。 其次,执行u 不会被算作一次修改操作,而执行 U 则会被算作一次修改。

让我们会的之前的例子:

one
two

修改第二行的内容为 “three” (ciwthree<esc>):

one
three

再次修改第二行的例子为 “four” (ciwfour<esc>):

one
four

此时,如果你按下 u,你会看到 “three”。如果你再次按下 u,你会看到 “two”。然而,在第二行仍为 “four” 的时候,如果你按下 U,你会看到

one

执行 U 会跳过中间所有修改,直接恢复到文件最初的状态(第二行为空)。另外,由于 UNO 实际上是执行了一个新的修改,因此你可以 UNDO 执行过的 UNDO。 执行 U 后 再次执行 U 会撤销 自己。假如你连续执行 U,那么你将看到第二行的文本不停地出现和消失。

就我个人而言,我几乎不会使用 U,因为很难记住文本最初的样子。(我几乎不使用它)

Vim 可以通过变量 undolevels 来选择最多可执行 undo 的次数。你可以通过 :echo &undolevels 来查看当前的配置。我一般设置为 1000。如果你也想设置为 1000 的话,你可以执行 :set undolevels=1000。不用担心,你可以设置它为任何一个你想设置的值。

断点插入操作

在上文中我提到,u 每次恢复一个修改,类似于点命令。在每次进入 插入模式和退出插入模式之间的任何修改都被定义为一次修改。

如果你执行 ione two three<esc> 之后,按下 u,Vim 会同时删除 “one two three”,因为这是一笔修改。如果你每次只输入较短的文本,那这是可接受的;可假设你在一次插入模式中输入了大量的文本,而后退出了插入模式,可很快你意识到这中间有部分错误。此时,如果你按下 u,你会丢失上一次输入的所有内容。 因此,假设你按下 u 只删除你上一次输入的一部分文本岂不是会更好。

幸运的是,你可以拆分它。当你在插入模式时,按下 Ctrl-G u 会生成一个断点。例如,如果你执行 ione <Ctrl-G u>two <Ctrl-G u>three<esc>,之后你按下u,你仅会失去文本 “three”,再次执行 u,会删除 “two”。当你想要输入一长段内容时,应该有选择性地执行断点插入操作。在每一句话的末尾,两个段落的中间,或者每一行代码结束时插入断点是一个很好的选择,这可以帮助你快速从错误中恢复出来。

在插入模式中,执行删除操作时插入断点也非常有用。例如通过 Ctrl-W 删除光标前的单词时,以及 Ctrl-U删除光标前的所有文本时。一个朋友建议我使用如下的映射:

inoremap <c-u> <c-g>u<c-u>
inoremap <c-w> <c-g>u<c-w>

通过上述命令,你可以很轻松地恢复被删除的文本。

撤销树

Vim 将每一次修改存储在一个撤销树中。你打开一个空白文件,然后添加一段新文本:

one

再插入一段新文本:

one
two

undo一次:

one

插入一段不同的话:

one
three

再次 undo

one

再次插入另一段话:

one
four

现在如果你执行 undo,您将丢失刚刚添加的文本 "four" :

one

如果你再次执行 undo 操作:


文本 “one” 也会丢失。对于大部分编辑器来说,找回文本 “two” 和 “three” 都是不可能的事情,但是对于 Vim 来说却不是这样。执行 g+,你会得到:

one

再次执行 g+ ,你将会看到一位老朋友:

one
two

让我们继续执行 g+:

one
three

再一次执行 g+ :

one
four

在 Vim 中,你每一次执行 u 去做一次修改时,Vim都会通过创建一个"撤销分支"来保存之前的文本内容。在本例中,你输入"two"后, 执行 u,然后输入"three",你就创建了一个叶子分支,保存了含有"two"的文本状态。此时,撤销树已经包含了至少两个叶子节点,主节点包含文本"three"(最新),而另一undo分支节点包含文本“two”。假如你执行了另一次撤销操作并且输入了"four",那么此时会生成三个节点,一个主节点包含文本"four", 以及另外两个节点分别存储了"three"和"two"。

为了在几个不同的节点状态间进行切换,你可以执行 g+ 去获取一个较新的状态,以及执行 g- 去获取一个教旧的状态。 uCtrl-Rg+, 和 g- 之间的区别是,u and Ctrl-R 只可以在 main 节点之间进行切换,而g+g- 可以在 所有 节点之间进行切换。

Undo 树并不可以很轻松地可视化。我发现一个插件 vim-mundo 对于理解 undo 树很有帮助。花点时间去与它玩耍吧。

保存撤销记录

当你通过 Vim 打开一个文件,并且立即按下 u,Vim 很可能会显示 “Already at oldest change” 的警告。

要想从最近的一次编辑工作中(在vim关闭文件再打开,算做一次新的编辑工作),在撤销历史中回滚,可以通过 :wundo命令使Vim 保存一份你的 undo 历史记录。

创建一个文件 mynumbers.txt. 输入:

one

插入另一行文件 (确保你要么退出并重新进入插入模式,要么创建了断点):

one
two

插入新的一行:

one
two
three

现在,创建你的撤销记录文件。 语法为 :wundo myundofile。 如果你需要覆盖一个已存在的文件,在 wundo 之后添加 !.

:wundo! mynumbers.undo

退出 Vim。

此时,在目录下,应该有mynumbers.txtmynumbers.undo 两个文件。再次打开 mynumbers.txt 文件并且按下 u,这是没有响应的。因为自打开文件后,你没有执行任何的修改。现在,通过执行 :rundo 来加载 undo 历史。

:rundo mynumbers.undo

此时,如果你按下 u,Vim 会删除 “three”。再次按下 u可以删除 “two”。这就好像你从来没有关闭过 Vim 一样。

如果你想要自动加载 undo 历史文件,你可以通过在你的 .vimrc 文件中添加如下代码:

set undodir=~/.vim/undo_dir
set undofile

我认为将所有的 undo 文件集中保存在一个文件夹中最好,例如在 ~/.vim 目录下。 undo_dir 是随意的。 set undofile 告诉 Vim 打开 undofile 这个特性,因为该特性默认是关闭的。现在,无论你何时保存,Vim 都会自动创建和保存撤销的历史记录(在使用undo_dir目录前,请确保你已经创建了它)。

时间旅行

是谁说时间旅行不存在。 Vim 可以通过 :earlier 命令将文本恢复为之前的状态。

假如有如下文本:

one

之后你输入了另一行:

one
two

如果你输入 “two” 的时间少于10秒,那么你可以通过如下命令恢复到 “two” 还没被输入前的状态:

:earlier 10s

你可以使用 :undolist 去查看之前所做的修改。 :earlier 可以加上分钟 (m), 小时 (h), and 天 (d) 作为参数。

:earlier 10s    恢复到10秒前的状态
:earlier 10m 恢复到10分钟前的状态
:earlier 10h 恢复到10小时前的状态
:earlier 10d 恢复到10天前的状态

另外,它同样接受一个计数作为参数,告诉vim恢复到老状态的次数。比如,如果运行:earlier 2,Vim将恢复到2次修改前的状态。功能类似于执行g-两次。同样,你可以运行:earlier 10f命令告诉vim恢复到10次保存前的状态。

这些参数同样作用于:earlier命令的对应版本::later

:later 10s    恢复到10秒后的状态
:later 10m 恢复到10分钟后的状
:later 10h 恢复到10小时后的状
:later 10d 恢复到10天后的状态
:later 10 恢复到新状态10次
:later 10f 恢复到10次保存后的状态

聪明地学习撤销操作

uCtrl-R 是两个不可缺少的 Vim 参数。请先学会它们。在我的工作流中,我并不使用 UNDO,然而我认为承认它存在是很好的。下一步,学会如何使用:earlier:later,以及时间参数。在这之后,请花些时间理解 undo 树。 插件 vim-mundo 对我的帮助很大。单独输入本章中展示的文本,并且查看撤销树的每一次改变。一旦你掌握它,你看待撤销系统的眼光一定不同。

在本章之前,你学习了如何在项目内查找任何文本,配合撤销,你可以在时间维度上查找任何一个文本。你现在可以通过位置和写入时间找到任何一个你想找的文本。你已经对 Vim 无所不能了。