撤销

Nvim :help 页面,从 生成源代码 使用 tree-sitter-vimdoc 解析器。


撤销和重做
基础知识在用户手册的 02.5 节中解释。

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

<Undo>undo <Undo> u u 撤销 [次数] 次更改。
:u :un :undo :u[ndo] 撤销一次更改。 E830
:u[ndo] {N} 跳到更改编号 {N} 之后。有关 {N} 的含义,请参见 undo-branches
:u[ndo]! 撤销一次更改并将其从撤销历史记录中删除。 E5767
:u[ndo]! {N} 类似于 ":u[ndo] {N}",但忘记当前撤销分支中直到 {N} 的所有更改。您只能使用 ":undo! {N}" 向后移动到同一撤销分支中,而不能重做或切换到另一个撤销分支。
CTRL-R
CTRL-R 重做 [次数] 次被撤销的更改。
:red :redo redo :red[o] 重做一次被撤销的更改。
U
U 撤销一行上的所有最新更改,即最新更改所在的行。 U 本身也计为一次更改,因此 U 会撤销之前的 U
最后一次更改会被记住。您可以使用上面的撤销和重做命令将文本恢复到每次更改之前的样子。您也可以再次应用这些更改,将文本恢复到撤销之前的状态。
"U" 命令在撤销/重做中与任何其他命令一样对待。因此,"u" 命令会撤销 "U" 命令,而 'CTRL-R' 命令会再次重做它。当混合使用 "U"、"u" 和 'CTRL-R' 时,您会注意到 "U" 命令会将一行的状态恢复到上一次 "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 不兼容。例如,在 Vi 中 "dwdwu." 会删除两个单词,而在 Nvi 中它不会执行任何操作。

3. 撤销块 undo-blocks

通常情况下,一个撤销命令会撤销一个键入的命令,无论该命令产生了多少更改。这个可撤销更改序列形成一个撤销块。因此,如果键入的键调用了一个函数,那么函数中的所有命令将一起被撤销。
如果您想编写一个不创建新的可撤销更改而与上一次更改合并的函数或脚本,请使用以下命令
:undoj :undojoin E790 :undoj[oin] 将后续更改与上一个撤销块合并。 警告: 谨慎使用,它可能会阻止用户正确撤销更改。不要在撤销或重做之后使用它。
这在您需要在更改过程中的中途提示用户时最为有用。例如,在一个调用 getchar() 的函数中。请确保在此之前存在与之相关的更改,您必须将其合并。
这本身无法正常工作,因为下一个按键操作将再次开始新的更改。但您可以执行以下操作
:undojoin | delete
在此之后,"u" 命令将撤销删除命令和上一次更改。 undo-break undo-close-block 要执行相反的操作,请为下一个更改使用新的撤销块,在插入模式下使用 CTRL-G u。这在您希望插入命令可分段撤销时很有用。例如,对于每个句子。 i_CTRL-G_u
设置 'undolevels' 的值也会关闭撤销块。即使新值等于旧值也是如此。使用 g:undolevels 显式地仅读取和写入 'undolevels' 的全局值。
let &g:undolevels = &g:undolevels
请注意,类似的赋值 let &undolevels=&undolevels 不会在局部选项被设置为不同的值的情况下保留 'undolevels' 的全局选项值。例如
" Start with different global and local values for 'undolevels'.
let &g:undolevels = 1000
let &l:undolevels = 2000
" This assignment changes the global option to 2000:
let &undolevels = &undolevels

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

