undo

undo.txt 适用于 Vim 9.0 版本。 最近更新: 2022年7月 VIM 参考手册 by Bram Moolenaar 译者: jwdu、tocer 撤销和重做 undo-redo 在用户手册的 02.5 节对它们的基本操作有解释。 1. 撤销和重做操作的命令 undo-commands 2. 撤销操作的两种方法 undo-two-ways 3. 撤销块 undo-blocks 4. 撤销分支 undo-branches 5. 撤销的持久性 undo-persistence 6. 撤销操作的解释 undo-remarks

1. 撤销和重做命令 undo-commands

<Undo> undo <Undo> u u 撤销 [count] 次更改。 :u :un :undo :u[ndo] 撤销一次更改。 E830 :u[ndo] {N} 转到改变号 {N} 之后。{N} 的含义参见 undo-branches CTRL-R CTRL-R 重做 [count] 次被撤销的更改。 :red :redo redo :red[o] 重做一个被撤销的更改。 U U 撤销最近对特定行所作的一系列更改,也就是最近作改变的 那行。 U 自己也算一次改变,因此 U 会撤销之前的 U 。 由于最近所作的更改是被记住的,因此,你可以应用上面的撤销和重做命令把文件倒转到 你所作每次更改以前的状态。当然,你也可以重新应用这些修改,把文件重做到执行撤销 操作以前的状态。 对于撤销/重做 (undo/redo) 来说,"U" 命令和其他命令是同等对待的。所以 "u" 可以 撤销一个 "U" 命令、'CTRL-R' 也可以重做对 "U" 命令的撤销。当 "U"、"u" 和 'CTRL-R' 三个命令混合使用时,你将注意到 "U" 命令决意将被该行还原到前一个 "U" 命令之前的状态。这或许会令你困惑。多试试,你会习惯的。 "U" 命令总是把缓冲区标记为已改变。即使它把修改过的缓冲区重做到未改变的状态也是 如此。为此,只有使用 "u" 命令,才能令缓冲区正确地回复到未改变的标记。

2. 撤销操作的两种方法 undo-two-ways

撤销和重做的工作模式依赖于 'cpoptions' 中 'u' 标志位。这里有两种模式,Vim 模式 (不包含 'u') 和 Vi-兼容模式 (包含 'u')。在 Vim 模式下,"uu" 命令撤销两次改变, 在 Vi-兼容模式下,"uu" 命令什么也不做 (撤销第一次撤销)。 不包含 'u' 的 Vim 模式: 及时的撤销命令可以回到先前的状态。你也可以使用重做命令再次前进。不过,如果在撤 销命令后你做了一个新的改变,重做上次的撤销便不再可能。 包含 'u' 的 Vi-兼容模式: 撤销命令撤销包含以前的撤销命令在内的任何更改。重做命令重复前一个撤销命令。但它 用来重复改变命令。为此请用 "." 命令。 举例 Vim 模式 Vi-兼容模式 "uu" 两次撤销 空操作 "u CTRL-R" 空操作 两次撤销 原理: Nvi 使用 "." 命令而不是 CTRL-R。不幸的是,这不是 Vi 兼容的。例如 "dwdwu." 命令,在 Vi 中删除两个词,但在 Nvi 中,它什么也不做。

3. 撤销块 undo-blocks

