adiff#
A semantic, side-by-side diff viewer for the terminal with $EDITOR support.
Unlike traditional diff tools, adiff uses tree-sitter to parse source files into ASTs and diffs at the token level. This means pure formatting changes — re-indented blocks, reformatted function signatures, whitespace normalization — are shown as equal, while genuine logic changes are clearly highlighted.

Features#
- Semantic diff — tree-sitter token-level diffing ignores style-only changes
- Syntax highlighting — per-language color via Chroma
- Live file watching — press
eto open the new file in$EDITOR; adiff watches for saves and reloads automatically - Vim keybindings —
j/k,g/G,n/pfor change navigation - Themeable — TOML theme files with full colour control;
nordbuilt-in
Semantic Supported languages#
Bash, C, C++, Go, JavaScript, JSON, Nix, Python, Ruby, Rust, TypeScript
Supported languages#
All languages without semantic support are supported with traditional text diffs
Installation#
Nix (flake)#
inputs.adiff.url = "https://flakes.adriano.fyi/adiff";
Then add inputs.adiff.packages.${system}.default to your packages, or use the provided overlay.
Direct package reference (home-manager / NixOS environment.systemPackages):
home.packages = [ inputs.adiff.packages.${pkgs.system}.default ];
Overlay — adds pkgs.adiff to your package set:
nixpkgs.overlays = [ inputs.adiff.overlays.default ];
# Then reference it like any nixpkgs package:
home.packages = [ pkgs.adiff ];
In a NixOS or home-manager flake this typically looks like:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
adiff.url = "git+https://tangled.org/adriano.tngl.sh/adiff";
};
outputs = { nixpkgs, home-manager, adiff, ... }: {
homeConfigurations.yourname = home-manager.lib.homeManagerConfiguration {
pkgs = import nixpkgs {
system = "x86_64-linux";
overlays = [ adiff.overlays.default ];
};
modules = [
({ pkgs, ... }: { home.packages = [ pkgs.adiff ]; })
];
};
};
}
Usage#
adiff [flags] <old-file> <new-file>
| Flag | Default | Description |
|---|---|---|
-theme |
nord |
Theme name or path to a .toml file |
Keybindings#
| Key | Action |
|---|---|
j / k |
Move down / up |
h / l |
Scroll left / right (8 cols) |
H / L |
Scroll left / right (half-column jump) |
0 / $ |
Jump to line start / end |
n / p |
Jump to next / previous change |
g / G |
Jump to top / bottom |
s |
Toggle semantic ↔ text diff mode |
e |
Open new file in $EDITOR; diff reloads on save |
q |
Quit |
Mouse / trackpad: vertical wheel scrolls rows; horizontal wheel scrolls columns.
Diff modes#
Semantic mode (default) uses the tree-sitter AST to identify which tokens actually changed. Adjacent removed/added line blocks where all tokens are identical are collapsed into equal (unhighlighted) lines. Only lines containing genuinely changed tokens are highlighted.
Text mode falls back to a standard line-level diff with no AST involvement. Toggle between modes with s.
Themes#
Themes are TOML files. adiff searches for themes in this order:
- Literal path (if the value contains
/or ends in.toml) ~/.config/adiff/themes/<name>.toml- Built-in
nord
Theme format#
[meta]
name = "mytheme"
description = "My custom theme"
author = "you"
[chroma]
style = "nord" # any Chroma style name
[diff]
added_bg = "#2E4034"
added_fg = "#A3BE8C"
removed_bg = "#3D1F1F"
removed_fg = "#BF616A"
equal_fg = "#D8DEE9"
header_fg = "#88C0D0"
line_number_fg = "#4C566A"
status_fg = "#5E81AC"
help_fg = "#4C566A"
cursor_symbol = "▶"
Place custom themes at ~/.config/adiff/themes/<name>.toml and reference them with -theme <name>.
hookable integration#
hookable is a Claude Code hook runner that exposes tool call inputs as environment variables and forwards them to an arbitrary command. adiff reads these variables natively, so it can be dropped in as the --cmd to preview file changes before Claude applies them.
When invoked with no file arguments and HOOKABLE_TOOL_NAME is set, adiff constructs the before/after diff automatically:
| Tool | Before | After |
|---|---|---|
Edit |
Current file on disk | File with old_string replaced by new_string |
Write |
Current file on disk (empty if new) | Incoming content |
Claude Code hook configuration#
Add to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"hooks": [{"type": "command", "command": "hookable --interactive --no-exit-code --cmd 'adiff -i'"}]
},
{
"matcher": "Write",
"hooks": [{"type": "command", "command": "hookable --interactive --no-exit-code --cmd 'adiff -i'"}]
}
]
}
}
--interactive runs adiff under a PTY so the full TUI renders. --no-exit-code tells hookable to ignore adiff's exit code and always wait for a keypress — press y to allow the change or n to deny it.
Development#
nix develop # CGO-aware dev shell
go test ./... # run tests
nix build # hermetic build → ./result/bin/adiff
Releasing#
VERSION is the single source of truth for the release version. Bump it with svu via the flake app:
nix run .#bump-version # bump to next version (based on conventional commits)
nix run .#bump-version -- patch # force a patch bump
nix run .#bump-version -- minor # force a minor bump
nix run .#bump-version -- major # force a major bump
After bumping, commit VERSION and tag the release.