Skip to content

Latest commit

 

History

157 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jiff

CI Latest release License

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

Jiff showing a side-by-side source code diff

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 or italic 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 supports one top-level table: [color]. Set color.depth to 16, 256 or 24 to choose the preferred output; it defaults to truecolour (24). The palettes live in [color.ansi16], [color.ansi256] and [color.truecolor]. Unknown tables, styles and fields are reported as errors.

The two palette tables use the same style names and fields:

Field Value Meaning
color Palette-specific colour Foreground colour
bgcolor Palette-specific colour Background colour; diff styles only
bold true or false Enable or disable bold text
italic true or false Enable or disable italic text

Inline tables keep short styles compact:

[color]
depth = 256

[color.ansi16]
add = { color = "green", bold = true }

[color.ansi256]
add = { color = 114, bold = true }
add_highlight = { color = 231, bgcolor = 22 }

[color.truecolor]
add = { color = "#87d787", bold = true }
add_highlight = { color = "#ffffff", bgcolor = "#005f00" }

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

[color.ansi256.add_highlight]
color = 231
bgcolor = 22
bold = true
italic = true

All styles and fields are optional. Missing ANSI16 values use Jiff's built-in defaults. Missing ANSI256 values inherit the corresponding resolved ANSI16 values. Missing truecolour values inherit ANSI256 using the standard xterm RGB mapping. Use "default" to select the terminal's normal foreground or background explicitly:

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

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

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

The old layout, where styles appeared directly below [color], is no longer accepted.

Jiff uses the preferred palette when the terminal supports it. Truecolour falls back to ANSI256 and then ANSI16; ANSI256 falls back to ANSI16. An explicit depth of 16 always uses ANSI16, and an explicit depth of 256 still emits indexed colours on a truecolour terminal. --no-color continues to disable all palettes.

Diff styles

These styles control the diff itself:

Style Used for Default foreground Default background
same Unchanged text Terminal default Terminal default
line_number Side-by-side line-number gutters Terminal default Terminal default
line_number_add Line numbers beside additions Inherits line_number Inherits line_number
line_number_remove Line numbers beside removals Inherits line_number Inherits line_number
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 ten accept color, bgcolor, bold and italic. Their built-in text attributes are both false. line_number, line_number_add and line_number_remove apply to the complete padded line-number cell, but not the vertical rule beside it, so a background colour fills the number cleanly without catching the divider. The two changed-line styles inherit line_number when omitted. The +/- markers inherit the corresponding diff colour and italics, and are deliberately bold. 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, bold and italic; both text attributes default to 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 palette values

ANSI16 foregrounds and backgrounds use these case-insensitive names:

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).

ANSI256 foregrounds and backgrounds use integer indexes from 0 to 255, or the string "default".

Truecolour foregrounds and backgrounds use a six-digit RGB value such as "#89b4fa", or the string "default". Short hex values, alpha channels and CSS colour names are not accepted.

Complete themes

The repository includes complete themes in jiff-configure/themes. High Contrast Light, High Contrast Dark, Tokyo Night and Twilight Dark are Jiff themes. The other 25 use the static themes bundled with bat: 1337, the four Catppuccin variants, Coldark, DarkNeon, Dracula, GitHub, Gruvbox, Monokai Extended, Nord, OneHalf, Solarized, Sublime Snazzy, TwoDark, Chalkboard and Zenburn.

Each theme has a truecolour palette plus indexed and named fallbacks. The syntax styles come directly from its bat theme. Jiff derives the diff backgrounds from that palette, because bat does not define diff colours. Terminals can customise their first 16 colours, so the ANSI16 fallback's exact appearance still depends on the terminal theme. bat's ansi and Base16 templates are not included: they rely on a terminal-specific palette rather than defining fixed colours.

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. If Jiff already has a configuration file, the tool loads it as Current configuration using the same path lookup as Jiff. To preview a pair of your own files:

uv run jiff-configure OLD NEW

Use the picker to switch between the current configuration, the default colours and the packaged themes. Preferred output chooses the depth used by Jiff and the previews; Palette to edit switches between the indexed palette and its named fallbacks. ANSI16 fields use named selectors. ANSI256 fields open a 16 by 16 colour grid which works with the mouse or arrow keys. Truecolour fields use a validated #RRGGBB editor and colour swatch. Both pickers include a separate terminal-default choice.

The Side-by-side, Inline, and Three-way tabs use Jiff's real Python renderer, so they update as each palette changes. If the current terminal cannot display the preferred depth, the previews use the best fallback and say so above the tabs. The saved TOML contains all three complete palettes, including separate bold and italic settings.

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. Side-by-side Git diffs put each path above its pane when both fit. Added and deleted files use /dev/null for the missing side. Wider paths and inline output keep the Git-style --- and +++ headings. 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 positional protocol. Renames and copies include the new path as an extra argument. --git-external-diff tells Jiff to parse those arguments, retain both paths, keep 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~

Added and deleted files use /dev/null for the missing side.

Unresolved files are another difference between the two Git interfaces. git diff calls Jiff with the unresolved path, which lets Jiff read all three stages from Git's index. git difftool does not call a custom file-mode tool for unresolved paths. Use the diff.external integration above when you want Jiff's three-way conflict view during a merge or rebase.

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

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages