Skip to content

Repository files navigation

jiff

Build Status

A terminal diff tool supporting sub-line diffs and side-by-side output display

Implementations

This repo has two separate, fully featured implementations: one written in Rust, the other in Python. The Rust version is expected to be faster, but the Python version is more portable. The core diff behaviour is identical, though syntax highlighting may differ slightly because the implementations use different language engines.

Installing

Install the Rust implementation with Cargo:

cargo install --path .

Or install the Python implementation as a standalone uv tool:

uv tool install ./python

Both commands install a jiff executable. Only install one implementation at a time unless you deliberately arrange their order in $PATH.

Invoke Jiff with two files or directories to see the difference between them:

jiff FILE1 FILE2
jiff DIR1 DIR2

By default, Jiff renders diffs in side-by-side mode. To render diffs in the more conventional inline mode, pass --inline.

Long output from either implementation is sent to $PAGER, using less by default. Output which fits in the terminal, or is redirected to another command, is printed directly. Pass --no-pager to always print directly.

By default Jiff shows every unchanged line. Pass -U<n> or --unified=<n> to show at most <n> lines of context on either side of each change. For example, this shows three context lines:

jiff -U3 FILE1 FILE2

Omitted regions are marked with their number of unchanged lines in either output layout. -U0 shows only changed lines and those markers.

Jiff also supports three-way merge diffs. Pass three files to compare two versions against a common base. The second file is the base, so the order is:

jiff LOCAL BASE REMOTE

Side-by-side mode draws one pane per file. Changes are highlighted in the two outer panes, relative to base, while the central (base) pane highlights the text changed by the local side, the remote side or both. --inline falls back to two labelled diffs, LOCAL against BASE followed by BASE against REMOTE.

Syntax highlighting

Jiff automatically detects source languages from the input filenames. Git difftool comparisons use the repository path supplied through --path, rather than trying to identify Git's temporary filenames.

Use --syntax=LANGUAGE to override automatic detection:

jiff --syntax=python FILE1 FILE2

Common language names and extensions are accepted. The Python implementation uses Pygments, while Rust uses syntect, so their complete language lists and a few token boundaries differ. An unknown automatically detected language falls back to plain text. An unknown explicit language is reported as an error.

Pass --no-syntax to retain Jiff's diff colours without token highlighting. --no-color disables both. The built-in syntax palette is deliberately muted:

  • comments are grey;
  • keywords are magenta;
  • strings are cyan;
  • numbers are blue;
  • function and type names are yellow.

Syntax highlighting only changes foreground colour and optional bold text - diff highlights control the background colour.

Configuration

Jiff supports an optional XDG-style config file for persisting configuration.

File location

Jiff uses the first configuration file it finds in this order:

  1. The path in $JIFF_CONFIG, when set. If no other paths are searched.
  2. $XDG_CONFIG_HOME/jiff/config.toml, or ~/.config/jiff/config.toml when $XDG_CONFIG_HOME is not set.
  3. ~/.jiffconfig.

The XDG path is recommended for normal use. Create it with:

mkdir -p ~/.config/jiff
touch ~/.config/jiff/config.toml

JIFF_CONFIG is useful for trying another theme temporarily:

JIFF_CONFIG=jiff-configure/themes/high-contrast-light.toml jiff OLD NEW

TOML structure

The config file must contain valid TOML and currently supports one top-level table: [color]. Unknown tables, styles and fields are reported as errors.

Each entry below [color] names a style. A style has up to three fields:

Field Value Meaning
color Colour name Foreground colour
bgcolor Colour name Background colour; diff styles only
bold true or false Enable or disable bold text

Inline tables keep short styles compact:

[color]
add = { color = "blue", bold = true }
add_highlight = { color = "yellow", bgcolor = "blue" }

Alternatively you may use the longer, equivalent, TOML table form:

[color.add_highlight]
color = "yellow"
bgcolor = "blue"
bold = true

All styles and fields are optional. Omitted values keep their built-in default. Use the colour name default when you want to clear a built-in foreground or background instead:

[color]
add = { color = "default" }

That example makes added text use the terminal's normal foreground colour.

Diff styles

These styles control the diff itself:

Style Used for Default foreground Default background
same Unchanged text Terminal default Terminal default
omitted ... N unchanged lines ... markers bright_black Terminal default
add Normal added text and unchanged characters in paired lines green Terminal default
add_highlight Changed characters and unpaired side-by-side additions black green
remove Normal removed text and unchanged characters in paired lines red Terminal default
remove_highlight Changed characters and unpaired side-by-side removals black red
overlap_highlight Middle-pane characters changed by both outer files in a three-way black yellow

All seven accept color, bgcolor and bold. Their built-in bold value is false. Line numbers and the +/- markers inherit the corresponding diff colour and are deliberately bold so they remain visible beside highlighted text. overlap_highlight is only used in three-way side-by-side output.

Syntax highlighting styles

Syntax configuration only changes how token categories are drawn.

Style Used for Default foreground
syntax_comment Comments and documentation bright_black
syntax_comment_highlight Comments within highlighted text bright_black
syntax_keyword Language keywords magenta
syntax_keyword_highlight Language keywords within highlighted text magenta
syntax_string String literals cyan
syntax_string_highlight String literals within highlighted text cyan
syntax_number Numeric literals blue
syntax_number_highlight Numeric literals within highlighted text blue
syntax_definition Function, type and other definition names yellow
syntax_definition_highlight Function, type and other definition names within highlighted text yellow

These ten styles accept color and bold; their built-in bold value is false. They do not accept bgcolor. Diff backgrounds must remain in control, and syntax highlighting renders on top of diff highlights.

Use --no-syntax to ignore the syntax styles while retaining the diff colours. Use --no-color to disable both diff and syntax styling.

Supported colour names

The following case-insensitive colour names are supported:

default
black
bright_black
red
bright_red
green
bright_green
yellow
bright_yellow
blue
bright_blue
magenta
bright_magenta
cyan
bright_cyan
white
bright_white

gray and grey are aliases for bright_black; purple is an alias for magenta. default means the terminal's normal foreground or background (not Jiff's built-in value). Hex colours, RGB values, ANSI colour numbers and colours outside the standard 16-colour ANSI palette are not currently supported.

Complete themes

The repository includes seven complete themes which you can use as-is or extend:

Theme Best suited to Character
High contrast light Light terminals Crisp blue and magenta diff colours
High contrast dark Dark terminals Bright cyan and yellow diff colours
Catppuccin Mocha Dark terminals Soft green, magenta and cyan
Dracula Dark terminals Green and red diffs with purple syntax
Gruvbox Dark Dark terminals Warm, bright foregrounds on restrained backgrounds
Nord Dark terminals Cool cyan, red and blue
Tokyo Night Dark terminals Cyan and magenta with blue syntax

The first two prioritise contrast and colour-blind accessibility. The other five borrow the colour relationships of popular editor themes and are intended to render well in these themes (though bear in mind not all terminals implement their themes in the same way). Jiff's config uses ANSI colour names, so your terminal theme will choose the exact shades. This generally makes the palettes sit naturally alongside a matching terminal theme, but they are not exact RGB reproductions.

Copy any theme to the standard XDG location to use it:

mkdir -p ~/.config/jiff
cp jiff-configure/themes/high-contrast-dark.toml ~/.config/jiff/config.toml

Building a theme interactively

You can build a theme interactively using the separate jiff-configure tool. Run it from the repository with:

uv run jiff-configure

This starts with a small built-in Python diff which exercises all supported diff functionality. To preview a pair of your own files:

uv run jiff-configure OLD NEW

Pick one of the packaged themes as a starting point, then adjust the settings for each style. The Side-by-side, Inline, and Three-way tabs use Jiff's real Python renderer, so they update as the palette changes.

Press Ctrl+S or use the Save button to save your theme.

Git difftool

Once jiff is installed and available in $PATH, configure it as a custom Git difftool with:

git config --global diff.tool jiff
git config --global difftool.jiff.cmd 'jiff --path "$MERGED" "$LOCAL" "$REMOTE"'
git config --global difftool.prompt false
git config --global difftool.trustExitCode true

Remove --global if the configuration should only apply to the current repository.

For a one-off comparison without changing your Git configuration, use --extcmd:

git difftool --no-prompt --extcmd='jiff --no-pager --path "$BASE"'

The --no-pager in this example avoids opening a pager for each changed file. For a multi-file comparison with automatic paging, use the --dir-diff form instead. Jiff returns zero after displaying a text or binary comparison and non-zero when it cannot read, configure or display the diff. difftool.trustExitCode makes Git report those failures rather than silently continuing.

Git diff and Git show

Git's ordinary diff command uses a different interface from git difftool. It calls an external diff once per changed path using its own seven-argument protocol. --git-external-diff tells Jiff to parse those arguments, retain its colours and leave Git in charge of the pager.

Configure Jiff globally, or omit --global to use it in one repository:

git config --global diff.external 'jiff --git-external-diff'

Ordinary diff commands will now use Jiff:

git diff
git diff --cached
git diff HEAD~

git show and git log do not enable external diff programs by default. Pass --ext-diff, or add shorter aliases:

git show --ext-diff HEAD
git log -p --ext-diff

git config --global alias.jshow 'show --ext-diff'
git config --global alias.jlog 'log -p --ext-diff'

The aliases are then available as git jshow and git jlog. Git aliases cannot replace built-in commands, so a Git configuration cannot make the exact command git show imply --ext-diff.

External diff output is intended for people to read; it is not a patch. Use git diff --no-ext-diff for scripts or anything which needs Git's normal patch format.

Jiff automatically renders merge conflicts using the same three-way diff functionality described above. This deliberately represents the index, not the working-tree file. Any conflict markers or edits made since the merge are therefore not included. Some Git commands render unresolved paths with Git's built-in combined diff instead of calling an external helper; Jiff cannot replace output when it is not invoked.

Rust

Building

cargo build

Running

cargo run -- <options>

Testing

Unit tests:

cargo test

System tests:

uv run robot tests

Optionally, the Python tests can be skipped with:

uv run robot -v SKIP_PYTHON_TESTS:True tests

Python

Running

uv run jiff FILE1 FILE2

Testing

Unit tests:

uv run python -m unittest discover -s python/tests
uv run python -m unittest discover -s jiff-configure/tests

System tests:

uv run robot tests

Optionally, the Rust tests can be skipped with:

uv run robot -v SKIP_RUST_TESTS:True tests

Developing

Setting up a development environment

Create the development environment and install the git hooks with:

uv sync
uv run pre-commit install

Once the hooks are installed they will run automatically on commit. You can run pre-commit manually with:

uv run pre-commit run --all-files

About

A terminal diff tool supporting sub-line diffs and side-by-side output display

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages