差异

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


diff 模式 此文件描述了 diff 功能:显示同一个文件的两到八个版本的差异。
基本内容在用户手册的第 08.7 节中解释。

1. 启动 diff 模式 启动-vimdiff

要以 diff 模式开始编辑,运行 "nvim -d"。这将像往常一样启动 Nvim,并额外设置以查看参数之间的差异。
nvim -d file1 file2 [file3 [file4]]
除了 -d 参数外,-R 可用于只读模式。
第二个及以后的参数也可以是目录名称。Vim 将在目录名称后面追加第一个参数的文件名以查找该文件。
默认情况下将使用内部 diff 库。当 'diffopt''diffexpr' 已经设置时,将使用外部 "diff" 命令。这仅在这样的 diff 程序可用时才有效。
差异是当前标签页 标签页 的本地差异。你无法在另一个标签页的窗口中看到差异。这确实使得一次拥有多个差异成为可能,每个差异都在自己的标签页中。
发生的情况是,Nvim 为每个文件打开一个窗口。这就像使用 -O 参数一样。这使用垂直分割,但如果你更喜欢水平分割,请改用 -o 参数。
nvim -d -o file1 file2 [file3 [file4]]
如果你始终更喜欢水平分割,请在 'diffopt' 中包含 "horizontal"。
在每个被编辑的文件中,以下选项被设置
'diff' 开启 'scrollbind' 开启 'cursorbind' 开启 'scrollopt' 包含 "hor" 'wrap' 关闭,或者如果 'diffopt' 包含 "followwrap",则保持不变 'foldmethod' "diff" 'foldcolumn' 值来自 'diffopt',默认值为 2。
这些选项在窗口级别被设置。当编辑另一个文件时,它们将重置为全局值。选项仍然可以从模式行中被覆盖,当再次编辑文件时。但是,当 'diff' 被设置时,'foldmethod''wrap' 不会从模式行中被设置。请参阅 :diffoff,了解轻松恢复选项的方法。
显示的差异实际上是缓冲区中的差异。因此,如果你在加载文件后进行修改,这些修改将包含在显示的差异中。你可能需要偶尔执行 ":diffupdate",因为并非所有修改都会立即生效,尤其是在使用外部 diff 命令时。
在你的 vimrc 文件中,你可以做一些特殊的事情,当 Vim 以 diff 模式启动时。你可以使用类似下面的结构
if &diff
   setup for diff mode
else
   setup for non-diff mode
endif
当已经在 Vim 中时,你可以通过三种方式启动 diff 模式。
E98
:diffs[plit] {filename} :diffs :diffsplit 在文件 {filename} 上打开一个新窗口。选项被设置为与 "nvim -d" 相同,适用于当前窗口和新打开的窗口。另请参阅 'diffexpr'
:difft :diffthis :difft[his] 使当前窗口成为 diff 窗口的一部分。这将设置与 "nvim -d" 相同的选项。
:diffp[atch] {patchfile} E816 :diffp :diffpatch 使用当前缓冲区,用 {patchfile} 中找到的 diff 对其进行补丁,并在结果上打开一个缓冲区。这将设置与 "nvim -d" 相同的选项。{patchfile} 可以是 "patch" 程序理解的任何格式,或者 'patchexpr' 可以处理的格式。请注意,{patchfile} 应该只包含一个文件的 diff,即当前文件。如果 {patchfile} 还包含其他文件的 diff,结果将不可预测。Vim 会将目录更改为 /tmp 以避免意外地对当前目录中的文件进行补丁。但它仍然可能导致各种 ".rej" 文件被创建。当存在绝对路径名时,这些文件可能仍然会被补丁。
要使这些命令使用垂直分割,请在前面添加 :vertical。示例
:vert diffsplit main.c~
:vert diffpatch /tmp/diff
如果你始终更喜欢垂直分割,请在 'diffopt' 中包含 "vertical"。
E96
最多可以有八个缓冲区设置了 'diff'
由于选项值与缓冲区一起被记忆,你可以短暂地编辑另一个文件,然后回到同一个文件,并再次进入 diff 模式。
:diffo :diffoff :diffo[ff] 关闭当前窗口的 diff 模式。当 'diff' 未设置时,也会重置相关选项。
:diffo[ff]! 关闭当前窗口的 diff 模式,并在当前标签页中所有设置了 'diff' 的窗口中关闭 diff 模式。重置相关选项仅在设置了 'diff' 的窗口中进行,如果当前窗口未设置 'diff',则不会更改其中的任何选项。隐藏缓冲区也从 diff 缓冲区列表中删除。
:diffoff 命令将相关选项重置为使用 :diffsplit:diffpatch:diffthis 或以 diff 模式启动 Vim 时所具有的值。当两次使用 :diffoff 时,将恢复上次保存的值。否则,它们将被设置为其默认值。
'diff' 关闭 'scrollbind' 关闭 'cursorbind' 关闭 'scrollopt' 不包含 "hor" 'wrap' 打开,或者如果 'diffopt' 包含 "followwrap",则保持不变 'foldmethod' "manual" 'foldcolumn' 0。
'foldenable' 很可能被重置为关闭。那是当 'foldmethod' 被恢复为 "manual" 时。折叠本身并没有被清除,但它们不应该显示出来,重置 'foldenable' 是最好的方法。