单个 undo 命令通常撤掉一个输入的命令,不论这个命令造成多少改变。这个可以撤销的 改变序列构成了一个撤销块。所以如果键入的命令调用一个函数,那么在这个函数中的所 有命令全部被撤销。 如果你想编写一个函数或脚本,它不包含新的可撤销之改变,而合并到之前的改变中,用 以下命令: :undoj :undojoin E790 :undoj[oin] 把其后的改变和以前的撤销块进行合并。 警告: 小心使用。它会阻止用户合理地撤销改变。不要在撤销 或重做后使用。 这最适用于在改变的过程中需要提示用户的场合。例如调用 getchar() 的函数。确保 在这之前有一个相关的必须与之合并的改变。 这条命令不能单独工作,因为下一个键击会再次造成新改变。但你可以这么做: :undojoin | delete 在此之后,"u" 命令会同时撤销 delete 命令和前一次改变。 undo-break 要反过来,把一个改动分成两个撤销块,在插入模式下用 CTRL-G u。可用于使某个插入 命令部分可撤销。譬如可分拆到每个句子。 i_CTRL-G_u 设置 'undolevels' 的值也会打断撤销。即使新值和旧值相同。 Vim9 脚本里: &undolevels = &undolevels 老式脚本里: let &undolevels = &undolevels

4. 撤销分支 undo-branches undo-tree

上面我们只讨论了单线的撤销/重做。但你也可以进行分支。考虑你先撤销了若干改变, 然后又进行了一些其它的改变。此时,被撤销的改变就成为一个分支。下面的命令使你能 够到达那个分支。 这在用户手册中有解释: usr_32.txt :undol :undolist :undol[ist] 列出改变树的所有叶结点。例如: number changes when saved 88 88 2010/01/04 14:25:53 108 107 08/07 12:47:51 136 46 13:33:01 7 166 164 3 seconds ago "number" 列是改变号。这个编号持续增加,用于标识特定可 撤销的改变,参见 :undo 。 "changes" 列是树的根结点到此叶结点所需的改变数目。 "when" 列是此改变发生的日期时间。四种可能的格式是: N seconds ago HH:MM:SS 时分秒 MM/DD HH:MM:SS 同上,还有月日 YYYY/MM/DD HH:MM:SS 同上,还有年 "save" 列给出此改变是否已写入硬盘和第几次写入文件。可 用于 :later:earlier 命令。 要更详细的信息可用 undotree() 函数。 g- g- 转到较早的文本状态。如果带计数,重复那么多次。 :ea :earlier :earlier {count} 转到 {count} 次较早的文本状态。 :earlier {N}s 转到大约 {N} 秒钟之前的较早的文本状态。 :earlier {N}m 转到大约 {N} 分钟之前的较早的文本状态。 :earlier {N}h 转到大约 {N} 小时之前的较早的文本状态。 :earlier {N}d 转到大约 {N} 天之前的较早的文本状态。 :earlier {N}f 转到 {N} 次文件写入之前的较早的文本状态。 如果上次写入之后有改动,":earlier 1f" 会恢复文本到上次 写入时的状态。否则会转到再上一次写入时的状态。 如果在第一次文件写入的状态,甚至于从未写入过文件, ":earlier 1f" 会转到首次改变之前的状态。 g+ g+ 转到较新的文本状态。如果带计数,重复那么多次。 :lat :later :later {count} 转到 {count} 次较新的文本状态。 :later {N}s 转到大约 {N} 秒钟之后的较新的文本状态。 :later {N}m 转到大约 {N} 分钟之后的较新的文本状态。 :later {N}h 转到大约 {N} 小时之后的较新的文本状态。 :later {N}d 转到大约 {N} 天之后的较新的文本状态。 :later {N}f 转到 {N} 次文件写入之后较新的文本状态。 若在在最后一次文件写入的状态,":later 1f" 会转到最新的 文本状态。 注意 如果由于 'undolevels' 选项,撤销信息被清空,那么文本状态将无法访问。 在不同时间点上移动的时候,不要奇怪一个时间点会同时发生多个改变。通过撤销树跳转 然后又做了新的改变后就会发生这种情况。 示 例 从这一行开始: one two three 按三次 "x" 删除第一个单词: ne two three e two three two three 现在按 "u" 三次撤销: e two three ne two three one two three 按三次 "x" 删除第二个单词: one wo three one o three one three 现在按 "g-" 三次撤销: one o three one wo three two three 现在,回到第一个撤销分支,也就是在删除 "one" 之后。重复 "g-" 会回到原始文本: e two three ne two three one two three 使用 ":later 1h" 跳到最后一次改变: one three 使用 ":earlier 1h" 再次回到开始: one two three 注意 使用 "u" 和 CTRL-R 无法得到全部可能的文本状态,但是重复 "g-" 和 "g+" 却可 以。

