前阵子帮某个课题组整理论文返修稿导师的要求很简单在最终 PDF 里把这次修改过的内容全部标出来方便审稿人快速定位。第一反应是手动高亮可真到动手才发现十几个文件、三四个章节来回改手动标注基本是场灾难漏标错标是常态。后来我把目光放到了 VSCode 和 latexdiff 这套组合上折腾了几天终于理顺了一条稳定可复现的流程。如果你也在写 LaTeX 论文、经常遇到“请标注修改痕迹”这类需求那这篇文章应该能帮你省下不少时间。先说说什么场景适合这套方案你有一个修改前的旧版 tex 文件又有一个修改后的新版 tex 文件你想生成第三份“标注差异”的 tex 文件并编译成 PDF。新版里的新增内容用高亮或下划线标出删除内容用删除线或特殊颜色标出审稿人一眼就能看出哪个部分动了。vscodelatexdiff 的核心就是用 VSCode 做编辑和任务调度用 latexdiff 做差异分析本质上是一条本地命令行工作流不需要什么特殊服务器也不需要上传文件到任何地方。1. 为什么需要 latexdiff从普通 diff 到“懂 LaTeX 的差异比较”如果只是单纯看两个纯文本文件差在哪Git 自带的 diff 或者 VSCode 里的文本比较器就够了。问题是 LaTeX 源码并不是普通文本它里面有命令、环境、分组、注释和各种数学符号。大部分情况下一个段落就是很长的一行你改了一个词普通的行级 diff 会把整行标成“替换”输出的结果基本没法看既分不清具体哪里变了也很难拿去做成标注 PDF。latexdiff 的思路完全不同。它不是按行比较而是先把两个 tex 文件拆成逻辑单元识别出普通文本、LaTeX 命令、数学环境、浮动物体等内容再在单元级别做差异匹配最后生成一个新的 tex 文件。这个新文件会在导言区自动补充一些宏定义正文里把进入的修改内容包装进类似\DIFadd和\DIFdel的宏里。你拿去正常编译PDF 里就会呈现颜色和下划线效果。换句话说latexdiff 输出的是“合法可编译的 tex 文件”而不是给你一个截图或者一个纯文本补丁。这也是 latexdiff 和普通 diff 最本质的区别普通 diff 强调“最小改动”而 latexdiff 强调“可编译的 LaTeX 文档”。理解这一点之后很多使用上的困惑就会迎刃而解。比如它为什么要求两个文件都能独立编译因为只有结构完整、命令正确它才能正确识别分组的边界输出才不会破坏原始的 LaTeX 结构。如果某个文件本身编译就报错latexdiff 的行为很可能是不可预期的。另外我想强调一下latexdiff 并不需要你安装额外的 TeX 引擎它本质上是一个 Perl 脚本只是读取 tex 文件、做文本分析、输出新的 tex 文件。真正去编译生成 PDF 的还是你本地的 xelatex、pdflatex 或 latexmk。所以如果你已经有一个能跑通的 LaTeX 环境那 latexdiff 的门槛很低。这套方案适合谁适合经常写论文返修稿的研究生和科研人员适合需要给导师交标注版 PDF 的人也适合期刊投稿时提交“highlighted changes”版本的人。它不要求你会写 Perl 脚本也不要求你理解解析原理只要会调用命令、会看输出结果就行。2. 环境准备与 VSCode 终端里的第一版对比大多数完整的 TeX 发行版都已经内置了 latexdiff。我用的是 TeX Live装完之后直接打开终端输入latexdiff就能用。MiKTeX 用户一般也没问题如果提示找不到命令先检查有没有把发行版的可执行目录加到 PATH 里或者重新运行一次安装包更新。验证方法很简单latexdiff --version能看到版本号就说明环境没问题。接下来找一个最小项目跑通流程。假设你有一个old.tex是修改前版本main.tex是修改后版本最简单的对比命令是latexdiff old.tex main.tex main_diff.tex这会把差异结果写到main_diff.tex然后正常编译xelatex main_diff.tex打开生成的 PDF你会看到被删除的内容是红色删除线新增的内容是蓝色下划线。第一次看到这个效果时还是挺有成就感的好像自己什么都没做一份规范的标注稿就出来了。但我强烈建议你第一次尝试时不要直接上大型项目而是先拿一个只有几行文字的测试文件跑跑看确认输出 PDF 能正常编译再往真实项目上套。道理很简单大项目如果有编译报错你很难判断是 latexdiff 的问题还是你原有文档的问题。如果你是在 Windows 上使用 PowerShell还要注意一个细节PowerShell 的重定向符号默认编码可能是 UTF-16某些版本下会把main_diff.tex写成带 BOM 的格式导致后续编译乱码或者 latexdiff 二次处理时出错。我踩过这个坑后来干脆在 Git Bash 里跑命令或者在 VSCode 里把默认终端切到 Git Bash用 bash 的重定向编码问题会少很多。第一次跑通之后你会注意到main_diff.tex的导言区多了一段类似下面的代码\providecommand{\DIFadd}[1]{{\protect\color{blue}#1}} \providecommand{\DIFdel}[1]{{\protect\color{red}\strikeout{#1}}}这段代码就是 latexdiff 自动插入的宏定义用来控制新增和删除内容在 PDF 里的样式。如果你对默认样式不满意后面可以用--type参数调整或者直接在main_diff.tex里改这几个宏但我不建议手工改生成文件因为一旦重新运行 latexdiff改动又会被覆盖。更合理的做法是用--type参数统一控制或者写一个人~/.latexdiffrc配置文件。3. 子文件项目、中文文档与样式选择三个绕不开的实际问题在真实论文项目里几乎没有人把所有内容都塞进一个 tex 文件。通常主文件用\input或者\include引入各个章节、图片、表格文件。如果你直接对两个主文件跑 latexdiff它默认不会深入子文件内容只把引入指令当作普通命令处理比对出来的结果会非常粗糙甚至只显示主文件里那几行\input没有变化等于白做。解决办法是加--flatten参数latexdiff --flatten old/main.tex main.tex main_diff.tex这个参数会把所有通过\input和\include引入的内容内联进主文件再执行差异比较。我实际用下来的感受是处理大型项目时--flatten的效果比较可靠子文件里细小的文字改动也能被正确捕捉。但也有一个副作用生成的main_diff.tex会把原来拆分清晰的章节全部揉在一个文件里行数可能很大编译速度会有所下降整体还能接受。如果你希望 diff 结果仍然保持原来的子文件结构可以研究更复杂的 latexdiff-vc 用法但对大多数返修场景--flatten已经够用且省心。第二个绕不开的问题是中文文档。现在用 XeLaTeX 加 ctex 宏包处理中文已经是主流方案这种情况下 latexdiff 一般能正常工作。我自己的经验是把两个文件都统一保存为 UTF-8 无 BOM 格式文件路径也不要出现中文名。曾经遇到过一个文件是 GBK 编码diff 过程中正则匹配出了奇怪的错误把源文件转成 UTF-8 之后问题消失。如果你手里的文件是 GBK可以先用工具转码或者干脆在编辑器里另存为 UTF-8。如果你用的是老式\begin{CJK}环境配合 pdflatex那兼容性会差很多latexdiff 很可能在 CJK 环境边界上卡住或者产生误报。我的建议是能迁移就迁移到 ctex XeLaTeX这不仅是 latexdiff 的问题也是长期写中文论文更稳妥的组合。第三个问题是用什么输出样式。latexdiff 默认的类型是 CFONT也就是我前面说的红色删除线加蓝色文字。如果文档要打印出来彩色打印不一定方便或者有的导师就是不喜欢花里胡哨的 PDF你可以换成 UNDERLINE 类型新增内容用下划线删除内容用删除线整体是黑白风格打印友好。还有一种 WHOLE 类型整段整段的显示新增和删除适合大段改动时给人看。我个人一般分场景用样式类型显示效果适用场景CFONT 默认红色删除线、蓝色新增屏幕审阅、电子标注UNDERLINE下划线 删除线打印纸质稿、正式提交WHOLE整段色块化显示大段改写、快速浏览差异命令也很简单latexdiff --typeUNDERLINE old.tex main.tex main_diff.tex如果你每次都想固定用某一种样式可以在用户目录下建一个~/.latexdiffrc文件写入你常用的默认参数。我自己的配置是--typeUNDERLINE --mathmarkupoff这样每次运行 latexdiff 不用重复敲参数但前提是你明白这两行配置具体做了什么否则不知道自己在用什么规则做差异标记。配置文件的生效范围是当前用户换一台电脑跑项目时还得记得把这些配置带上。4. 把对比流程固化到 VSCodeTasks、快捷键和一个脚本命令行用了几次之后我发现自己越来越懒尤其是每次返修都要重复敲同一串命令。于是我把整套流程固化到了 VSCode 里用一个任务脚本解决。思路是这样的写一个简单的 bash 脚本把旧的、新的、输出文件路径都当参数传进去脚本内部依次执行 latexdiff 和编译命令然后在 VSCode 里配置一个 Task按快捷键就能跑跑完直接用 LaTeX Workshop 的 PDF 预览查看标注效果。先看脚本长什么样我把它放在项目根目录的scripts/latexdiff-wrapper.sh里#!/usr/bin/env bash set -euo pipefail OLD${1:-old/main.tex} NEW${2:-main.tex} OUT${3:-main_diff.tex} latexdiff --flatten --mathmarkupoff --typeCFONT $OLD $NEW $OUT latexmk -xelatex -interactionnonstopmode $OUT脚本本身不复杂关键是--flatten和--mathmarkupoff这两个参数对于子文件项目和公式较多的论文来说算是返修标配。latexmk -xelatex会顺手把生成的main_diff.tex编译成 PDF执行完直接在当前目录看到main_diff.pdf。如果你是纯 Windows 且不想装 Git Bash可以改成 PowerShell 版本但要注意重定向编码问题所以我还是推荐在 VSCode 里把默认终端设置为 Git Bash 或 WSL。然后我在.vscode/tasks.json里注册这个脚本{ version: 2.0.0, tasks: [ { label: latexdiff: 生成修订稿, type: shell, command: bash, args: [ ${workspaceFolder}/scripts/latexdiff-wrapper.sh, ${input:oldTex} ], options: { cwd: ${workspaceFolder} }, problemMatcher: [], group: { kind: build, isDefault: true } } ], inputs: [ { id: oldTex, type: promptString, description: 请输入旧版本 tex 文件的路径相对项目根目录, default: old/main.tex } ] }这里做了一个交互式输入每次运行任务时VSCode 会弹出一个输入框让我填旧版本文件路径。虽然多数时候old/main.tex是默认值但偶尔我会拿某个中间版本做对比动态输入路径就很有用。如果你觉得交互式输入太啰嗦也可以在args里写死路径一劳永逸。我倾向保留输入框因为不同返修轮次对比的基础版本往往不同。绑定快捷键也不难在.vscode/keybindings.json里加一条{ key: ctrlshiftd, command: workbench.action.tasks.runTask, args: latexdiff: 生成修订稿 }我选择的ctrlshiftd在部分版本里可能已有其他用途如果你的按键冲突换成ctrlaltd或者别的组合都行。有了快捷键之后整个流程就变成编辑完正文 → 按下快捷键 → 弹出旧文件路径 → 回车 → 等脚本跑完 → 预览 PDF。手都不用离开键盘体验比反复开终端敲命令强很多。还有一个细节如果你已经装了 LaTeX Workshop 扩展它会接管.tex文件的编译和预览。我们的 Task 脚本里用latexmk -xelatex编译main_diff.tex生成 PDF 之后LaTeX Workshop 的预览面板不一定自动打开你可以在文件树里直接点开main_diff.pdf或者配置脚本执行完再调用一次打开命令。我个人习惯是脚本只负责生成文件预览交给 VSCode 手动点这样任务执行失败时不会把 PDF 面板搞得一团糟。5. 踩坑实录数学公式误报、caption 报错与输出位置问题latexdiff 在真实项目里远没有想象中完美尤其是在数学公式多的论文里。我刚跑通第一个真实项目时发现公式区域经常出现奇怪的“新增”和“删除”比如一个原本没改动的公式只是我调整了前后文的一个逗号latexdiff 却把整个公式标记成修改PDF 里公式颜色变得一片蓝红看得人头皮发麻。原因大概是数学环境内部的分组和命令被 diff 引擎拆分之后默认的--mathmarkup行为会把差异范围扩展得很大。解决这个问题最直接的就是加--mathmarkupoff让 latexdiff 不对数学环境内部做精细标记改动检测集中在普通文本层。代价是公式里如果真的有重大改动不会像文本那样高亮得那么明显但至少不会出现大范围误报。在返修稿场景里公式被改动通常可以从上下文文字变化中推断出来所以我认为这个取舍很划算。另一个高频报错是 caption 相关的问题表现就是编译main_diff.tex时出现类似Argument of \caption has an extra }的错误。这种错误多半是 latexdiff 把\DIFadd和\DIFdel塞进了浮动体环境里的短参数造成分组失衡。我遇到的具体场景是我把某个图的说明文字从一句话改成两句话latexdiff 正确地检测到了 caption 里多出的内容但它在 caption 参数内部插入宏时和原文档里的某些命令配合出了问题。处理这种 caption 报错我最后的方案是调整改法如果只是替换图片文件caption 文本尽量保持原样如果确实要改 caption 文字我会把变化拆成“新增一张图”和“修改说明”两处让 latexdiff 不在同一个浮动物体内部做复杂的分组嵌套。如果你不想改文档结构更硬核的办法是手工打开main_diff.tex找到出错位置把 DIF 宏调整到合适位置因为生成文件本身是允许手动微调的。但这只在极少数情况下值得做毕竟每次重新运行 latexdiff 就会覆盖手工修改。第三个坑很容易被忽略输出位置。我一开始习惯把main_diff.tex输出到一个专门建的diff/目录里想保持主目录整洁。结果编译时图片引用直接找不到了因为原来的相对路径都是基于主文件所在目录的diff/目录下编译时相对路径全乱。后来我干脆把main_diff.tex和main_diff.pdf都输出到主文件同一目录图片、参考文献、样式文件全部正常目录里多几个文件忍忍就算了。这也解释了为什么我在脚本里的输出路径默认就是根目录下的main_diff.tex而不是放进子目录。如果你确实希望把这些生成物集中管理一个可行的做法是不要改主文件位置而是改\graphicspath或者编译时的-cd参数但这些调整会带来额外的心智负担在返修场景里不划算。我的经验是diff 生成物和源文件放一起命名区分清楚比如main_diff.tex然后把它加进.gitignore避免后面的 Git 比较又把它当成新版文件。还有一个小问题是 Perl 输出的警告信息比如Use of uninitialized value这种通常不影响最终结果只要 PDF 能正常编译出来就可以忽略。但如果 LaTeX 日志里出现了\DIFadd相关错误那说明确实有结构性问题不要强行编译到底应该回头检查差异区域的内容。6. 进阶结合 Git 历史版本让返修对比自动化当返修进行到第二轮、第三轮时我发现自己很难记得“第一轮提交的版本”到底对应哪个文件。与其手动复制旧版本不如让 Git 替我保管。如果你的项目已经在用 Git 且每次返修前都打了 tag 或者 commit那 latexdiff 结合 Git 使用会非常顺滑。latexdiff 自带一个配套脚本latexdiff-vc专门用来做版本控制对比。基本用法是latexdiff-vc --git -r HEAD~1 --flatten main.tex这个命令会把 HEAD~1 这个历史版本和当前工作区版本的 main.tex 做差异比较自动生成类似main-diff.tex的文件并尝试用你的 LaTeX 工具链编译成 PDF。用起来比手动保存旧版本省事太多尤其是改动比较分散、提交比较频繁的时候。如果你是 SVN 用户latexdiff-vc 也提供了--svn选项用法大同小异。我个人的习惯仍是把这一层封装进 VSCode Task。可以写一个独立脚本比如scripts/git-diff-wrapper.sh#!/usr/bin/env bash set -euo pipefail latexdiff-vc --git -r HEAD~1 --flatten main.tex然后在 tasks.json 里再注册一个任务快捷键可以设成ctrlshiftg之类。这样写论文时每完成一轮修改我只需要提交一次 Git然后按快捷键就能拿到一份和上一版本对比的标注 PDF。这个流程对我的返修体验是一次质的提升因为我不再需要手动归档旧版本也不怕某个中间版本被覆盖。不过在自动化过程中要留意latexdiff-vc生成的文件名和普通 latexdiff 不太一样可能会在main-diff.tex和main.tex之间多出一些缓存文件。我建议大家专门在.gitignore里把*-diff.tex和对应的 PDF 文件忽略掉否则 Git 状态会变得很混乱你自己也会分不清哪个是真正要提交的版本。另外有一个反复踩到的坑当main_diff.tex放在项目根目录时千万不要顺手把它当成新版本文档去比较。我曾经有一次返修第二轮时忘了指定旧版本路径把我的main_diff.tex当成新版文件同main.tex比较结果 diff 出来的内容乱七八糟逻辑完全乱套。后来我把所有生成产物统一命名为带_diff后缀的文件并且在 Git 里忽略它们才解决了这个问题。7. 一点个人心得如果让我给刚开始使用这套流程的人一个建议那就是不要跳过最小实验。先用一个几行的测试文件跑通 latexdiff再上真实项目能省掉大量排查时间。真正常见的错误无非那么几类文件编码不一致、路径不对、数学环境误报、浮动体 caption 被破坏。把这些规律摸清楚之后每次返修就是按快捷键的事。最后补充一个工作流上的小技巧导师或者审稿人想要的是“哪些地方变了”而不是“你改了几行代码”。所以当你用 latexdiff 生成标注稿后不妨在邮件里附一句“新增内容为下划线或高亮删除内容为删除线”并且提醒他们直接看编译后的 PDF而不是去阅读main_diff.tex源码。这个简单的提醒能省下很多来回沟通。整个 latexdiff 流程跑顺之后你会发现返修稿的“标注”不再是负担而是一个两三分钟就能完成的标准动作。