2. 查看差异 查看-差异

效果是 diff 窗口显示相同的文本,差异用高亮显示。当滚动文本时,'scrollbind' 选项将使其他窗口中的文本也滚动。对于垂直分割,文本应该对齐良好。
当出现以下情况时,文本对齐会出错
'wrap' 开启,一些行将被换行并占据两行或更多屏幕行
在一个窗口中折叠打开,但在另一个窗口中没有打开
'scrollbind' 关闭
文本已发生更改
"filler" 不存在于 'diffopt' 中,删除/插入的行会使对齐出错
所有在设置了 'diff' 选项的窗口中编辑的缓冲区都将加入 diff。这对于隐藏缓冲区也是可能的。它们必须先在一个窗口中被编辑,才能实现。要摆脱隐藏缓冲区,请使用 :diffoff!
:DiffOrig diff-原始文件 由于 'diff' 是一个窗口本地选项,因此可以在一个窗口中以 diff 模式查看同一个缓冲区,而在另一个窗口中以 "正常" 模式查看。也可以查看自加载文件以来对缓冲区所做的更改。由于 Vim 不允许为同一个文件拥有两个缓冲区,因此你需要另一个缓冲区。此命令很有用
command DiffOrig vert new | set buftype=nofile | read ++edit # | 0d_
       \ | diffthis | wincmd p | diffthis
使用 ":DiffOrig" 查看当前缓冲区与其加载来源文件之间的差异。
一个未加载的缓冲区不能用于 diff。但它确实适用于隐藏缓冲区。你可以使用 ":hide" 关闭一个窗口,而不卸载缓冲区。如果你不希望缓冲区仍然用于 diff,请在隐藏它之前执行 ":set nodiff"。
:dif :diff :diffupdate :dif[fupdate][!] 更新 diff 高亮和折叠。
当你对文本进行更改时,Vim 会尝试保持差异更新。这主要处理插入和删除的线路。线路内的更改和更复杂的更改不会导致差异更新。要强制更新差异,请使用
:diffupdate
如果包含 !,Vim 将检查文件是否在外部被修改,是否需要重新加载。它将提示每个修改的文件,就像使用 :checktime 一样。
Vim 将显示填充行,用于在一个窗口中缺失但在另一个窗口中存在的行。这些行是在另一个文件中插入的,或者是在此文件中删除的。从 'diffopt' 选项中移除 "filler" 将使 Vim 不显示这些填充行。
折叠用于隐藏未更改的文本。请参阅 折叠,了解所有可用于折叠的命令。
差异上方未包含在折叠中的行的上下文可以使用 'diffopt' 选项设置。例如,要将上下文设置为三行
:set diffopt=filler,context:3
差异用以下组进行高亮显示
hl-DiffAdd DiffAdd 添加(插入)的线路。这些线路存在于此缓冲区中,但在另一个缓冲区中不存在。 hl-DiffChange DiffChange 更改的线路。 hl-DiffText DiffText 更改的线路内的更改文本。Vim 会找到第一个不同的字符,以及最后一个不同的字符(从行尾搜索)。它们之间的文本将被高亮显示。这意味着中间仍然相同的部分也会被高亮显示。这里的 'diffopt' 标志 "iwhite" 和 "icase" 被使用。 hl-DiffDelete DiffDelete 删除的线路。也称为填充行,因为它们实际上不存在于此缓冲区中。