5. 撤销的持久性 undo-persistence persistent-undo

卸载缓冲区时,Vim 通常会删除该缓冲区建立的撤销树。通过设置 'undofile' 选项, Vim 会在写入文件时自动保存撤销历史,而重新编辑文件时,恢复撤销历史。 'undofile' 选项在写入文件之后检查,而发生在 BufWritePost 自动命令之前。要控制 哪些文件需要保存撤销信息,可以使用 BufWritePre 自动命令: au BufWritePre /tmp/* setlocal noundofile Vim 把撤销树保存在一个独立的撤销文件里,每个编辑的文件对应一个,使用一个简单的 方案来对应文件系统的路径到撤销文件名。Vim 会检测是否某个撤销文件不再和写它时的 那个文件同步 (使用文件内容的哈希值),如果文件内容在撤销文件写入后有改动,忽略 撤销文件,以防止文件遭破坏。如果撤销文件的拥有者和正在编辑文件的不同,也忽略 之,除非撤销文件的拥有者是当前用户。设置 'verbose' 可获取关于打开文件的消息。 撤销文件通常保存在文件本身相同的目录里。这可以用 'undodir' 选项改变。 如果文件被加密,撤销文件的文件也会加密。使用相同的密钥和方法。 encryption 备注 撤销文件不保存文本属性。只要缓冲区载入了就可以恢复文本属性,但你不能从撤 销文件中恢复。理据: 这需要和之前以完全相同的方式定义相关联的文本属性类型,而这 不能保证。 你也可以用 ":wundo" 和 ":rundo" 来相应地保存和恢复撤销历史: :wundo :rundo :wundo[!] {file} 把撤销历史写入 {file}。 如果 {file} 已存在而看起来不像撤销文件 (文件头部的魔术数字不 符),此命令失败。除非加上 !。 如果文件存在且看起来像撤销文件,覆盖之。如果没有撤销历史,不 写入任何东西。 实现细节: 覆盖操作如此执行,先删除已有的文件,然后建立同名的 文件。所以,不能在写保护目录里覆盖已有的撤销文件。 :rundo {file}{file} 读出撤销历史。 你可以在自动命令中用这些命令来显式指定历史文件名。例如: au BufReadPost * call ReadUndo() au BufWritePost * call WriteUndo() func ReadUndo() if filereadable(expand('%:h') .. '/UNDO/' .. expand('%:t')) rundo %:h/UNDO/%:t endif endfunc func WriteUndo() let dirname = expand('%:h') .. '/UNDO' if !isdirectory(dirname) call mkdir(dirname) endif wundo %:h/UNDO/%:t endfunc 此时,应该关闭 'undofile',否则每次写入文件时会有两个撤销文件。 可以用 undofile() 函数来确定 Vim 会使用的文件名。 注意: 读写文件时,如果设置了 'undofile',大部分的错误信息会被屏蔽,除非设置了 'verbose'。而 :wundo 和 :rundo 会得到更到的错误信息,如文件不能读或写等。 注意: Vim 从不删除撤销文件。你需要自己进行清除。 读出已经存在的撤销文件可能会有以下的失败原因: E822 不能打开,文件许可权限不许可。 E823 文件头的魔术数字不符。通常这意味着这不是一个撤销文件。 E824 撤销文件的版本号说明它是由更新的 Vim 版本写入的。需要更新的版本来读。 如果你想保留该文件中的撤销信息,不要写入缓冲区。 "File contents changed, cannot use undo info" 文件文本和撤销文件写入时的不同。这意味着不再能使用该撤销文件,否则会破 坏文本。这也可能是因为 'encoding' 和撤销文件写入时的不同。 E825 撤销文件没有合法的内容,不能使用。 E826 撤销文件经过加密而解密失败。 E827 撤销文件经过加密而此版本的 Vim 不支持加密。用别的 Vim 打开该文件。 E832 撤销文件经过加密而 'key' 没有设置,文本文件本身没有加密。这可能是因为 文本文件本来是由 Vim 用加密方式写入的,后来又用非加密方式进行了覆盖。 最好删除该撤销文件。 "Not reading undo file, owner differs" 撤销文件的拥有者和文本文件的拥有者不同。因为安全原因,不使用该撤销文 件。 写入撤销文件可能会有以下的失败原因: E828 待写入的文件不能建立。可能你没有该目录的写入权限。 "Cannot write undo file in any directory in 'undodir'" 'undodir' 里没有一个可用的目录。 "Will not overwrite with undo file, cannot read" 待写入的撤销文件已经存在,但不能读回。你需要先删除或改名。 "Will not overwrite, this is not an undo file" 待写入的撤销文件已经存在,但开头没有正确的魔术数字。你需要先删除或改 名。 "Skipping undo file write, nothing to undo" 没有可写的撤销信息,没有任何改变,或者 'undolevels' 为负。 E829 写入撤销文件时有错。可以多试几次。

6. 撤销操作的解释 undo-remarks

能记忆的最大改变次数由 'undolevels' 选项决定。如果它的值是零,我们总是运行在 Vi-兼容模式。如果它的值是负的,任何撤销都是不可能的。这只有在内存紧张的时候适 用。 clear-undo 如果设置 'undolevels' 为 -1,撤销信息不会立即清除。当有新改变时才会。要强迫清 除撤销信息,可以用: :let old_undolevels = &undolevels :set undolevels=-1 :exe "normal a \<BS>\<Esc>" :let &undolevels = old_undolevels :unlet old_undolevels 当前缓冲区的位置标记 ('a 至 'z) 随文本一起被保存和复原。 当所有的改变都被撤销时,缓冲区被标记为未改变。这时可以使用 ":q" 而不一定是 ":q!" 退出 Vim 。注意 未改变是相对文件的最后写入而言的。在写入 ":w" 后紧跟的撤 销 "u",实际上改变了被写入以后缓冲区的状态。因此,此时缓冲区应被视为已改变。 当使用手动 folding 时,折叠不会被保存和复原。只有完全发生折叠内部的改变才不 会影响该折叠,因为它开头和最后一行没有改变。 数字编号的寄存器也可以被用作撤销删除操作。你每一次删除文本,该文本被放在 "1 寄 存器中。同样的,"1 寄存器的内容被移到 "2 寄存器,依次类推。"9 寄存器的内容则会 丢失。现在,你可以通过命令 '"1P' 得到最近删除的文本。(如果被删除的内容来自最近 的删除或复制操作,'P' 或 'p' 同样也可得到你要的结果,因为他们会复制未命名寄存 器的内容)。使用 '"3P' 可以得到三个删除操作之前的文本。 redo-register 如果你想得到多于一处被删除文本的内容,你可以使用重复命令 "." 一个特殊的特性: 它会递增所使用寄存器内的序号。所以,你如果先使用 '"1P' ,那么接下来的 "." 就意 味着 '"2P'。重复这样的操作,所有编号的寄存器都会被插入。 例如: 如果你用 'dd....' 删除了内容 (五行文本),那么用 '"1P....' 可以恢复之。 如果你不知道被删除的内容在哪一个寄存器,你可以用 :display 命令。还有一个方法 就是先试第一个寄存器 '"1P' ,如果不对,用 'u.' 命令。这将会移走第一次放进的内 容,然后在第二个寄存器上重复 put 命令。重复使用 'u.' 直到你得到想要的内容为 止。 vim:tw=78:ts=8:noet:ft=help:norl: