diff

diff.txt 适用于 Vim 9.0 版本。 最近更新: 2023年2月 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 选项 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 将依据第一个参数所指定的文件名在指定目录 中查找另外的文件名。 缺省使用内部 diff 库。如果设置了 'diffopt''diffexpr',则使用外部的 "diff" 命令。只有这样的 diff 程序可用的时候才行。 窗口比较局部于当前标签页 tab-page 中。你不能看到某窗口和别的标签页中的窗口的 差异。这样,可以同时打开多组比较窗口,每组差异在单独的标签页中。 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 提供了反转这样选项的简便办法。 比较基于缓冲区的内容。因而,如果在载入文件后你做过改动,这些改动也将参加比较。 不过,你也许要不时地使用 "diffupdate"。因为并非所有的改动的结果都能自动更新, 尤其是使用外部 diff 命令的时候。 在 .vimrc 文件中,你可以用以下的结构,对比较模式做专门的设定: if &diff setup for diff mode else setup for non-diff mode 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, 以避免当前目录下的文件被意外的打补丁。但它仍不 能避免若干 ".rej" 文件产生。当绝对路径名出现时,这些文件也不能 避免被打补丁。 要垂直分割窗口,在前面加上 :vertical 。例如: :vert diffsplit main.c~ :vert diffpatch /tmp/diff 如果你偏好垂直分割,可以在 'diffopt' 中包含 "vertical"。 E96 最多可以对八个缓冲区设置 'diff' 模式。 因为选项的值是由缓冲区记录的,在编辑另一个文件后又回到此文件时,你仍会处于比较 模式。 :diffo :diffoff :diffo[ff] 为当前窗口关闭比较模式。即使 'diff' 没有置位,也复位相关选项。 :diffo[ff]! 为当前窗口和当前标签页所有置位 'diff' 的窗口关闭比较模式。只在 在置位 'diff' 的窗口中才复位相关选项,如果当前窗口没有置位 'diff',不改变其任何选项。 隐藏缓冲区也从参于比较的缓冲区列表中删除。 :diffoff 命令复位相关选项到启用 :diffsplit:diffpatch:diffthis 之 前或以比较模式启动 Vim 时的值。 使用 :diffoff 两次恢复再上次的保存值。 否则复位其默认值如下: 'diff' 关闭 'scrollbind' 关闭 'cursorbind' 关闭 'scrollopt' 没有 "hor" 'wrap' 打开,'diffopt' 包含 "followwrap" 时保持不变 'foldmethod' "manual" 'foldcolumn' 0 'foldenable' 极有可能复位为关闭。这是 'foldmethod' 恢复为 "manual" 时进行的。 折叠本身虽然并不清除,但不应显示,复位 'foldenable' 是最好的办法。

2. 查看比较结果 view-diffs