3. 跳至差异 跳至-差异

可以使用两个命令跳至差异: [c
[c 向后跳至上一个更改的开始。当使用计数时,重复执行该操作该次数。 ]c
]c 向前跳至下一个更改的开始。当使用计数时,重复执行该操作该次数。
如果游标没有可以移动到的更改,则会出错。

4. 差异复制 复制-差异 E99 E100 E101 E102 E103

合并
有两个命令可以将文本从一个缓冲区复制到另一个缓冲区。结果是,缓冲区在指定范围内将相等。
:diffg :diffget :[range]diffg[et] [bufspec] 修改当前缓冲区以撤消与另一个缓冲区的差异。如果给出 [bufspec],则使用该缓冲区。如果 [bufspec] 指向当前缓冲区,则不会发生任何事情。否则,只有在 diff 模式下还有另一个缓冲区时,此命令才会生效。请参阅下方了解 [range]。
:diffpu :diffput E793 :[range]diffpu[t] [bufspec] 修改另一个缓冲区以撤消与当前缓冲区的差异。与“:diffget”相同,但修改的是另一个缓冲区,而不是当前缓冲区。当[bufspec]被省略,并且在 diff 模式下有多个其他缓冲区'modifiable' 被设置,则会失败。有关[range],请参见下文。
do
[count]do 与“:diffget”相同,没有范围。 “o”代表“获得”(“dg”不能使用,它可能是“dgg”的开始!)。 注意:这在 Visual 模式下不起作用。如果你提供一个[count],它将被用作“:diffget”的[bufspec]参数。
dp
[count]dp 与“:diffput”相同,没有范围。 注意:这在 Visual 模式下不起作用。如果你提供一个[count],它将被用作“:diffput”的[bufspec]参数。
如果没有提供[range],则会影响光标位置或其上方的差异。当使用[range]时,Vim 会尝试仅放置或获取指定的行。当存在已删除的行时,这可能并不总是可行。
在缓冲区的最后一行下方可能存在已删除的行。当光标位于缓冲区的最后一行,并且该行上方没有差异时,“:diffget”和“do”命令将从另一个缓冲区获取行。
为了能够在[range]中从另一个缓冲区获取这些行,可以使用最后一行号加一。此命令将获取来自另一个缓冲区的所有差异
:1,$+1diffget
请注意,已删除的行将显示,但不会被计为文本行。你无法将光标移动到其中。要使用来自另一个缓冲区的行填充已删除的行,请在它们下方的行上使用“:diffget”。 E787
当即将被修改的缓冲区为只读,并且由FileChangedRO触发的自动命令更改缓冲区时,命令将失败。自动命令不得更改缓冲区。
上面的[bufspec]参数可以是缓冲区编号、缓冲区名称的模式或缓冲区名称的一部分。示例
:diffget 使用另一个处于 diff 模式的缓冲区 :diffget 3 使用缓冲区 3 :diffget v2 使用与“v2”匹配并处于 diff 模式的缓冲区(例如,“file.c.v2”)

5. Diff 选项 diff-options

另请参阅 'diffopt''fillchars' 的“diff”项。
diff-slow diff_translations 对于非常长的行,diff 语法高亮可能会很慢,特别是因为它会尝试匹配所有不同类型的本地化。要禁用本地化并加快语法高亮速度,请将全局变量 g:diff_translations 设置为零
let g:diff_translations = 0
设置此变量后,重新加载语法脚本
set syntax=diff

查找差异 diff-diffexpr