上面我们只讨论了一行撤销/重做。但也有可能分叉。当您撤销一些更改,然后进行新的更改时,就会发生这种情况。被撤销的更改将成为一个分支。您可以使用以下命令转到该分支。
这在用户手册中解释:usr_32.txt
:undol :undolist :undol[ist] 列出更改树中的叶子。示例
编号 更改 保存时间
88 88 2010/01/04 14:25:53 108 107 08/07 12:47:51 136 46 13:33:01 7 166 164 3 秒前
"编号" 列是更改编号。此编号会持续递增,可用于识别特定可撤销更改,请参见 :undo。"更改" 列是从树的根节点到该叶子的更改次数。"保存时间" 列是进行此更改的日期和时间。四种可能的格式是: N 秒前 HH:MM:SS 小时、分钟、秒 MM/DD HH:MM:SS 同上,带月和日 YYYY/MM/DD HH:MM:SS 同上,带年 "保存" 列指定了是否将此更改写入磁盘以及写入哪个文件。这可以与 :later:earlier 命令一起使用。有关更多详细信息,请使用 undotree() 函数。
g-
g- 转到较旧的文本状态。使用计数重复多次。 :ea :earlier :ea[rlier] {次数} 转到较旧的文本状态 {次数} 次。:ea[rlier] {N}s 转到大约 {N} 秒之前的较旧文本状态。:ea[rlier] {N}m 转到大约 {N} 分钟之前的较旧文本状态。:ea[rlier] {N}h 转到大约 {N} 小时之前的较旧文本状态。:ea[rlier] {N}d 转到大约 {N} 天之前的较旧文本状态。
:ea[rlier] {N}f 转到较旧的文本状态,该状态是 {N} 次文件写入之前的状态。当自上次写入以来进行了更改时,":earlier 1f" 将将文本恢复到写入时的状态。否则,它将转到之前的写入。当处于第一次文件写入的状态,或文件未写入时,":earlier 1f" 将转到第一次更改之前。
g+
g+ 转到较新的文本状态。使用计数重复多次。 :lat :later :lat[er] {次数} 转到较新的文本状态 {次数} 次。:lat[er] {N}s 转到大约 {N} 秒之后的较新的文本状态。:lat[er] {N}m 转到大约 {N} 分钟之后的较新的文本状态。:lat[er] {N}h 转到大约 {N} 小时之后的较新的文本状态。:lat[er] {N}d 转到大约 {N} 天之后的较新的文本状态。
:lat[er] {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+" 会。
卸载缓冲区时,Vim 通常会销毁为该缓冲区创建的撤销树。通过设置 'undofile' 选项,Vim 会在您写入文件时自动保存您的撤销历史记录,并在您再次编辑该文件时恢复撤销历史记录。
在写入文件之后,但在 BufWritePost 自动命令之前,会检查 'undofile' 选项。如果您想控制要写入撤销信息的哪些文件,可以使用 BufWritePre 自动命令
au BufWritePre /tmp/* setlocal noundofile
Vim 将撤销树保存在单独的撤销文件中,每个编辑文件一个,使用一个简单的方案,将文件系统路径直接映射到撤销文件。Vim 会检测撤销文件是否不再与它所写入的文件同步(使用文件内容的哈希值),并在文件在撤销文件写入后发生更改时忽略它,以防止损坏。如果撤销文件的拥有者与编辑文件的拥有者不同,则撤销文件也会被忽略,除非撤销文件的拥有者是当前用户。设置 'verbose' 以在打开文件时获取有关此方面的消息。
撤销文件的存放位置由 'undodir' 选项控制,默认情况下,它们会保存到应用程序数据文件夹中的专用目录中。
您也可以使用 ":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 编写的。 您需要该较新版本才能打开它。 如果要保留撤销信息,请不要写入缓冲区。 "文件内容已更改,无法使用撤销信息" 文件文本与写入撤销文件时的文本不同。 这意味着无法使用撤销文件,它会破坏文本。 当 'encoding' 与写入撤销文件时不同时也会发生这种情况。 E825 撤销文件不包含有效内容,无法使用。 "不读取撤销文件,所有者不同" 撤销文件的拥有者与文本文件的拥有者不同。 出于安全考虑,不使用撤销文件。
写入撤销文件可能会由于以下原因而失败: E828 要写入的文件无法创建。 也许您没有该目录的写入权限。 "无法在 'undodir' 中的任何目录中写入撤销文件" 'undodir' 中的任何目录都无法使用。 "不会用撤销文件覆盖,无法读取" 存在要写入的撤销文件的同名文件,但无法读取。 您可能希望删除或重命名此文件。 "不会覆盖,这不是撤销文件" 存在要写入的撤销文件的同名文件,但它没有以正确的魔数开头。 您可能希望删除或重命名此文件。 "跳过撤销文件写入,没有可撤销的内容" 没有要写入的撤销信息,没有更改或 'undolevels' 为负数。 E829 写入撤销文件时出错。 您可能希望重试。

6. 关于撤销的说明 undo-remarks

记住的更改数量由 'undolevels' 选项设置。 如果为零,则始终使用与 Vi 兼容的方式。 如果为负数,则无法撤销。 如果您内存不足,请使用此选项。
清除撤销
当您将 'undolevels' 设置为 -1 时,撤销信息不会立即清除,而是在下次更改时发生。 要强制清除撤销信息,您可以使用以下命令
:let old_undolevels = &l:undolevels
:setlocal undolevels=-1
:exe "normal a \<BS>\<Esc>"
:let &l:undolevels = old_undolevels
:unlet old_undolevels
请注意使用 &l:undolevels 来显式读取 'undolevels' 的局部值,以及使用 :setlocal 来仅更改局部选项(它优先于相应的全局选项值)。 通过使用 &undolevels 保存选项值是不可预测的;它读取局部值(如果已设置)或全局值(否则)。 此外,如果已设置局部值,则通过 :set undolevels 更改选项将同时更改全局值和局部值,需要额外的工作来保存和恢复这两个值。
缓冲区('a 到 'z)的标记也与文本一起保存和恢复。
当所有更改都已撤销时,缓冲区不被视为已更改。 然后可以使用 ":q" 而不是 ":q!" 退出 Vim。 请注意,这与文件的上次写入相关。 在 ":w" 之后键入 "u" 实际上会更改缓冲区(与已写入的内容相比),因此缓冲区被认为已更改。
当使用手动 折叠 时,折叠不会保存和恢复。 只有完全在折叠内的更改才会保留折叠,因为折叠的第一行和最后一行不会更改。
编号寄存器也可以用于撤销删除。 每次删除文本时,它都会被放入寄存器 "1 中。 寄存器 "1 的内容将移至 "2,依此类推。 寄存器 "9 的内容将丢失。 现在您可以使用 put 命令获取最近删除的文本:'"1P'。 (此外,如果删除的文本是上次删除或复制操作的结果,'P' 或 'p' 也适用,因为这将放入未命名寄存器的内容)。 您可以通过 '"3P' 获取三次回退前的文本。
重做寄存器
如果要恢复已删除文本的多个部分,可以使用重复命令 "." 的一项特殊功能。 它将增加所用寄存器的编号。 因此,如果您先执行 '"1P',则后续的 "." 将导致 '"2P'。 重复此操作将导致所有编号寄存器被插入。
示例:如果您使用 'dd....' 删除了文本,则可以使用 '"1P....' 恢复它。
如果您不知道已删除文本位于哪个寄存器中,可以使用 :display 命令。 另一种方法是尝试使用第一个寄存器 '"1P',如果它不是您想要的,则执行 'u.'。 这将删除第一个 put 的内容,并为第二个寄存器重复 put 命令。 重复 'u.' 直到您得到想要的内容。
主要
命令索引
快速参考