比较产生的结果是,不同比较窗口显示同一文件不同版本,高亮标出差异的部分。当滚动 文本时,'scrollbind' 选项将使得另一窗口的相应文本也随着滚动。垂直分割时,文本 会恰当地对齐。 不过,以下情况将使文本对齐出现问题: - 当设置了 'wrap' 后,一些行将被自动换行,在屏幕上占用两行以上。 - 一个缓冲区打开了折叠,另一个却没有。 - 'scrollbind' 被关闭 - 文本做过改动 - 'diffopt' 中没有 "filler",插入或删除的行会使对齐出错 所有打开 'diff' 选项并在某一个窗口编辑的缓冲区都将参与比较。即使对于一个隐藏的 缓冲区也是如此,如果它曾在一个窗口内被编辑过的话。要去除隐藏缓冲区,用 :diffoff! :DiffOrig diff-original-file 因为 'diff' 是一个局部于窗口的选项,所以同一缓冲区可以在某一个窗口里是比较模 式,而在另一个窗口里则是普通模式。你也可能想查看对一缓冲区在读入文件后所做过的 改动。既然 Vim 不允许同一个文件有 2 个缓冲区,你需要另一个缓冲区。可用下面的命 令: 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 那样。 一些行在本窗口没有,但却在一个窗口里出现。它们或者是在那个文件里新插入的,或者 是在本文件里刚删除的。对这些行,除非 'diffopt' 选项中不包含 "filler",Vim 将在 本窗口对应的位置显示填充行。 折叠可以用来隐藏那些没有被更改过的文字。要知道所有用于折叠的命令,可以参考 folding'diffopt' 选项还可以用来设置在差异文之前不被折叠的行数 (上下文)。如要把上下文 设为 3 行: :set diffopt=filler,context:3 以下的语法高亮群组可以用来显示比较的结果: hl-DiffAdd DiffAdd 添加 (插入) 的行。这些行只存在于本缓冲区里。 hl-DiffChange DiffChange 改动过的行。 hl-DiffText DiffText 在改动行中被更改的文本。Vim 找到第一个和最后一 个不同的字符 (从行末开始搜起) 之间的文字,包括 其实没被改动的部分,都被高亮。这里只受到 'diffopt' 中的 "iwhite" 和 "icase" 标志位的影 响。 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] 时,Vim 试图仅改动它指定的行。不过,当有被删除的行时,这不总有 效。 可能在最后一行之后有删除的行。要从另一个缓冲区中取得那些行,可使用最后一行的行 号加一来实现。以下命令从另一个缓冲区中得到所有的差异文本: :1,$+1diffget 注意: 被删除的行会被显示,但不作为文本行看待。你也不能将光标移至其中。要用另一 个缓冲区来填充被删除的行,可在其下一行用 ":diffget"。 E787 如果要修改的缓冲区只读,而 FileChangedRO 激活的自动命令要修改缓冲区,该命令 会失败。此时,自动命令不能修改缓冲区。 参数 [bufspec] 可以是缓冲区的序号,匹配缓冲区名称或缓冲区名称的一部分的模式。 例如: :diffget 使用另一个进入比较模式的缓冲区 :diffget 3 使用 3 号缓冲区 :diffget v2 使用名字同 "v2" 匹配的缓冲区,并进入比较模式 (例如,"file.c.v2")

5. diff 选项 diff-options

也可参考 'diffopt''fillchars' 中的 "diff" 项。 diff-slow diff_translations 比较语法高亮对很长的行可能会较慢,尤其是因为它试图匹配各种本地化的文本。要关闭 本地化并提高语法高亮速度,设置全局变量 g:diff_translations 为零: let g:diff_translations = 0 设置此变量后,重新载入语法脚本: set syntax=diff 查 找 不 同 diff-diffexpr 'diffexpr' 选项可以用来设定不同于内部 diff 支持或标准 "diff" 程序的方法来比较 文件间的异同。 E959 'diffexpr' 为空的时候,Vim 使用以下命令在 file1 和 file2 中查找不同之处: diff file1 file2 > outfile 其中的 ">" 会用 'shellredir' 的值替换。 "diff" 的输出必须是普通的 "ed" 风格或合并风格 (unified) 的 diff。 不能 用上下 文 (context) 风格的 diff。而且合并风格的 diff 不能指定上下文行。"diff -u" 行,要用 "diff -U0"。 下面就是一个 Vim 所期望的 "ed" 风格的范例: 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" 参数使得文件以二进制模式读入,这样在 DOS 上 CTRL-Z 就不会结束文本。 redraw! 命令不一定必要,取决于执行外壳命令时是否会在屏幕上显示东西。 如果 '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' 选项为一或更高的值,以获得更多 的信息。 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" 或别的临时目录。这是为 了让当前目录下的文件不被意外的打补丁。Vim 也将删除以 v:fname_in 开始以 ".rej" 和 ".orig" 结尾的文件。 如果 'patchexpr' 表达式以 s: 或 <SID> 开始,用脚本 ID ( local-function ) 代 替之。例如: set patchexpr=s:MyPatchExpr() set patchexpr=<SID>SomePatchExpr() 否则,表达式在设置此选项的脚本的上下文内进行计算,这样保证了局部于脚本的项目可 用。 vim:tw=78:ts=8:noet:ft=help:norl: