第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) 的功能,执行这个命令会撤销所有最新的修改。
那么,U
和 u
的区别是什么呢?首先,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-
去获取一个教旧的状态。 u
, Ctrl-R
, g+
, 和 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.txt
和 mynumbers.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次保存后的状态
聪明地学习撤销操作
u
和 Ctrl-R
是两个不可缺少的 Vim 参数。请先学会它们。在我的工作流中,我并不使用 UNDO,然而我认为承认它存在是很好的。下一步,学会如何使用:earlier
和 :later
,以及时间参数。在这之后,请花些时间理解 undo 树。 插件 vim-mundo 对我的帮助很大。单独输入本章中展示的文本,并且查看撤销树的每一次改变。一旦你掌握它,你看待撤销系统的眼光一定不同。
在本章之前,你学习了如何在项目内查找任何文本,配合撤销,你可以在时间维度上查找任何一个文本。你现在可以通过位置和写入时间找到任何一个你想找的文本。你已经对 Vim 无所不能了。