diff

diff.txt 适用于 Vim 9.2 版本。 最近更新: 2025年9月 VIM 参考手册 by Bram Moolenaar 译者: Zimin<cranecai@users.sf.net>、tocer diff vimdiff gvimdiff diff-mode 本文档介绍 +diff 特性: 比较同一份文件的两到八个版本并展示差异。 基础用法请参考用户手册 08.7 一节。 1. 进入比较模式 start-vimdiff 2. 查看比较结果 view-diffs 3. 跳转到差异文 jumpto-diffs 4. 复制差异文本 copy-diffs 5. 比较锚点 diff-anchors 6. diff 选项 diff-options

1. 进入比较模式 start-vimdiff

用比较模式开启编辑的最简单方法是用 "vimdiff" 命令。该命令如常启动 Vim,但自动 配置好差异视图,方便查看输入参数文件间的差异: vimdiff file1 file2 [file3 [file4]] 这等同于: vim -d file1 file2 [file3 [file4]] 也可用 "gvimdiff" 或 "vim -d -g" 直接启动 GUI 版本的比较模式。 还可用 "viewdiff" 或 "gviewdiff" 以只读模式启动比较。 所有这些命令前都可加上 "r" 前缀,进入受限模式 (参见 -Z )。 第二个及后续参数也可传入目录名。Vim 会自动将首个参数文件名拼接到该目录后寻找对 应文件。 缺省使用内置比较库。设置 'diffopt''diffexpr' 后会使用外部 "diff" 命令。前 提是系统有 diff 程序可用。 比较仅限于当前标签页的窗口 tab-page 。其他标签页的窗口无法参与比较。借此可以 在多个标签页中同时开启多组独立的文件比较。 执行 "vimdiff" 时,Vim 将为每个文件单独打开窗口,默认使用 -O 方式,即垂直分 割。如果临时需要水平分割,可加上 -o 参数: vimdiff -o file1 file2 [file3 [file4]] 如果希望缺省总是使用水平分割,可在 'diffopt' 里包含 "horizontal"。 每个参与比较的编辑窗口自动设置以下选项: 'diff' 打开比较模式 'scrollbind' 打开滚动绑定 'cursorbind' 打开光标绑定 'scrollopt' 包含 "hor",滚动绑定在水平方向上同步 'wrap' 关闭自动回绕,'diffopt' 包含 "followwrap" 时则保持不变 'foldmethod' "diff",启用基于比较改动的折叠 'foldcolumn' 折叠指示列的宽度,由 'diffopt' 值指定,缺省为 2 以上选项仅局部于该窗口。窗口切换编辑其他文件时,以上选项会被重设回全局值。 不过,通过模式行,部分选项可在重新编辑文件时被覆盖。例外是在 'diff' 置位时,模 式行无法覆盖 'foldmethod''wrap'。 可执行 :diffoff 快速还原以上选项值。 展示的差异基于缓冲区内容的实际差异。因而,载入文件做过的改动会自动参加比较。 不过,部分改动不会立刻刷新比较结果 (使用外部 diff 命令时尤为明显),为此可手动 执行 :diffupdate 进行刷新。 在以比较模式启动时,如果要做专门设置,可在 .vimrc 中加入以下结构: if &diff 用于比较模式的相关设置 else 用于非比较模式的相关设置 endif 已经打开 Vim 后,也有三种方式开启比较模式。 E98 :diffs[plit] {filename} :diffs :diffsplit 为文件 {filename} 打开新窗口。当前窗口和新窗口都会像 "vimdiff" 一样,自动设置相关选项。另见 'diffexpr' :difft :diffthis :difft[his] 使当前窗口参与比较。会像 "vimdiff" 一样设置相关选项。 :diffp[atch] {patchfile} E816 :diffp :diffpatch 以当前缓冲区为基础,使用 {patchfile} 提供的补丁进行修补,并打 开一个新缓冲区 (在分割的新窗口中) 显示修补结果。两个缓冲区所在 窗口都会像 "vimdiff" 一样设置相关选项。 {patchfile} 可以是 "patch" 程序支持的任意格式,或 'patchexpr' 可以处理的格式。 注意: {patchfile} 只能包含一个文件 (即当前文件) 的差异补丁。如 果 {patchfile} 同时包含其他文件的补丁,结果将不可预知。在修补 前,Vim 会临时切换当前目录到 /tmp (见 tempfile ),以避免意外 修补当前目录下的文件。但仍可能产生若干 ".rej" 文件。要注意在补 丁中使用绝对路径名的文件仍可能被意外修补。在受限模式 ( restricted-mode ) 下,不允许使用 "patch" 命令。 要使这些命令使用垂直分割而非默认的水平分割,加上 :vertical 前缀。例如: :vert diffsplit main.c~ :vert diffpatch /tmp/diff 如果希望缺省总是使用垂直分割,可在 'diffopt' 里包含 "vertical"。 E96 最多可以有八个缓冲区同时开启 'diff' 模式。 因为相关选项值会与缓冲区一起保存,可以临时在同一窗口编辑另一个文件,回到原来文 件时,会自动重新打开比较模式及相关选项。 :diffo :diffoff :diffo[ff] 关闭当前窗口的比较模式。即使 'diff' 未置位,也会复位相关选项。 :diffo[ff]! 关闭当前标签页中所有置位 'diff' 的窗口 (包括当前窗口) 的比较模 式。仅当窗口置位 'diff' 时才复位相关选项,当前窗口也是如此。 清空当前标签页的比较缓冲区列表,包括隐藏缓冲区。 :diffoff 命令会相将相关选项恢复到 :diffsplit:diffpatch:diffthis 执 行之前或以启动 Vim 时开启比较模式前的值。 如果执行 :diffoff 两次,即使 'diff' 已复位,仍会恢复相关选项到上次保存的值。 相关选项如果没有手动设置过,则恢复其缺省值如下: 'diff' 关闭 'scrollbind' 关闭 'cursorbind' 关闭 'scrollopt' 不包含 "hor" 'wrap' 打开,'diffopt' 包含 "followwrap" 时则保持不变 'foldmethod' "manual" 'foldcolumn' 0 'foldmethod' 恢复为 "manual" 时,'foldenable' 会被关闭,否则恢复其保存值。折叠 本身不会直接被清除,但设为 "manual" 时它们不应显示,复位 'foldenable' 是实现这 一点的最佳方式。

