About • Features • Screenshot • Build & Run • CLI • Project Structure • Troubleshooting • License
Warning
Under active development. APIs, FXML structure, and UI details may change without notice.
MiMiComparator is a Kotlin/JVM desktop application for comparing folders and text files side by side. It uses JavaFX 26 with FXML for the main layout and Kotlin code for the dynamic compare, home, toolbar, and tree behavior.
The app is currently focused on macOS and follows a compact native-style desktop workflow: two panels, a path bar, an icon-only toolbar, directory tree rows, synchronized scrolling, and a small status bar.
- Recursive directory comparison by relative path, size, and modified date
- Expandable tree model for directory results with synchronized left/right expansion
- Line-by-line text comparison for file mode
- Toggleable directory/file mode
- Glob-style filter field, for example
**,*.kt,*.txt, or comma-separated patterns - Synchronized vertical scrolling between left and right panels
- Icon-only toolbar with tooltips for compact macOS-style operation
- Home/session screen with access to auto-saved paths
- Persistent window geometry, split ratio, mode, sync-scroll state, and input paths
- CLI startup with optional automatic comparison
- Log4j2-based logging with rolling file output
| Layer | Technology |
|---|---|
| Application platform | Kotlin/JVM desktop app |
| Language | Kotlin 2.3.21 |
| UI toolkit | JavaFX 26 (FXML + programmatic) |
| UI style | macOS-oriented compact desktop controls |
| JSON persistence | Jackson + jackson-module-kotlin |
| Logging | Log4j2 + SLF4J bridge |
| Utilities | Apache Commons (Lang3, Collections4, IO) |
| Build | Gradle 9.4 (Kotlin DSL) |
| JDK | 25 (Amazon Corretto / OpenJDK) |
| Packaging | jpackage → macOS .app bundle |
Requires JDK 25+. Use the Gradle wrapper from the project root.
# run the app
./gradlew run
# build JAR + distributions
./gradlew build
# package macOS .app bundle
./gradlew packageMacApp
# run with CLI args
./gradlew run --args="--left /path/to/dir1 --right /path/to/dir2"The application can start empty, restore the saved session, or compare paths immediately.
# open normally
./gradlew run
# compare two paths
./gradlew run --args="--left /Users/me/A --right /Users/me/B"
# force directory mode
./gradlew run --args="--left /Users/me/A --right /Users/me/B --dirs"
# force file mode
./gradlew run --args="--left /Users/me/a.txt --right /Users/me/b.txt --files"Positional arguments are also supported:
./gradlew run --args="/path/to/left /path/to/right"User state is stored under:
~/.mimi/comparator/
The saved state includes the last left/right paths, directory mode, synchronized scrolling, split ratio, and window placement. Logs are written through Log4j2 and rotate according to app/src/main/resources/log4j2.xml.
app/src/main/kotlin/org/senatov/
├── App.kt # entry point, stage, theme, fonts
├── MainController.kt # FXML controller, compare logic, UI wiring
├── cli/
│ └── CliArgs.kt # CLI argument parser
├── compare/
│ ├── CompareResult.kt # file compare result (data class)
│ ├── DirCompareResult.kt # dir compare result (data class)
│ ├── DirectoryComparator.kt # recursive dir tree compare
│ └── FileContentComparator.kt # line-by-line text diff
├── helpers/log/
│ └── LogHelper.kt # stack-trace method name helper
├── model/
│ ├── CompareLineItem.kt # single row in compare list
│ └── tree/
│ ├── DirTreeModel.kt # tree state, expand/collapse, flatten
│ └── DirTreeNode.kt # single node in dir tree
└── ui/
├── cell/
│ └── DiffCellFactory.kt # ListCell factory (dir/file modes)
└── config/
├── ComparatorState.kt # persistent UI state (data class)
└── ComparatorStateService.kt # JSON load/save via Jackson
Source files live under
org/senatov/on disk while the Kotlin package isorg.senatov.mimicomparator. The build uses Kotlin/JVM source discovery, so the physical folder name does not need to mirror the package name.
- Toolbar buttons are icon-only; hover tooltips provide the command names.
- The status bar is reserved for current compare state and counts.
- Directory rows use compact macOS-style spacing with separate disclosure, icon, name, size, and modified-date fields.
- The event log is kept in code for diagnostics but hidden in the main UI.
If Kotlin reports a permission error under ~/Library/Application Support/kotlin/daemon, the project disables the external compiler daemon through:
kotlin.compiler.execution.strategy=in-processThis keeps compilation inside the Gradle process and avoids daemon marker files in the user Library folder.
If JavaFX cannot write to ~/.openjfx/cache, clear the cache or run with a temporary cache directory:
JAVA_TOOL_OPTIONS="-Djavafx.cachedir=/tmp/openjfxcache" ./gradlew runpackageMacApp requires a full JDK with jpackage and jmods. A JRE-only installation is not enough.
Contributions welcome — fork, branch, PR. Code style: clean, compact, Kotlin-idiomatic.
GNU General Public License v3.0
Iakov Senatov — github.com/senatov
