difftastic 0.71.0 compares files by syntax tree instead of by line. The Windows build is a single 116351488-byte difft.exe inside a 10446790-byte zip.

Line-based diff answers "which lines changed". On a real edit that is often the wrong question, because moving one call inside an if block rewrites four lines while changing one thing.

I compared two Python files that differ in six places, then the same pair padded to 4000 copies each, and timed both against git diff --no-index on the small pair.

One 116351488-byte exe

The release zip holds a single executable. It contains the tree-sitter grammars for every supported language, which is why a diff tool is 116 MB.

ItemValueNote
Release zip10446790 bytesdifft-0.71.0-x86_64-pc-windows-msvc.zip
difft.exe116351488 bytesStandalone, no runtime
Version output94 bytesOne line naming version and target
Config writtenNoneEnvironment variables only

The 116 MB is grammars, not UI

There is no interface to draw and no editor to embed. The weight is the parser set: one compiled grammar per language, and every one has to be present for the tool to recognise a file by extension.

It reads files or directories

Pass two paths and it compares them. Pass two directories and it walks both, which is the same shape as git diff --no-index.

Start-up costs 191 ms

Three runs of difft --version took a median of 191 milliseconds, and the output line is 94 bytes.

CommandMedianOutput
difft --version191 ms94 bytes
difft a.py b.py (447 vs 519 bytes)518 ms722 bytes
git diff --no-index a.py b.py177 ms751 bytes

191 ms is the floor for any diff

Because a version check and a two-file comparison share the same load path, no comparison can finish faster than this. A small file pair at 518 ms spends 327 ms on parsing and tree comparison.

Three times slower than git on the same pair

518 ms against 177 ms on a 447-byte file is 2.9 times. The extra time buys grammar parsing: git only has to look at lines, difft has to build two syntax trees and match their nodes.

Big input takes 5 seconds

Concatenating the same pair 4000 times gives two files of 1788000 bytes with the same six differences repeated throughout. Comparing those took 5001 ms and produced 6910985 bytes of output.

InputTimeOutput
447 vs 519 bytes518 ms722 bytes
1788000 vs 1788000 bytes5001 ms6910985 bytes
Growth factor9.7 times slower9572 times more output

Output grows faster than input

1788000 bytes in produced 6910985 bytes out, a ratio of 3.9. Every repeated difference is drawn in full, so a file made of the same block 4000 times is the worst case for any diff tool.

Five seconds is over the interactive limit

Above roughly one second a diff stops feeling instant. For files in the megabyte range, line-based tools answer in a fraction of that because they never build a tree.

What the output actually shows

The screenshot is one comparison of the small pair. It is split into two hunks, marked 1/2 and 2/2, and each hunk shows only the region around a change.

ElementMeaning
1/2 and 2/2 headingsTwo separate change regions in one file
Red text on leftRemoved tokens, e.g. 5000
Green text on rightAdded tokens, e.g. 10000
Line numbers on both sidesOriginal and new position kept aligned
Coloured keywords and stringsSyntax highlighting inside the diff
difftastic 0.71.0 comparing two Python files in a console: two hunks marked 1/2 and 2/2, removed tokens in red on the left, added tokens in green on the right, syntax highlighting with line numbers on both sides

Six changes became two hunks

The six edits land in two clusters: three near the top around the constant and the function signature, three near the bottom around the tuple and the print. difft groups them by proximity instead of showing six separate blocks.

The tuple line shows the difference inline

One line in the second hunk has four unchanged city names and one added one, coloured separately inside the same line. Line-based diff would show that as one deleted line and one added line, doubling the reading.

Flags worth knowing

The tool takes two positional paths and a handful of environment variables. There is no config file to edit.

SettingEffect
--color=neverPlain text output, no escape codes
DFT_PAGEROverrides the pager, set to cat to disable paging
--pagingNot a flag in 0.71.0, the parser rejects it
> set DFT_PAGER=cat
> difft a.py b.py
b.py --- 1/2 --- Python
1 LIMIT = 5000              1 LIMIT = 10000

--paging is rejected

Passing --paging=never returns "unexpected argument" and suggests quoting it after --. The pager is controlled by the DFT_PAGER environment variable instead.

Plain output for piping

--color=never is what the timing runs used, because escape codes would inflate the measured output. For reading, the colour is the point.

130 lines of languages

Running difft --list-languages prints 130 lines: each language takes a name line plus one or two lines of file extensions, so the list covers roughly 45 languages.

EntryExtensions
Ada*.ada *.adb *.ads
Apex*.apexc *.trigger
Assemblyseveral per dialect

Recognition is by extension

A file with an extension in that list gets tree matching; anything else falls back to line diff with no warning. Renaming a script to .txt silently downgrades the comparison.

The grammars are why the exe is 116 MB

Every entry in that list is a compiled grammar linked into difft.exe. Forty-five parsers plus the matching engine is the whole 116351488 bytes.

Where it earns a place

Reviewing a refactor where code moved without changing behaviour, in a language the tool has a grammar for.

TaskFitWhy
Reviewing moved codeHighTree matching ignores pure reordering
Spotting one character inside a lineHighInline token colour instead of whole-line swap
Files above 1 MBLow5001 ms and 6.9 MB of output on 1.8 MB input
Unknown file typesLowFalls back to line mode without a grammar

Pair it with git, do not replace it

git answers in 177 ms and is always available. difft costs 518 ms on the same pair but shows which token inside the line moved. Use git for speed and difft when a diff is hard to read.

Licence and cost

MIT. Free for personal and commercial use, source on GitHub, written in Rust. The Windows build is one exe with no installer and no registry writes.