2. 查看比较结果 view-diffs

开启比较模式后,各个比较窗口会显示同一份文本,高亮标出差异部分。滚动文本时, 'scrollbind' 选项会同步滚动其他比较窗口的文本。垂直分割模式下文本会保持对齐。 不过,以下情况会影响文本对齐: - 开启 'wrap' 后,部分行会自动回绕折成多行屏幕行 - 某个窗口展开折叠,其他窗口则关闭折叠 - 'scrollbind' 被关闭 - 对文本做了编辑改动 - 'diffopt' 未包含 "filler",新增或删除行导致错位 所有打开 'diff' 选项的窗口中编辑的缓冲区,都会参与比较。也包含隐藏缓冲区。这些 缓冲区必定是曾经在窗口中编辑过才会如此。要从比较缓冲区列表中移除隐藏缓冲区,可 用 :diffoff! :DiffOrig diff-original-file 'diff' 是窗口局部选项,因此同一个缓冲区可以在一个窗口中用比较模式,而在另一个 窗口里用普通模式查看。也可以比较缓冲区修改版本和文件原始版本。由于 Vim 不允许 同一文件存在两个缓冲区,需要新建一个临时缓冲区。可用以下自定义命令: command DiffOrig vert new | set bt=nofile | r ++edit # | 0d_ \ | diffthis | wincmd p | diffthis ( defaults.vim 已内置该命令)。执行 ":DiffOrig" 即可对当前缓冲区及其文件原始版 本进行比较。 已卸载的缓冲区无法参与比较。但隐藏缓冲区可以。可用 :hide 关闭窗口但不卸载缓 冲区。如果不想缓冲区继续参与比较,在隐藏前先执行 `:set nodiff`。 :dif :diff :diffupdate :dif[fupdate][!] 刷新比较结果高亮和折叠。 Vim 会在文本修改后尝试自动更新比较结果。新增或删除行一般能正确处理。但行内改动 或更复杂的改动会出现问题。要强制刷新,可手动执行: :diffupdate 带 ! 时,Vim 会检测文件是否被外部程序修改而需要重新加载。像 :checktime 一样 逐个提示询问是否加载变更文件。 对于在一个窗口没有,但在另一个窗口出现的行,Vim 会显示填充行。它们或是在另一个 文件中新增,或是在本文件删除。从 'diffopt' 选项中排除 "filler" 可关闭填充行的 显示。 未发生改动的文本会用折叠隐藏。折叠相关命令可见 folding 。 可通过 'diffopt' 选项设置在差异文上方保留的上下文行数。例如保留 3 行上下文: :set diffopt=filler,context:3 比较结果使用以下语法组高亮: hl-DiffAdd DiffAdd 新增 (插入) 行,即只存在于本缓冲区的行。 hl-DiffChange DiffChange 改动行。 hl-DiffText DiffText 改动行内的差异文本。具体行为由 'diffopt' 里的 inline: 设置决定。 inline: 设为 "simple" 时,Vim 会找出本行首个 和末个不同字符 (从行末开始搜索),高亮中间所有 文本,包括其实没被改动的部分。同时会考虑 'diffopt' 中的 "iwhite" 和 "icase" 标志位。 inline: 设为 "char" 或 "word" 时,调用 Vim 内置比较库做精细比较,并精准高亮行内差异。同时 会考虑所有影响内置比较库的 'diffopt' 标志位。 inline: 设为 "none" 时,不启用行内高亮。 hl-DiffTextAdd DiffTextAdd 改动行内的新增文本。和 DiffText 类似,用于其他 缓冲区无对应文本的情景。 inline: 为 "simple" 或 "none" 时不生效。 hl-DiffDelete DiffDelete 删除行。也称为填充行,即当前缓冲区不存在的行。

