
上一期,我没有继续加功能,而是先给这个文件对比工具补了一份开发计划。
计划里的 P0 只有一件事:改掉内容对比编辑器里最难用的操作。
旧版虽然能标出左右文件的不同内容,也能向左、向右复制,但每次都要先手动选中文字。选少了,内容不完整;选多了,又可能把相邻代码一起覆盖。文件里只有一两处差异时还能忍,差异多了以后,操作比直接打开两个编辑器修改还麻烦。
所以第 6 期正式开始做 P0:让程序识别每一处连续差异,并在旁边直接放 ←、→,点击一次只处理当前这一块。
旧版的问题不在按钮,而在差异判断
旧版编辑器中间有固定的左右复制按钮。它做的事情很简单:读取当前选中的文字,再替换另一侧光标位置的内容。
这种实现写起来不难,但把最麻烦的判断留给了使用者:
- 哪几行属于同一处差异?
- 应该从哪一行选到哪一行?
- 目标一侧是替换、插入还是删除?
- 复制完成后,其他行有没有跟着错位?
更麻烦的是,旧版主要按相同行号比较。假设右侧文件中间多了一行,后面的内容即使完全相同,也可能因为行号错位而连续标红。
因此这次不能只把两个按钮挪到差异旁边。程序得先有真正的"差异块",知道左右各自对应哪些行。
先把三种操作说清楚
正式改代码前,我先确认了三种差异的方向含义。
| 差异情况 | 点击 → |
点击 ← |
|---|---|---|
| 左右都有内容,但内容不同 | 用左侧替换右侧 | 用右侧替换左侧 |
| 只有左侧有内容 | 把左侧内容插入右侧 | 删除左侧内容 |
| 只有右侧有内容 | 删除右侧内容 | 把右侧内容插入左侧 |
这里最容易误操作的是删除。
比如某几行只存在于左侧,点击 ← 的结果不是"从右侧复制",而是让左侧与右侧保持一致,也就是删除左侧这几行。如果所有按钮都是同样的蓝色,使用时很容易顺手点错。
最终方案把会产生删除的箭头改成红色。普通替换和插入继续使用蓝色,方向不变,但风险一眼就能看出来。
第一版 UI 方案
编辑器改成左、中、右三栏:
- 左边显示左侧文件。
- 右边显示右侧文件。
- 中间是固定的差异操作轨道。
- 每个连续差异块只显示一组
←、→。 - 一侧缺少对应内容时,显示"此处无对应内容"的占位行。
- 整个文件覆盖操作移到底部,不再和单个差异块混在一起。
当时先做出的效果图如下:

这张图确认了主要布局,但删除操作还有一个实际问题:如果每次点红色箭头都弹窗确认,连续处理十几个差异块会很烦;如果完全不提示,又容易误删。
最后采用了一个折中方案:
- 删除差异块时默认弹出确认。
- 确认框里可以勾选"下次不再提示删除确认"。
- 这个选择只在本次程序运行期间有效。
- 随时可以从"编辑"菜单重新勾选"删除差异块前确认"。
- 程序重启后自动恢复默认确认。

这样第一次操作有保护,确认自己理解方向后,也不必反复点击同样的弹窗。
差异算法没有自己硬写
差异块是这次功能的基础。它至少要正确处理:
- 普通行修改。
- 左侧新增。
- 右侧新增。
- 文件开头和结尾的差异。
- 空文件。
- 连续多行修改。
- 重复行附近的插入和删除。
这些情况看起来只是比较字符串,但真正麻烦的是行如何对齐。自己写一个"从上往下找不同"的算法,很容易在重复行和中间插入时选错位置。
这次没有继续坚持纯 JDK,而是使用了 java-diff-utils 4.12。它提供了成熟的 Myers 行差异算法,支持 Java 8,许可证是 Apache License 2.0,也没有额外运行时依赖。项目里直接带上 JAR,启动脚本把 lib 加入类路径即可。
这类基础算法没有必要为了"自己写"再造一遍。工具真正需要自己控制的是差异块怎样展示、点击箭头后怎样修改文件,以及怎样防止误操作。
代码先拆模型,再接 Swing
原来的内容编辑器代码都写在主窗口类里。如果继续往里面增加差异算法、对齐行、箭头、撤销和删除确认,文件只会越来越难改。
这次先把核心逻辑拆成几块:
DiffEngine:计算左右文件的差异。DiffHunk:记录一个连续差异块的类型、位置和内容。LineDocument:保留文本行、LF/CRLF 和末尾换行状态。HunkApplyService:把指定差异块应用到左边或右边。DiffAlignmentService:生成左右等高的显示行和占位行。DiffEditorFrame:负责三栏界面、按钮、编辑、保存和撤销。
先有模型的好处很直接:差异算法和文件修改可以脱离界面测试。即使 Swing 窗口还没写完,也能先确认点击某个方向后,最终文本到底对不对。
为什么最终用了对齐表格
方案阶段考虑过使用两个 JTextPane,在文本中插入不可保存的占位行,再把中间按钮定位到对应段落。
实际往下推时,这种方式有两个麻烦:
- 占位内容混在编辑文档里,保存前必须保证它一定被剔除。
- 用户手动增加或删除一行后,按钮位置和占位位置都要重新映射。
最终编辑区改成左右两个对齐表格。每一行背后都保存真实文件行号;没有内容的一侧使用只读占位单元格。中间轨道和左右表格使用同样的固定行高,因此插入、删除后仍然能保持对齐。
真实行可以双击编辑,也可以通过右键菜单插入或删除一行。占位行只负责显示,不会写入文件。
这不是为了把界面做成表格,而是为了明确区分"真实文件内容"和"为了对齐而显示的内容"。
点击一个箭头后发生了什么
以 → 为例,实际流程是:
- 读取当前差异块在左右文件中的行范围。
- 判断操作会产生替换、插入还是删除。
- 如果会删除右侧内容,根据当前设置决定是否确认。
- 把右侧修改前的文本放入撤销记录。
- 只替换当前差异块对应的右侧行。
- 重新计算全部差异块和对齐行。
- 保持滚动位置,刷新中间箭头。
- 标记右侧文件"已修改",但暂时不写磁盘。
一次差异块操作只产生一条撤销记录,按 Ctrl+Z 可以整体撤回。只有点击保存后,修改才真正写入文件。
手动编辑后不会立即在界面线程里反复计算,而是等待约 300 毫秒,再在后台重新计算差异。这样连续输入时不会每敲一个字都重排整个编辑器。
真实截图还是发现了问题
功能编译通过、测试通过后,我用三类差异生成了一张真实 Swing 窗口截图。
第一张截图里,代码正常显示,但"此处无对应内容"变成了几个方框。原因是占位行错误地沿用了 Consolas,当前系统没有正确回退到中文字库。
最后把代码行继续保留为等宽字体,占位提示单独改成微软雅黑,重新截图后才正常。
代码复查时还发现另一个边界:左右都有内容但行数不同的 CHANGE 差异,也可能产生删除。最初的红色判断只覆盖"仅左侧"和"仅右侧",会漏掉这种不等长替换。后来把判断改成比较左右差异块行数,并补了一条回归测试。
这两个问题都不是编译器能发现的。一个只能从真实界面看出来,另一个需要把交互语义重新代入边界场景。
最终运行效果
最终编辑器如下:

现在三种差异都能直接处理:
- 内容不同:点击方向按钮替换当前块。
- 左侧独有:向右插入,或者从左侧删除。
- 右侧独有:向左插入,或者从右侧删除。
两侧纵向滚动仍然联动,长代码可以各自横向滚动。整文件复制、分别保存、全部保存和重新加载也都保留了。
相比旧版,最大的变化不是少选了几次文字,而是不用再自己判断目标位置。程序已经把属于同一处的差异放在了一行操作轨道上。
这次实际修改了哪些内容
1. 差异模型
- 引入 Myers 行差异算法。
- 支持修改、左侧独有、右侧独有三类差异。
- 记录左右起止行和差异内容。
- 保留 LF、CRLF 和文件末尾换行状态。
2. 编辑器界面
- 改成左右文件加中间操作轨道的三栏结构。
- 差异行使用红色背景,相同行使用浅绿色背景。
- 缺失位置显示不可编辑的占位行。
- 每个连续差异块显示一组
←、→。 - 双击真实行可以编辑,右键可以插入或删除行。
3. 操作安全
- 删除型箭头标红。
- 删除前默认确认。
- 支持本次运行期间不再提示。
- 支持从"编辑"菜单恢复删除确认。
- 每次差异块操作可以通过
Ctrl+Z撤销。 - 未保存关闭和重新加载仍然需要确认。
4. 构建与依赖
- 增加
java-diff-utils 4.12。 - 更新
run.bat的编译和运行类路径。 - 增加第三方依赖说明。
- 保持 Java 8 兼容。
5. 测试
- 修改、插入、删除差异识别。
- 重复行和空文件。
- 差异块左右应用结果。
- 占位行对齐。
- LF、CRLF 和末尾换行。
- 不等长修改块的删除提示。
- 原有过滤规则回归。
- 真实 Swing 窗口截图检查。
最终版提示词
如果要在一个现有 Java Swing 文件对比工具中实现同类功能,可以使用下面这份提示词:
text
请继续开发一个现有的 Java 8 Swing 文件对比工具。工具已经支持文件和目录
SHA-256 对比、可折叠目录树、过滤规则、左右同步、F5 刷新、联动滚动和
双击打开内容编辑器。
本次实现 P0"差异块级双向操作",替换当前依赖手动选中文字再复制的方式。
功能要求:
1. 使用可靠的行差异算法识别连续差异块,覆盖 CHANGE、仅左侧、仅右侧。
2. 每个差异块记录左右起止行和内容,不能继续按相同行号简单比较。
3. 编辑器改成左侧文件、中间差异操作轨道、右侧文件三栏结构。
4. 左右显示行必须对齐;一侧缺失内容时显示只读占位行,占位文字绝不能保存。
5. 每个连续差异块在中间显示一组 ←、→,不需要先选择文字。
6. → 表示让右侧当前差异块与左侧一致;← 表示让左侧与右侧一致。
7. 修改、插入、删除三种语义必须正确。任何会减少目标侧行数的操作都属于删除。
8. 删除型箭头使用红色,普通替换和插入使用蓝色,并提供清晰的悬浮说明。
9. 删除前默认确认,确认框增加"下次不再提示删除确认"。
10. 关闭提示只在当前程序会话生效;编辑菜单提供可勾选的
"删除差异块前确认",程序重启后恢复默认确认。
11. 每次差异块操作只修改当前块,不能影响其他差异;操作后重新计算差异。
12. 每次差异块操作作为一个撤销单元,支持 Ctrl+Z。
13. 保留双击编辑、手动插入删除行、未保存状态、分别保存、全部保存、
重新加载和整文件双向复制。
14. 左右纵向滚动保持联动,横向滚动互不影响。
15. P0 继续使用 UTF-8,但必须保留 LF/CRLF 和文件末尾换行状态。
16. 差异计算放到后台执行,手动编辑使用短延迟合并刷新,避免阻塞 Swing EDT。
17. 优先把差异算法、差异块应用和行对齐拆成独立模型,不要继续堆在主窗口类中。
18. 引入第三方差异库前检查 Java 8 兼容性、许可证和运行时依赖,并更新启动脚本。
19. 增加回归测试,至少覆盖修改、插入、删除、重复行、空文件、文件首尾、
不等长差异、左右应用、占位对齐、LF/CRLF 和末尾换行。
20. 完成后编译全部源码和测试,运行真实 Swing 界面并生成截图检查字体、对齐、
箭头颜色、最小窗口和按钮文字。
修改前先阅读现有代码和开发计划,给出执行计划、交互方案和 UI 效果图。
我确认后再修改正式代码。实现完成后更新 README、开发计划状态和第三方依赖说明。
下一步
P0 已经完成,开发计划中的下一项是 P1:编码与换行保真。
当前编辑器仍然按 UTF-8 读取和保存文件。遇到 GBK、GB2312 或 UTF-16 文件时,打开后可能乱码,直接保存还可能破坏原文件。
下一步需要先做编码识别,再记录每一侧文件的原始编码、BOM 和换行方式,保存时尽量保持不变。同时还要处理识别不确定时的提示,不能只凭一次猜测直接覆盖。
之后再做同步预览和备份。目录同步涉及覆盖文件,真正用于项目之前,最好先明确展示将新增和覆盖哪些内容,并保留失败恢复能力。
最后
这一期终于把开发计划里的第一个核心问题解决了。
以前看到一处差异,要先判断范围、选中文字、找到目标位置,再点复制;现在程序先把差异对齐,操作的人只需要决定方向。
这也是工具从"功能能跑"到"操作顺手"的区别。很多时候并不是少一个按钮,而是程序有没有替使用者完成本来就能自动完成的判断。
不过现在的使用体验仍然谈不上成熟。编辑器目前是行级对比,还没有字符级高亮;常见中文编码没有识别;大文件差异计算、快捷键和差异导航也还有优化空间。
后面会继续按计划往下做,不再临时想到什么就加什么。下一期先处理编码问题,继续记录方案、提示词、实现过程和实际效果。