Your git changes in a Rex pane, right next to your terminal: a tree of the files that changed and a colored diff of the selected file. Review them, mark what you've read, commit, and handle your branches (fetch, checkout, pull, push, merge, delete, history) without leaving the panel.
Changes 6 files Branches ⎇ main ✓ 2/6 staged +31 −13
──────────────────────────────────────────────────────────────────────
▾ app/ +24 −11
├─ ▾ services/ +18 −10
▎│ ├─ ✓ A frete.py +7
│ ╰─ ○ M pagamento.py +11 −10
╰─ ▾ views/ ✓ +4 −1
╰─ ✓ M pedido.py +4 −1
○ M README.md +7 −1
────────────────────────────────────────────────────────────────────
app/services/pagamento.py 1–29 of 29
⋯ @@ -1,17 +1,18 @@
1 − from decimal import Decimal
1 + from decimal import Decimal, ROUND_HALF_UP
2
3 class Pagamento:
↑↓ navigate ←→ folders space stage/fold c commit x whole file 2 branches q quit
The panel has three screens:
- Changes (
1): the tree and the diff above. Review, stage, and commit. - Branches (
2): your local branches, plus remote ones you don't have yet. Fetch, check out, pull, push, merge, and delete. - History (Enter on a branch): that branch's commits.
In a wide pane (full screen or zoomed) the Changes screen puts the tree on the left and the diff on the right.
- Tree of changed files only. Folders with a single subfolder collapse into
one row (
src/components/ui/), each file shows its status (A,M,D) and+N −N, and folders add up their files. - Mark files as reviewed. Press space on a file to
git addit, and again to unstage it. Each file shows✓(staged),◐(staged, then changed again), or○(not staged); reviewed files fade, folders get a✓when everything in them is staged, and the header counts your progress. - Discard what you don't want.
don a file or folder throws its changes away, after you confirm withy. - Commit from the panel. Press
c, type the message, and Enter commits what's staged. It runs the realgit, so your hooks and commit signing work as usual. - Branches. Press
2for a list of your local branches with how far each one is ahead/behind its upstream, followed by remote branches you haven't checked out yet.ffetches, space checks one out,ppulls it, Shift+P pushes it,mmerges it into the branch you're on,ddeletes it, and Enter shows its commit history. - A diff that reads well. Line numbers, full-width green/red backgrounds, syntax colors, and the exact words that changed highlighted inside each line. Long lines wrap to the pane width, keeping the code's indentation.
- Follows your Rex setup. Everything is drawn as terminal text, so it uses your Rex font (ligatures included). Colors come from your Rex theme: the panel asks the terminal for its foreground, background, and palette, and syntax colors use the theme's own ANSI colors. Switch themes and the panel follows.
- Live. It watches the repository and updates as soon as a file changes,
including after
git addor a commit. - Fast. A single native binary. Only the visible lines are drawn, the diff is computed for the selected file only, and git is read in-process (libgit2) on a background thread, so the UI never blocks.
- Keyboard and mouse. Arrow keys, click (files, checkboxes, folders, and the tabs in the header), scroll, and a draggable divider between the tree and the diff.
- Rex on macOS. (Linux should work but is untested.)
giton yourPATH. Reading the repository doesn't need it, but commit, checkout, pull, push, and merge run the realgitcommand.- A Rust toolchain to build it:
brew install rust, or rustup.
git clone https://github.com/victorvf/rexdiff
cd rexdiff
make installmake install builds a release binary and links ~/.local/bin/rexdiff to it.
Make sure ~/.local/bin is on your PATH. Since it's a link, running
make build again later updates the command in place.
Add a Rex action and bind it to a key. Copy
extras/rex/init.lua into your Rex config at
~/.config/rex/init.lua (or append it if you already have one), then reload:
rex config reloadNow Option + Shift + ? opens rexdiff next to the focused pane, for the folder that pane is in, and focuses it. Press it again (from either pane) to close it. The action also shows up in the command palette as "rexdiff: Toggle Changes".
Rex matches keys without Shift applied, so ? is written as alt+shift+/.
Change the key in the last line of the file if you prefer another one.
From any Rex terminal inside a git repository:
rexdiff # open the panel next to this pane (45% of the width)
rexdiff hide # close it
rexdiff toggle # open or close it
rexdiff --ratio 35 # narrower panel
rexdiff --below # open below instead of to the right
rexdiff --focus # focus the panel when it opens
rexdiff view # run the panel in the current terminal
rexdiff helpEach terminal pane gets its own panel. Running rexdiff again while it's open
does nothing.
| Key | Action |
|---|---|
↑ ↓ / j k |
Previous / next row (folders and files) |
← → / h l |
Previous / next folder. An open folder lands on its first file |
space / Enter |
On a file: stage it, or unstage it if it's fully staged. On a folder: open or close it |
Tab / Shift-Tab |
Next / previous file, wrapping around |
f / b, PgDn / PgUp |
Scroll the diff one page |
J / K |
Scroll the diff one line |
g / G |
Top / bottom of the diff |
c |
Commit what's staged (see below) |
d |
Discard the changes of the file or folder under the cursor (see below) |
x |
Toggle between changed hunks only and the whole file |
+ / - |
Make the tree bigger / smaller |
1 / 2 |
Changes / Branches screen (or click the tabs in the header). They work on every screen |
r |
Reload the repository |
q |
Quit (the pane closes) |
With the mouse: click a file to show it, click its checkbox to stage or unstage it, click a folder to open or close it, scroll over the tree or the diff, and drag the divider to resize.
The diff always compares your working tree with HEAD, so staging a file
doesn't change what you see, only its checkbox.
Stage the files you want with space, then press c to open the commit box in
the footer:
| Key | Action |
|---|---|
Enter |
Commit |
Esc |
Cancel |
Ctrl+W / Option+Backspace |
Delete the last word |
Ctrl+U |
Clear the message |
The footer shows the result, such as ✓ Committed 1a2b3c4, or what went
wrong. Nothing is pushed.
git runs detached from the panel's terminal, so it can't stop to ask for
anything: a hook that needs input makes the commit fail with a message instead
of hanging.
d on a file or folder asks, in red in the footer, before throwing its changes
away. Press y to go ahead; any other key cancels.
- A file the last commit has (modified, deleted, partly staged) goes back to
that version, both in the index and on disk
(
git restore --source=HEAD --staged --worktree). - A new file is taken out of the index and deleted, along with any folder that ends up empty. The prompt says how many new files will go, because there's no getting them back.
- On a folder, it does this to every changed file inside it.
Press 2 (or click Branches in the header) to see your local branches,
most recently committed first:
● feature/frete ✓ origin/feature/frete frete: ajuste 2 min ago
main ↑5 ↓1 origin/main pedido: só commit 1 hour ago
old-idea not pushed try another layout 3 weeks ago
● marks the branch you're on and ◆ a branch that's checked out in another
worktree (git worktree), with that worktree's folder in parentheses:
aux/worktree (sistema_2). Git only lets
a branch be checked out in one place, so space and d on a ◆ branch tell you
which worktree has it instead of trying. Next to each one: ✓ when it matches its
upstream, ↑N commits to push, ↓N commits to pull, or not pushed when it
has no upstream yet. The ahead/behind counts use what git last fetched; press
f to fetch.
Below your local branches come the remote ones that no local branch tracks
yet, dimmed and marked remote (origin/pr/new-screen). Space on one creates
a local branch that tracks it and switches to it, which is how you check out
someone's pull request: f, find the branch, space. Enter shows its history
and m merges it into your branch; pull, push, and delete ask you to check it
out first.
| Key | Action |
|---|---|
↑ ↓ / j k |
Move |
space |
Check out the branch (git switch). On a remote branch, create a local one that tracks it (git switch --track) |
Enter |
Show the branch's commit history |
p |
Pull the branch |
P (Shift+P) |
Push the branch |
m |
Merge the branch into the one you're on (updating it from its upstream first) |
d |
Delete the local branch, after y |
D (Shift+D) |
Delete it even if it has unmerged commits, after y |
f |
Fetch from all remotes, dropping remote branches deleted there (git fetch --all --prune) |
Esc / 1 |
Back to Changes |
Push sends the branch to its upstream, or to origin with --set-upstream
when it doesn't have one yet (git push -u origin <branch>). It works on any
branch in the list, not just the current one. If the remote has commits you
don't have, the push is rejected and the footer tells you to pull first.
Checkout refuses when your uncommitted changes would be overwritten.
Pull on the branch you're on runs git pull, so it merges or rebases as your
git config says. On any other branch it can only fast-forward (merging needs a
checkout); if that branch has diverged, the footer asks you to check it out
first. Problems show up in the footer in plain words: merge conflicts (fix them
in the Changes screen, then commit), diverged branches with no pull.rebase
setting, or local changes in the way.
m brings another branch into the one you're on, the usual "update my feature
branch with main": on feature/x, press m on main. It first
fast-forwards main from origin/main (like p), then runs git merge main.
If your local main has diverged from origin/main it stops before merging and
tells you, so you never merge a stale main by accident. Conflicts are reported
the same way as with pull.
d deletes the local branch after you confirm with y (git branch -d). Git
refuses when the branch has commits that aren't on its upstream or on the
branch you're on, and the footer says so; D (Shift+D) deletes it anyway
(git branch -D), with a warning that those commits will be lost. You can't
delete the branch you're on, and the remote branch is left alone.
Press Enter on a branch to see its commits, newest first (up to 1,000):
‹ feature/frete · 13 commits · ↑1 not pushed
↑ 9005e63 (feature/frete) local: muda linha Demo just now
31cd565 remoto: novo arquivo Other 5 min ago
b829a13 (main) moeda: ajuste Demo 1 hour ago
Each row has the short hash, the branches and tags pointing at that commit,
the message, the author (when the pane is wide enough), and when it was made.
↑ marks commits that aren't on the branch's upstream yet, the ones a push
would send. A local branch and its remote counterpart on the same commit show
as just the local name. Merge commits are dimmed.
Move with ↑ ↓ (f/b for a page, g/G for the ends) and go back to the
branches with Esc.
- Stacked (default): tree on top, diff below. The tree takes up to about a third of the height.
- Side by side: when the panel is at least 160 columns wide (for example, zoomed or full screen), the tree goes on the left and the diff on the right. It switches back when the panel gets narrower.
rexdiff(or the shortcut) runsrex splitto create a pane next to the current one, runningrexdiff viewin that folder. It records which pane it opened in~/.local/state/rexdiff/, sohideandtogglecan close it.rexdiff viewis the panel itself, a terminal UI built with ratatui:- a worker thread reads the repository with git2
(libgit2): the list of changed files compared with
HEAD(staged, unstaged, and untracked) and their index state, the diff of the selected file, the local branches with their ahead/behind counts, and a branch's history; - the same thread runs what you ask for. Stage and unstage go through
libgit2; commit, checkout, pull, push, and merge run the
gitcommand, so hooks, commit signing, credential helpers, and yourpull.rebasesetting all apply. Those commands run detached from the panel's terminal, so they can never take over the screen to ask for input; - a file watcher (FSEvents on macOS, via notify)
triggers a reload when something relevant changes, ignoring files covered
by
.gitignore; - syntax colors come from syntect, mapped to the terminal's ANSI palette instead of a fixed theme, and the changed words come from a word-level diff (similar);
- theme colors are read with terminal queries (OSC 10, 11, and 4), and the green/red backgrounds are mixed from them.
- a worker thread reads the repository with git2
(libgit2): the list of changed files compared with
qends the program and Rex closes the pane.
Reading the repository never takes git's index lock, so the panel can't get in
the way of your own git commands. The only writes are the ones you ask for:
| Where | Key | What runs |
|---|---|---|
| Changes | space on a file |
git add / git restore --staged |
| Changes | c, then Enter |
git commit -m <message> |
| Changes | d, then y |
git restore --source=HEAD --staged --worktree for files the last commit has; new files are unstaged and deleted |
| Branches | space |
git switch <branch>, or git switch --track <remote>/<branch> on a remote branch |
| Branches | f |
git fetch --all --prune |
| Branches | p |
git pull on the current branch; git fetch <remote> <branch>:<branch> (fast-forward only) on any other |
| Branches | P |
git push, or git push -u origin <branch> the first time |
| Branches | m |
Fast-forward the branch from its upstream, then git merge <branch> |
| Branches | d / D, then y |
git branch -d <branch> / git branch -D <branch> |
If the panel ever crashes, the error is written to
~/.local/state/rexdiff/panic.log.
| File | What it does |
|---|---|
src/main.rs |
Command line: show, hide, toggle, view, and the Rex integration |
src/app.rs |
The panel: the Changes, Branches, and History screens, keyboard/mouse, layout, and drawing |
src/git.rs |
Background worker: changed files, diffs, word-level highlights, branches, history, and the git commands (stage, commit, checkout, pull, push, merge) |
src/highlight.rs |
Syntax highlighting with the terminal palette |
src/tree.rs |
Builds the folder tree from the changed files |
src/theme.rs |
Reads the terminal colors and derives the panel palette |
src/wrap.rs |
Wraps diff lines to the pane width |
- Syntax colors use syntect's built-in grammars: TypeScript uses JavaScript's, Vue and Svelte use HTML's.
- Renames show up as a deleted file plus an added one.
- Very large diffs are cut at 50,000 lines, and syntax colors are skipped above 20,000.
- The ↑/↓ counts and the remote branches on the Branches screen are as of the
last fetch; press
fto update them. - Pull requests from forks aren't on your remotes, so they don't show up as
remote branches; use
gh pr checkoutfor those. - Commit messages are a single line.
- History shows up to 1,000 commits per branch.
- Commands can't answer prompts: an SSH key without an agent, or a hook that asks for input, makes them fail with a message instead.
MIT: use it, copy it, change it, and share it, as long as the copyright and license notice stay with the code.