3. 跳转到差异文 jumpto-diffs

有两条命令可用于跳转到差异文: [c [c 反向跳转至上一处更改的开始位置。指定计数时,跳转相应次 数。 ]c ]c 正向跳转至下一个更改的开始位置。指定计数时,跳转相应次 数。 如果没有可供跳转的更改,报错。

4. 复制差异文本 copy-diffs E99 E100 E101 E102 E103

merge 有两条命令可用于将文件从一个缓冲区复制到另一个缓冲区。结果是在指定范围内,两缓 冲区的内容被统一。 :diffg :diffget :[range]diffg[et] [bufspec] 用另一个缓冲区来修改当前的缓冲区,消除不同之处。除非只有另一个 比较模式下的缓冲区, [bufspec] 必须给出,指定要使用的缓冲区。 如果 [bufspec] 指定当前缓冲区,则为空动作。 [range] 详见下文。 :diffpu :diffput E793 :[range]diffpu[t] [bufspec] 用当前缓冲区来修改另一个缓冲区,消除不同之处。与 ":diffget" 的 不同之处仅在于被修改的是对方缓冲区。 如果 [bufspec] 省略,且有多于一个缓冲区处于比较模式而 'modifiable' 置位,此命令将失败。 [range] 详见下文。 do [count]do 等同于不带范围的 :diffget 。"o" 代表 "obtain" (获取,不能用 "dg",因为那可能是 "dgg" 的前缀!)。注意 在可视模式下无效。 如果给出 [count],用作 ":diffget" 的 [bufspec] 参数。 dp dp 等同于不带范围的 :diffput注意 在可视模式下无效。 如果给出 [count],用作 :diffput[bufspec] 参数。 [range] 省略时,操作对象是光标所在或其正上方的差异文本。在缓冲区最后一行后可能 存在已删除的行 (显示为填充行)。如果光标位于该行且其上方没有差异 (且 [range] 省 略) 时,则操作对象为光标下方的差异文本。 [range] 给出时,Vim 仅修改指定行范围内的内容。这也包括在范围指定行间的删除行。 为了精确指定从对方缓冲区获取或放置的行范围,[range] 中允许使用 0 值以及末行行 号加一后的值 (译者注: 前者可获取当前缓冲区首行之前的删除行,后者可获取末行之后 的删除行)。以下命令可从对方缓冲区中获取全部差异文: :0,$+1diffget 注意: 删除行会显示 (为填充行),但不计入实际文本行。也无法将光标移至其中。要从 对方缓冲区获取内容填充删除行,可在其下一行上执行 :diffget E787 如果要修改的缓冲区只读,因此触发的 FileChangedRO 自动命令中切换了缓冲区,相 应命令会失败。此时,自动命令不能切换缓冲区。 上述的 [bufspec] 参数可为缓冲区编号,或是匹配缓冲区名 (可部分匹配) 的模式。 例如: :diffget 使用另一个比较模式下的缓冲区 :diffget 3 使用 3 号缓冲区 :diffget v2 使用名字匹配 "v2" 且在比较模式下的缓冲区 (如 "file.c.v2")

5. 比较锚点 diff-anchors