可以将 'diffexpr' 选项设置为使用内部 diff 支持或标准“diff”程序以外的其他内容来比较两个文件并找到差异。
'diffexpr' 为空时,Vim 使用此命令来查找文件 1 和文件 2 之间的差异
diff file1 file2 > outfile
“>”将替换为 'shellredir' 的值。
“diff”的输出必须是标准的“ed”样式 diff 或统一 diff。上下文 diff 将不起作用。对于统一 diff,不能使用上下文行。使用“diff -u”将不起作用,请使用“diff -U0”。
此示例说明了 Vim 期望的“ed”样式 diff 格式
1a2
> bbb
4d4
< 111
7c7
< GGG
---
> ggg
“1a2”项追加行“bbb”。“4d4”项删除行“111”。“7c7”项将行“GGG”替换为“ggg”。
'diffexpr' 不为空时,Vim 会对其进行求值以获得上面提到的格式的 diff 文件。这些变量被设置为使用的文件名
v:fname_in 原始文件 v:fname_new 同一文件的最新版本 v:fname_out 将结果 diff 文件写入的位置
此外,'diffexpr' 应该处理 'diffopt' 选项中的“icase”和“iwhite”。'diffexpr' 无法更改 'lines''columns' 的值。
使用不带参数的函数调用的优点是速度更快,请参阅 expr-option-function
示例(这与 'diffexpr' 为空几乎相同)
set diffexpr=MyDiff()
function MyDiff()
   let opt = ""
   if &diffopt =~ "icase"
     let opt = opt .. "-i "
   endif
   if &diffopt =~ "iwhite"
     let opt = opt .. "-b "
   endif
   silent execute "!diff -a --binary " .. opt .. v:fname_in .. " " .. v:fname_new ..
        \  " > " .. v:fname_out
   redraw!
endfunction
“-a”参数用于强制将文件作为文本进行比较,作为二进制文件进行比较没有用。 “--binary”参数使文件以二进制模式读取,这样 CTRL-Z 不会在 DOS 上结束文本。
redraw! 命令可能不需要,这取决于执行 shell 命令是否在显示屏上显示内容。
如果 'diffexpr' 表达式以 s: 或 <SID> 开头,则将其替换为脚本 ID (local-function)。示例
set diffexpr=s:MyDiffExpr()
set diffexpr=<SID>SomeDiffExpr()
否则,表达式将在设置选项的脚本的上下文中求值,因此脚本本地项目可用。
E810 E97 Vim 将进行测试以查看 diff 输出是否正常。如果它没有,你将收到一条错误消息。可能原因
无法执行“diff”程序。
“diff”程序没有生成标准的“ed”样式 diff(见上文)。
'shell' 和相关选项设置不正确。尝试使用“:!sort”之类的命令来查看过滤是否有效。
你正在使用 'diffexpr' 并且它不起作用。如果问题不清楚,请将 'verbose' 选项设置为 1 或更多,以查看更多消息。
适用于 MS-Windows 的自安装 Vim 包含一个 diff 程序。如果你没有它,你可能需要下载 diff.exe。例如,来自 https://gnuwin32.sourceforge.net/packages/diffutils.htm

使用补丁 diff-patchexpr

可以将 'patchexpr' 选项设置为使用标准“patch”程序以外的其他内容。
'patchexpr' 为空时,Vim 将像这样调用“patch”程序
patch -o outfile origfile < patchfile
这应该适用于大多数版本的“patch”程序。请注意,行中间的 CR 可能会导致问题,它被视为换行符。
如果默认设置不适合你,请将 'patchexpr' 设置为具有相同效果的表达式。这些变量被设置为使用的文件名
v:fname_in 原始文件 v:fname_diff 补丁文件 v:fname_out 结果的修补文件
使用不带参数的函数调用的优点是速度更快,请参阅 expr-option-function
示例(这与 'patchexpr' 为空相同)
set patchexpr=MyPatch()
function MyPatch()
   :call system("patch -o " .. v:fname_out .. " " .. v:fname_in ..
   \  " < " .. v:fname_diff)
endfunction
确保使用“patch”程序没有不必要的副作用。例如,注意额外生成的应该删除的文件。它应该只修补文件,而不是其他任何东西。Vim 在求值 'patchexpr' 之前将更改目录到“/tmp”或另一个临时目录。这希望能避免意外修补当前目录中的文件。Vim 还将删除以 v:fname_in 开头并以“.rej”和“.orig”结尾的文件。
如果 'patchexpr' 表达式以 s: 或 <SID> 开头,则将其替换为脚本 ID (local-function)。示例
set patchexpr=s:MyPatchExpr()
set patchexpr=<SID>SomePatchExpr()
否则,表达式将在设置选项的脚本的上下文中求值,因此脚本本地项目可用。
Main
命令索引
快速参考