An interactive diff viewer for the terminal, side by side or as one column, with manual line alignment.
jdiff matches lines on their content, ignoring indentation, so re-indented code still lines up with its original. Where the automatic alignment guesses wrong, you pick two lines and anchor them: everything around the anchor aligns again. It reads delta's git config, so an existing delta theme applies right away.
jdiff is inspired by delta, the syntax-highlighting pager for git written in Rust. delta formats the output of git once and hands it to a pager; jdiff is a program you work in, so it can do what a pager cannot:
- Resizing: the diff is laid out again when the terminal changes size; delta lays it out once, for the width it started with.
- Switching the layout: two panes or one column like
git diff;sswitches between them while you look at the diff. In delta, side by side is chosen at startup. - Reflow:
rswitches between wrapping long lines onto further lines, marked↪, and cutting them; delta wraps a line as often as--wrap-max-linesallows, set at startup, and truncates the rest.- A cut line that goes on past its pane ends in
», one scrolled out to the left (hl) starts with«.
- A cut line that goes on past its pane ends in
- More or less context: long unchanged runs collapse to a single row;
spaceopens or closes the nearest one,ctrl+spaceall of them. delta shows the context lines git gave it. - Walking the history:
jdiff loglists the commits, opens a commit's files one by one, andNPstep to the next or previous commit, keeping the open file. - Following moved code: like delta, jdiff colors blocks moved within a file or between the files of one commit or diff, magenta where they moved away and cyan where they moved to, as git's
--color-moveddoes.ojumps to the other end, also into another file. - Auto reload: outside the log, files on disk are watched: on Linux the kernel reports their changes (inotify), elsewhere, or when the files are in more than
inotify_max_dirsdirectories, they are checked every second. When you save an edit, its diff updates while jdiff is open, and anchors, folds and the cursor follow their lines.RorF5runsgit difforgit logagain, or lists the directories again, so new files and commits show up too. delta shows the diff as it was when git ran. - Anchors: mark a line with
enter, then its partner in the other pane. jdiff realigns around it. Anchors that would cross are offered for replacement. - Jump marks:
mplus a letter marks a line of the new file,'plus the letter jumps back to it within the diff.\lists the marks; there,enterjumps andddeletes.- Marks are kept per repository and survive restarts.
- A mark finds its line again by its text when the file changed.
delta highlights syntax; jdiff does not.
- Compare two files, two directories (
git difftool --dir-diff), whatgit diffshows (jdiff diff, e.g.--cachedfor the index), or the commits ofgit log. - Switch what is compared:
dasks in a popup for the arguments ofgit diff, e.g.HEAD~3 HEAD, and shows its files instead of what is shown, from any view, also from the log. - Word-level highlighting of changed lines. Indentation changes are shown character by character, so only the added or removed part is marked.
- Flow view (
f): all files of a diff, or all commits of the log, one after the other in one scrollable view. Commits load as they come near the cursor.cshows the full commit messages or only their subjects.Ckeeps the cursor inside the current commit.
- Search:
/searches the diff, as plain text or a regular expression,.and,go to the next and previous match, in the flow view through all files, in the log within the current commit. - Long lists: when the file or commit list, the help or the jump list goes on above or below, its first or last row says so, e.g.
↓ 21 more; an arrow never stands for a single item, which is shown instead. - Previews: the commit list shows the selected commit's message, the file list a preview of the selected file's diff (
v). - Whitespace: changed indentation, tabs and runs of spaces are marked as
·and→, only their added or removed part, not single spaces between changed words;wdraws all whitespace so and line ends as¶(line_end),Wturns the marking of changed whitespace off,tshows trailing whitespace. Changed\r\nline ends show as␍, all of them withe. Invisible characters, among them those that change the text direction, show as☠. - 16-color output: every color is an ANSI color, so your terminal palette decides how jdiff looks.
On macOS (Apple Silicon or Intel), install the upstream Homebrew cask:
brew tap ObiWahn/jdiff https://github.com/ObiWahn/jdiff.git
brew install --cask obiwahn/jdiff/jdiffTo update it, run brew update && brew upgrade --cask obiwahn/jdiff/jdiff.
The cask installs a prebuilt binary; Go is not needed.
Or download a binary from the upstream releases, for Linux, macOS or Windows.
Extract the archive and put jdiff (or jdiff.exe) on your PATH.
Older releases contain bare binaries; on Linux and macOS, make those executable with chmod +x.
To build from source, jdiff needs Go 1.24 or newer:
make build # builds bin/jdiff
go install . # or install it into $GOBINjdiff --version prints the tag or commit it was built from.
The other make targets: make test, make vet, make vuln (govulncheck), make fmt, make fmt-check and make clean.
jdiff OLD NEW # compare two files
jdiff DIR1 DIR2 # compare two directories
jdiff log [GIT LOG ARGS] # browse commits, e.g. jdiff log -20 or jdiff log main..topic
jdiff diff [GIT DIFF ARGS] # what git diff shows: jdiff diff (unstaged), jdiff diff --cached (the index), jdiff diff HEAD
jdiff --features NAMES ... # delta features instead of delta.features, for the delta themeGit aliases, in ~/.gitconfig: git jd takes the arguments of git diff (git jd --cached, git jd main..topic), git jl those of git log:
[alias]
jd = "!f() { : git diff; cd \"${GIT_PREFIX:-.}\" && jdiff diff \"$@\"; }; f"
jdiff = "!f() { : git diff; cd \"${GIT_PREFIX:-.}\" && jdiff diff \"$@\"; }; f"
jl = "!f() { : git log; cd \"${GIT_PREFIX:-.}\" && jdiff log \"$@\"; }; f"
jlog = "!f() { : git log; cd \"${GIT_PREFIX:-.}\" && jdiff log \"$@\"; }; f"The null command : git diff; does nothing when the alias runs; git's bash completion reads it
and completes git jd like git diff: options, branches and paths. It has to come first in the
function, before any other command.
As git's difftool, for git difftool -d:
[diff]
tool = jdiff
[difftool "jdiff"]
cmd = jdiff \"$LOCAL\" \"$REMOTE\"
[difftool]
prompt = falseFiles of the working tree are watched, so edits you save while jdiff is open show up live. On Linux the kernel reports the changes (inotify): jdiff watches the directories the files of the diff are in, not their subdirectories, up to inotify_max_dirs (20) of them, all with one file handle. Otherwise it checks the files every watch_interval.
? shows all key bindings in a popup.
| Key | Action |
|---|---|
j k |
move |
N P, n p |
next, previous commit in history, also with --reverse |
pgup pgdn (ctrl+b ctrl+f) |
page up, down |
g G |
first, last commit |
enter |
show the files of the commit |
f |
flow view of all commits |
v |
show or hide the message box |
| Key | Action |
|---|---|
j k, n p |
move |
pgup pgdn (ctrl+b ctrl+f) |
page up, down |
g G |
first, last file |
enter |
show the diff of the file |
'letter |
open the file of a jump mark |
N P |
next, previous commit in history (in the log) |
f |
flow view of all files |
v |
show or hide the diff preview |
s |
side by side or normal preview |
w |
show all whitespace and line ends or only changed |
W |
mark changed whitespace or not |
t |
show trailing whitespace or not |
e |
show all \r\n line ends (␍) or only changed |
r |
reflow: wrap long lines or cut them |
b |
bold or plain changed words |
backspace |
back to the commits (in the log) |
| Key | Action |
|---|---|
j k |
move |
pgup pgdn (ctrl+b ctrl+f), ctrl+u ctrl+d |
page, half page; the cursor keeps its screen row |
g G |
top, bottom |
h l |
scroll left, right, unless lines are wrapped |
] [ |
next, previous change |
n p |
next, previous file |
N P |
next, previous commit in history (in the log) |
space / ctrl+space |
fold or unfold the nearest / all unchanged code |
tab |
switch pane |
enter |
mark a line, then its partner: anchor; on an anchored line: remove the anchor |
esc |
cancel the mark |
/ |
search in a bar at the bottom: type the text; tab switches between changed lines (added, removed and moved; the default) and all lines, shift+tab between plain text and a regular expression (Go syntax), both kept in state.toml; ↑ ↓ go through the earlier searches of this session, enter finds, esc or backspace on nothing typed cancels. Case is ignored unless the text holds a capital letter. Matches are highlighted; a match in folded code unfolds it, one beyond the pane's edge scrolls it into view |
. , |
next, previous match; past the last or first one the cursor stays. In the flow view in all files, in the log in those of the current commit: N P go to the others |
o |
other end of moved code |
mletter |
set a jump mark on the line of the new file |
'letter |
jump to a mark, when its file is in this diff |
s |
side by side or normal layout |
w |
show all whitespace and line ends or only changed |
W |
mark changed whitespace or not |
t |
show trailing whitespace or not |
e |
show all \r\n line ends (␍) or only changed |
r |
reflow: wrap long lines or cut them |
b |
bold or plain changed words |
f |
flow view |
backspace |
back to the file list, also from the flow view; a commit of one file goes back to the commits |
The keys of the diff view, and:
| Key | Action |
|---|---|
n p |
next, previous file bar |
N P |
next, previous commit bar in history |
c |
full or short commit messages |
C |
stay in the current commit (N P still switch) |
f |
single file view |
| Key | Action |
|---|---|
\ |
jump marks: j k move, enter jumps, d or del deletes, esc closes |
d |
a popup: show the files of git diff ARGS instead of what is shown, also the log: type ARGS, e.g. --cached or HEAD~3 HEAD; ↑ the current ARGS to edit, while a git diff is shown; enter show, esc, or enter or backspace on nothing typed cancel. git runs in the directory jdiff was started in, also when it compares two directories or files. The popup grows with ARGS, up to the screen's width. When git fails, the popup stays and says why. |
R F5 |
load again: run git diff or git log again, list the directories again or read the two files |
? |
help |
q ctrl+c |
quit |
jdiff works without any configuration.
Its files live in $XDG_CONFIG_HOME/jdiff, by default ~/.config/jdiff:
| File | Content |
|---|---|
main.toml |
settings and the name of the theme |
themes/<name>.toml |
a theme |
state.toml |
the toggles c C f s v w, and how / searches, as last set; written by jdiff |
marks.toml |
jump marks, per repository; written by jdiff |
Unknown keys and invalid values are reported in the status bar.
theme = "desert" # a file in themes/; without it, delta's git config is the theme
context = 3 # unchanged lines kept visible around changes
collapse_unchanged = true # start with unchanged code folded
only_changed_whitespaces = true # mark only the added or removed part of whitespace
watch_interval = 1 # seconds between checks for changed files; 0 turns watching off
inotify_max_dirs = 20 # Linux: up to so many directories of changed files, the kernel reports changes; 0 turns it off
line_end = "¶" # drawn at the end of lines while w shows all whitespace; one column
cr_end = "␍" # drawn for the \r of \r\n line ends that changed, or all with e; one column
extends = "»" # last column of a line cut at the pane's edge (lines not wrapped); one column
precedes = "«" # first column of a line scrolled out to the left; one column
# The toggles, also kept in state.toml:
side_by_side = true
flow = false
preview = true
show_whitespaces = false
mark_whitespace = true
show_trailing = false
show_cr = false
wrap = false
bold_changes = true
full_messages = true
stay_in_commit = false
search_regex = false # / searches for a regular expression (shift+tab in its bar)
search_all = false # / searches all lines, not only changed ones (tab)jdiff takes its colors from delta's options: the [delta] section of your git config, including delta features (delta.features, DELTA_FEATURES, --features). Its own options are in [jdiff].
Alternatively, a theme file in themes/ sets the same options without section prefixes. Options a theme file leaves out take jdiff's defaults, not your git config. Named colors go in a [colors] table:
minus-style = "sand bold"
plus-emph-style = "bright-green bold"
cursor-style = "reverse"
minus-moved-style = "magenta"
[colors]
sand = "#c2b280"The options:
- From delta:
minus-style,minus-emph-style,minus-non-emph-style,plus-style,plus-emph-style,plus-non-emph-style,zero-style,line-numbers-minus-style,line-numbers-plus-style,line-numbers-zero-style,line-numbers-left-style,line-numbers-right-style,file-style,file-decoration-style,inline-hint-style,file-added-label,file-removed-label,file-modified-label,file-renamed-label. - jdiff's own:
anchor-style,pending-style,cursor-style,file-bar-style,commit-bar-style,commit-hash-style,branch-style,filler-style,whitespace-style,trailing-whitespace-style,hidden-char-style,overflow-style,more-style(the ↑ and ↓ of lists that go on; unset:whitespace-style),search-style(text found by/),minus-indent-style,plus-indent-style,minus-moved-style,plus-moved-style.
Styles use delta's syntax: attributes (bold, dim, italic, ul, reverse, …), then a foreground and a background color, given as a name, bright- name, 0–255 or #rrggbb. Colors beyond the 16 ANSI colors are mapped to the nearest one.
make test # go test -race with a timeout
make vet
make vuln # govulncheck, installed with go install golang.org/x/vuln/cmd/govulncheck@latest
make fmtCI (.github/workflows/ci.yml) runs fmt-check, vet, test, build and vuln on Linux with the newest stable Go, and the tests on macOS and Windows: on pushes to any branch and every Monday, for new vulnerabilities.
When they pass, GoReleaser builds jdiff for Linux (amd64, arm64), macOS (arm64, amd64) and Windows (amd64).
Branches and scheduled runs (including on forks) build snapshots and attach the archives, SHA-256 sums and generated cask to the run without publishing.
A tag v* pushed to ObiWahn/jdiff, e.g. git tag v0.0.3 && git push upstream v0.0.3, publishes the archives and sha256sums.txt as a GitHub release, then updates Casks/jdiff.rb on upstream's master branch.
Prereleases do not update the cask.
The upstream repository itself is the Homebrew tap: the explicit URL in brew tap avoids needing a separate homebrew-jdiff repository.
The release job uses its GITHUB_TOKEN with contents: write; no extra token or tap repository is needed.
If branch protection is enabled, it must allow the release bot to update Casks/jdiff.rb on master.
The macOS binaries are not notarized; the cask removes the quarantine attribute only from the staged jdiff binary so it can run.
To check the release setup locally with GoReleaser 2.19.0:
goreleaser check
goreleaser release --snapshot --clean # builds all targets and the cask, publishes nothingTo use your build in this repository, the same in its .git/config, with bin/jdiff:
[diff]
tool = jdiff
[difftool "jdiff"]
cmd = \"$(git rev-parse --show-toplevel)/bin/jdiff\" \"$LOCAL\" \"$REMOTE\"
[difftool]
prompt = false
[alias]
jd = "!f() { : git diff; top=$(git rev-parse --show-toplevel); cd \"${GIT_PREFIX:-.}\" && \"$top/bin/jdiff\" diff \"$@\"; }; f"
jdiff = "!f() { : git diff; top=$(git rev-parse --show-toplevel); cd \"${GIT_PREFIX:-.}\" && \"$top/bin/jdiff\" diff \"$@\"; }; f"
jl = "!f() { : git log; top=$(git rev-parse --show-toplevel); cd \"${GIT_PREFIX:-.}\" && \"$top/bin/jdiff\" log \"$@\"; }; f"
jlog = "!f() { : git log; top=$(git rev-parse --show-toplevel); cd \"${GIT_PREFIX:-.}\" && \"$top/bin/jdiff\" log \"$@\"; }; f"The packages under internal/:
| Package | Content |
|---|---|
diff |
alignment (patience with Myers fallback), anchors, word-level changes, moved blocks |
source |
reading files and directories |
gitsrc |
git log, the files of a commit, the repository root |
notify |
changes to files reported by Linux (inotify) |
theme |
delta-compatible styles: sources (Spec) resolved into a Theme |
settings |
the settings |
config |
the configuration files, themes, state and marks |
ui |
the Bubble Tea views: commit list, file list, diff view, flow view, popups |