比较锚点允许用户控制比较算法在文件间对齐与同步文本的具体位置。每个锚点会匹配其 他参与比较的文件中的对应锚点,以便精确控制比较结果。 这可用于涉及复杂编辑的改动,例如,一个函数被挪到其他位置后,又做了进一步修改。 缺省情况下,算法目标是生成最小差异文,导致整个函数被判定为先删除,后新增,这样 难以看清函数内部实际的改动内容。用比较锚点方法,可以固定该函数,使比较算法据此 锚点精准对齐。 可在 'diffanchors' 选项中设置锚点,这是一个逗号分隔的 {address} 列表,用于每个 参与文件。然后在 'diffopt' 里加入 "anchor",启用锚点功能。具体做法是,Vim 在内 部会把每个文件分割成由锚点区隔的多个区段。逐段比较,最后再合并结果。 设置 'diffanchors' 后,会立即刷新比较结果。但如果锚点通过位置标记绑定,则位置 标记位置被修改时,需要手动执行 :diffupdate 才能生效。 示例: 假定有以下两个并排文件。关注 foo() 函数内的改动,该函数被同时编辑和挪动。 A 文件: int foo() { int n = 1; return n; } int g = 1; int bar(int a) { a *= 2; a += 3; return a; } B 文件: int bar(int a) { a *= 2; a += 3; return a; } int foo() { int n = 999; return n; } int g = 1; 普通方式的比较算法产生的对齐结果是: int foo() { |---------------- int n = 1; |---------------- return n; |---------------- } |---------------- |---------------- int g = 1; |---------------- |---------------- int bar(int a) {|int bar(int a) { a *= 2; | a *= 2; a += 3; | a += 3; return a; | return a; } |} ----------------| ----------------|int foo() { ----------------| int n = 999; ----------------| return n; ----------------|} ----------------| ----------------|int g = 1; 而理想目标是让比较算法在 foo() 函数上对齐: ----------------|int bar(int a) { ----------------| a *= 2; ----------------| a += 3; ----------------| return a; ----------------|} ----------------| int foo() { |int foo() { int n = 1; | int n = 999; return n; | return n; } |} | int g = 1; |int g = 1; |---------------- int bar(int a) {|---------------- a *= 2; |---------------- a += 3; |---------------- return a; |---------------- } |---------------- 下面列举几种设置锚点、实现上述对齐效果的方式。所有用法都必须先在 'diffopt' 中 加入 anchor 才能生效。 位置标记: 分别在每个文件的 `int foo()` 行上设置位置标记 'a ,再配置锚点: set diffanchors='a 模式: 通过 pattern (见 :/ ) 指定锚点。这里为一致性起见,总是从第 1 行开始搜 索,确保对齐: set diffanchors=1/int\ foo(/ 可视选区: 分别在每个文件中用可视模式选中完整 foo() 函数体。此处设置双锚点。 能精准仅对齐函数本体,而不包含其后续内容。注意 下面写法里的 '>+1 必不可少。 这代表将函数末行之后一行作为分割线: set diffanchors='<,'>+1 手动: 可为每个缓冲区分别设置局部选项值,直接通过行号手动设定锚点: setlocal diffanchors=1,5 wincmd w setlocal diffanchors=7,11

6. diff 选项 diff-options

另见 'diffopt''fillchars' 中的 "diff" 项。 diff-slow diff_translations 超长文本行进行比较语法高亮时速度可能偏慢,主要原因是需要匹配不同语言的本地化文 本。要关闭本地化匹配以提速,可设置全局变量 g:diff_translations 为零: let g:diff_translations = 0 设置此变量后,重新加载比较语法脚本即可生效: set syntax=diff 自 定 义 比 较 实 现 diff-diffexpr 除了内置比较库和标准 "diff" 外部程序外,也可通过 'diffexpr' 选项自定义文件比较 方法。 E959 'diffexpr' 为空时,Vim 默认使用以下命令,比较 file1 和 file2: diff file1 file2 > outfile 重定向符 ">" 由 'shellredir' 值决定。 仅支持以下 "diff" 输出模式: 普通 "ed" 风格、不带上下文行的合并风格 (unified)。 不支持 上下文 (context) 风格,也不支持带上下文行的合并风格。因此,请勿使用 "diff -u",必须使用 "diff -U0"。 Vim 接受的 "ed" 风格比较输出范例: 1a2 > bbb 4d4 < 111 7c7 < GGG

> ggg "1a2" 项在第 1 行后追加 "bbb" 行。 "4d4" 项删除第 4 行 "111"。 "7c7" 项将第 7 行 "GGG" 替换为 "ggg"。 'diffexpr' 不为空时,指定用户自定义表达式,Vim 执行该表达式,获取满足上述格式 的 diff 文件。表达式中可使用以下预定义变量: v:fname_in 基准文件 v:fname_new 新版文件 v:fname_out 用于保存比较输出的文件位置 '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" 参数用于以二进制模式读入文件,避免 DOS 下 CTRL-Z 终止文本读入。 redraw! 命令仅用于清空命令行执行残留输出,因此仅在必要时才需要调用。 'diffexpr' 表达式以 s: 或 <SID> 开头时,会自动用脚本 ID ( local-function ) 代替 该占位符。例如: set diffexpr=s:MyDiffExpr() set diffexpr=<SID>SomeDiffExpr() 否则,表达式会在设置该选项的脚本上下文中求值,因此可以访问脚本局部项目。 E810 E97 Vim 会校验比较输出格式。如果不合法,会报错。常见原因: - 未安装或无法执行 "diff" 程序。 - "diff" 程序无法生成普通 "ed" 风格的 diff 文件 (见上)。 - 未正确设置 'shell' 及相关选项。可先用 ":!sort" 等命令测试过滤功能是否可用。 - 自定义 'diffexpr' 表达式有编写错误。 要进一步排查,可设置 'verbose' 选项值为一或更高,查看详细运行日志。 MS-Windows 版 Vim 自安装程序已附带 diff 程序。也可自行单独下载 diff.exe,如 http://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" 程序时不会产生意外副作用。例如,要留意额外生成的文件,这类文 件应当被删除。此表达式除了应用补丁生成结果文件外,不应做其他操作。 执行 'patchexpr' 前,Vim 会临时切换当前目录为 "/tmp" 或其他临时目录 tempfile , 以避免意外修补当前目录下的文件。Vim 也会自动删除以 v:fname_in 开头,以 ".rej" 和 ".orig" 结尾的文件。 'patchexpr' 表达式以 s: 或 <SID> 开头时,会自动用脚本 ID ( local-function ) 代替该占位符。例如: set patchexpr=s:MyPatchExpr() set patchexpr=<SID>SomePatchExpr() 否则,表达式会在设置此选项的脚本上下文中求值,因而可以访问脚本局部项目。 DIFF 函 数 示 例 diff-func-examples 以下是一些使用 diff() 函数计算两个字符串列表之间差异索引的示例。 " 修改多行 :echo diff(['abc', 'def', 'ghi'], ['abx', 'rrr', 'xhi'], {'output': 'indices'}) [{'from_idx': 0, 'from_count': 3, 'to_idx': 0, 'to_count': 3}] " 开头新增数行 :echo diff(['ghi'], ['abc', 'def', 'ghi'], {'output': 'indices'}) [{'from_idx': 0, 'from_count': 0, 'to_idx': 0, 'to_count': 2}] " 开头删除数行 :echo diff(['abc', 'def', 'ghi'], ['ghi'], {'output': 'indices'}) [{'from_idx': 0, 'from_count': 2, 'to_idx': 0, 'to_count': 0}] " 中间新增数行 :echo diff(['abc', 'jkl'], ['abc', 'def', 'ghi', 'jkl'], {'output': 'indices'}) [{'from_idx': 1, 'from_count': 0, 'to_idx': 1, 'to_count': 2}] " 中间删除数行 :echo diff(['abc', 'def', 'ghi', 'jkl'], ['abc', 'jkl'], {'output': 'indices'}) [{'from_idx': 1, 'from_count': 2, 'to_idx': 1, 'to_count': 0}] " 末尾新增数行 :echo diff(['abc'], ['abc', 'def', 'ghi'], {'output': 'indices'}) [{'from_idx': 1, 'from_count': 0, 'to_idx': 1, 'to_count': 2}] " 末尾删除数行 :echo diff(['abc', 'def', 'ghi'], ['abc'], {'output': 'indices'}) [{'from_idx': 1, 'from_count': 2, 'to_idx': 1, 'to_count': 0}] " 不连续的多处修改 :echo diff(['ab', 'def', 'ghi', 'jkl'], ['abc', 'def', 'ghi', 'jk'], {'output': 'indices', 'context': 0}) [{'from_idx': 0, 'from_count': 1, 'to_idx': 0, 'to_count': 1}, {'from_idx': 3, 'from_count': 1, 'to_idx': 3, 'to_count': 1}] " 不连续的多处修改,使用上下文长度 1 :echo diff(['ab', 'def', 'ghi', 'jkl'], ['abc', 'def', 'ghi', 'jk'], {'output': 'indices', 'context': 1}) [{'from_idx': 0, 'from_count': 4, 'to_idx': 0, 'to_count': 4}] vim:tw=78:ts=8:noet:ft=help:norl: