# DFDiff **Repository Path**: penngo/dfdiff ## Basic Information - **Project Name**: DFDiff - **Description**: DFDiff是一款使用Java Swing开发的,并使用Graalvm编译成原生应用的文本处理工具,提供文本编辑和文本对比功能。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 26 - **Forks**: 5 - **Created**: 2023-05-08 - **Last Updated**: 2026-06-29 ## Categories & Tags **Categories**: filemanager **Tags**: None ## README # DFDiff:用 Java Swing 构建的桌面文本编辑器 --- **人工备注说明** - 开发工具Claude Code + (火山方舟)glm-5.2; - 这项目是让AI重写DFDiff这个工具,大概花1小时完成,目前已完成90%功能; - 这篇文章也是使用Claude Code编写; --- > DFDiff 是一个用纯 Java Swing 写的桌面文本编辑器,定位是「轻量、可扩展、对开发者友好」。这篇文章记录 DFDiff 用到的技术栈、整体架构设计,以及如何从零编译运行它。 ## 一、功能 DFDiff 目前的能力清单: - **多标签 + 多窗口**:标签可以拖出成独立窗口,再拖回合并;多窗口共享同一套命令、主题、最近文件。 - **代码编辑内核**:语法高亮、行号 gutter、代码折叠、括号匹配、`Ctrl+D` 多光标累加选区、查找替换、跳行。 - **侧栏文件树**:多文件夹根、`.gitignore` / 内置规则过滤、右键菜单(新建/重命名/删除/复制路径/在资源管理器显示)。 - **项目/工作区**:多根文件夹持久化为 `.dfdiff-project`,启动自动恢复上次项目。 - **文件夹对比**:左右两栏树表,缺失侧空白占位对齐、差异向父目录汇总、左右滚动与展开同步。 - **文件文本差异对比**:左右双栏,显示**完整文档**并把差异行高亮,支持 `+ / - / U` 状态标记。 - **命令面板**:`Ctrl+P` 文件、`Ctrl+Shift+P` 命令,和菜单、快捷键共用一套命令注册表。 - **主题联动**:FlatLaf 暗色/亮色 + RSyntaxTextArea 内置主题一键切换,字体可配置。 ## 二、技术栈 | 技术 | 版本 | 作用 | | --- | --- | --- | | Java | 21 | 运行时与编译目标 | | Maven | - | 构建与依赖管理 | | RSyntaxTextArea | 3.6.3 | 编辑器内核:语法高亮、行号、折叠、查找 | | FlatLaf / FlatLaf Extras | 3.6.2 | 扁平化外观(暗色/亮色) | | SwingX (swingx-core) | 1.6.5-1 | `JXTreeTable`,用于文件夹对比的树形表格 | | Hutool | 5.8.44 | 通用工具库 | ## 三、整体架构 所有代码在 `src/main/java/com/penngo/gui/` 下,按职责分包: ``` core/ # 全局单例 WindowManager + per-window EditorWindow、项目/工作区、最近文件 editor/ # 编辑器内核:EditorTab、多光标、标签容器、拖拽接口 sidebar/ # 多根文件树、文件过滤、右键动作接口 compare/ # 文件夹对比树表 + 文件文本差异对比 command/ # 命令注册表、命令面板 menu/ # 菜单栏工厂 theme/ # 主题与偏好持久化 io/ # 文件读写服务 find/ # 查找替换条 status/ # 状态栏 settings/ # 字体设置对话框 ``` ### 1. 多窗口架构:全局单例 + 可多实例窗口 这是 DFDiff 最核心的设计。核心思想是**「共享资源全局化、窗口实例化」**: - **`WindowManager`(单例)** 持有所有共享资源:命令表、主题、最近文件、命令面板、所有窗口列表、活动窗口。命令在这里统一 `registerCommands()`,闭包操作 `activeWindow().getCtx().editor.xxx`。它还提供跨窗口能力:`allTabs()`、`findTabByPath()`(跨窗口去重)、`activateTab()`、`detachTab()`(拖出成新窗口)、`mergeTab()`(拖回合并)。 - **`EditorWindow`(每窗口一个实例)** 持有本窗口的标签、侧栏、状态栏、查找条、编辑器。快捷键由 `wm.bindKeys(rootPane)` 绑到本窗口 rootPane,但映射到全局命令表。 - **`AppContext`** 是 per-window 引用容器,通过 `ctx.wm` 反向访问全局资源。 关闭逻辑也很讲究:单窗口关闭只确认本窗口的未保存标签;只有**最后一个窗口**关闭时才 `System.exit`。 ### 2. 单一命令来源 这是 DFDiff 最值得借鉴的约定。一条命令只需要在 `WindowManager.registerCommands()` 里加一行: ```java r.register("edit.comment", "切换行注释", "ctrl SLASH", "编辑", () -> { var w = activeWindow(); if (w != null) w.getCtx().editor.toggleComment(); }); ``` 然后**三处自动覆盖**: - `MenuBarFactory` 按 id 取命令构建菜单项; - `WindowManager.bindKeys()` 按 shortcut 注册到每个窗口的 rootPane; - `CommandPalette`(Ctrl+P / Ctrl+Shift+P)也消费同一注册表。 这避免了「菜单改了、快捷键忘了改、命令面板又硬编码一份」的常见混乱。`MenuBarFactory.parse()` 还负责把 `"ctrl shift P"`、`"ctrl SLASH"`、`"ESCAPE"` 这种友好字符串解析成 `KeyStroke`。 ### 3. editor 层不反向依赖 core `EditorTab` 不直接 import `core` 包,而是通过 `EditorTabListener` 接口(dirty / 光标 / 消息)回调,由 `EditorManager implements EditorTabListener` 接收。标签拖拽通过 `editor/TabDragHandler` 接口解耦——`EditorTabs` 只调接口,`WindowManager.dragHandler()` 提供实现。 这种依赖反转让 editor 层可以独立测试和复用,core 层可以替换不同的拖拽策略。 ### 4. 侧栏多根文件树 `FileTreeModel` 用一个虚拟 `Roots` 哨兵根,`rootVisible=false` 让它直接呈现多个文件夹根。过滤在模型层完成(`FileFilter`:内置忽略 `.git`/`target`/`node_modules` 等 + 自定义 glob + 各根 `.gitignore` 解析),而不是 UI 层过滤——这样右键路径和多根路径判断都基于同一份过滤后的结构,逻辑更简单。 ### 5. 文件夹/文件差异对比 这是 DFDiff 的特色功能,分两层: - **文件夹对比**:`FolderCompareEngine` 基于左右子项名称并集生成两棵同构树,缺失侧用 `path == null` 的占位节点表达「空白对齐」。差异状态会向父目录汇总(子文件不同 → 父目录也标红)。展示用 SwingX 的 `JXTreeTable`,左右滚动与展开同步。 - **文件文本差异**:`TextDiffEngine` 用 LCS 做行级对齐,输出**完整左右对齐行**(相同行也显示),通过 `SAME / ADDED / MISSING / UPDATED` 状态标记差异。`TextFileLoader` 负责安全读取——二进制检测、BOM 识别、大文件限制、UTF-8 严格解码,避免对二进制文件显示乱码。 ## 四、编译运行 DFDiff 用 Maven 构建,需要 **JDK 21+**。 ### 1. 准备环境 确认 Java 版本: ```bash java -version # openjdk version "21" ... mvn -version ``` ### 2. 编译 ```bash mvn -DskipTests compile ``` ### 3. 打包(生成可执行 fat jar) ```bash mvn -DskipTests package ``` 打包后会在 `target/` 下生成两个 jar: - `DFDiff-1.0.0.jar`:只含项目代码 - `DFDiff-1.0.0-jar-with-dependencies.jar`:包含全部依赖,可直接运行 `maven-assembly-plugin` 负责把依赖打进 fat jar,并在 `MANIFEST.MF` 里写入主类 `com.penngo.gui.Main`。 ### 4. 运行 **方式一:直接运行 fat jar** ```bash java -jar target/DFDiff-1.0.0-jar-with-dependencies.jar ``` **方式二:用 Maven 运行** 如果想直接跑(不打包),可以在 `pom.xml` 里启用被注释的 `exec-maven-plugin` 配置,然后: ```bash mvn exec:java ``` > 注:仓库 `pom.xml` 里 `exec-maven-plugin` 配置默认是注释掉的,因为 `package` 出 fat jar 后用 `java -jar` 更通用。需要时取消注释即可。 启动后会弹出一个 Swing 主窗口,默认带一个欢迎标签,里面列出了常用快捷键。 ### 5. 常用命令速查 ```bash mvn -DskipTests compile # 编译 mvn -DskipTests package # 打包 mvn clean # 清理 target/ mvn dependency:tree # 查看/排查依赖树 ``` > 当前项目无 `src/test`,所以没有测试套件。`mvn test` 可作为未来加入测试后的标准入口。 ## 五、运行效果 ### 5.1 使用的测试数据 ![在这里插入图片描述](./images/01.png)