Skip to content

Repository files navigation

cmux-changes

A cmux Dock control that shows the git changes of the selected workspace, IDE-sidebar style. Diff, stage, discard and open a file in your editor without leaving the Dock — and it follows you as you switch workspace.

The Changes control in the cmux Dock listing staged and unstaged files, next to a file diff it opened in cmux's diff viewer

The control is the panel on the right — a Staged Changes section, a Changes section, status letters down the right edge, deletions struck through. on ProductCard.tsx opened the diff on the left, in cmux's own viewer.

Install

curl -fsSL https://raw.githubusercontent.com/AlexDemzz/cmux-changes/main/install.sh | bash
cmux-changes dock

The first line builds the panel and puts cmux-changes on your PATH — it needs the Rust toolchain (rustup.rs). The second adds the control to ~/.config/cmux/dock.json, backing that file up first and leaving your other controls alone. Run it again after moving things and it just repoints itself.

What lands on your PATH is a launcher, not the binary: it rebuilds the panel when the source changed (a no-op ~0.3 s otherwise), so a plain git pull is a complete update.

Prefer to edit dock.json yourself? The control is:

{
  "controls": [
    {
      "id": "changes",
      "title": "Changes",
      "command": "$HOME/.local/share/cmux-changes/scripts/cmux-changes.sh run",
      "height": 500
    }
  ]
}

Then reload the cmux Dock config. Reloading replaces the whole Dock, so any other control you have restarts too — there is no way to restart a single one.

Usage

key does
, k j move
g G top, bottom
open the diff in cmux's diff viewer — or fold, on a section header
e open the file in your IDE
stage / unstage
x discard
Changes tab, Commits tab — positional, they do not toggle
alternate tabs
o open the commit on GitHub (Commits tab)
r reload
q quit

Mouse: one click selects, two clicks within 400 ms open, the wheel scrolls. One click on a section header folds it, one click on a tab label switches tab. One click never opens a file — headers are the deliberate exception, since folding is instant and reversible.

The Commits tab lists git log -n 100 HEAD, with unpushed commits marked . Each tab remembers its own cursor and scroll. Nothing ever switches tab for you.

Configuration

e opens an editor, resolved in this order:

  1. $CMUX_DOCK_IDE
  2. ide= in ~/.config/cmux-changes/config
  3. the first of cursor, code found on PATH
cmux-changes editor          # show all three rungs and which one wins
cmux-changes editor code     # write ide=code

A Dock control also takes its own env map, if you would rather keep it in dock.json:

"env": { "CMUX_DOCK_IDE": "code" }

$VISUAL and $EDITOR are deliberately ignored. cmux starts a Dock control with $shell -lc — a login but non-interactive shell, so zsh reads .zshenv and not .zshrc, and those variables are usually empty there. Where they are set they name a terminal editor, which has nowhere to render from a detached GUI launch.

Set icons=0 in ~/.config/cmux-changes/config to replace the Nerd Font glyphs with a dot.

Running it outside the Dock

cmux-changes run

Runs the same panel in the current terminal — handy in a cmux split, or to try it before registering the control.

Updating

cmux-changes update

Pulls the latest version in place. If you have edited the files yourself it stops and says so, rather than stashing your work behind your back. The launcher rebuilds the panel on its next start; reload the Dock to restart it now.

How it works

  • It follows workspace selection: cmux workspace list --json for the current worktree, then cmux events --name workspace.selected on a fifo. A 1 s idle tick re-runs git status -uall (~43 ms warm) as a safety net.
  • Diffs are piped to cmux diff, always targeting the workspace explicitly. A Dock terminal exports its own surface, which lives in no workspace, and cmux would otherwise fail with Source surface not found.
  • Rendering is plain absolute cursor moves and SGR — no curses. Every row is drawn to cols - 1, leaving one column of slack so nothing wraps and scrolls the frame.
  • The help bar picks a variant from hand-measured width thresholds. Change the length of a help string and you have to re-measure, or it silently clips to . tests/t4_widths.py is what catches that.
  • The panel is Rust (crossterm for raw mode and input events, the escape sequences written by hand). It started life as a bash script; the pty test suite is what pinned the rewrite to the exact same rendering.

Tests

A pty harness that drives the real panel (through its launcher) and asserts on the rendered screen — fake-cmux logs every CLI call, so "did it open a diff" is measured, not assumed.

bash tests/mkrepos.sh "$PWD/tests/repos"   # 4 throwaway repos, ~1.6 MB, ~20 s
cd tests && python3 t3_click.py
file covers
harness.py pty plus a minimal ANSI screen emulator; key and SGR-mouse injection
t1_layout.py tab switching, underline position
t2_nav.py scroll, clamp, g/G, fold, per-tab cursor memory
t3_click.py 9 click assertions, single vs double, headers, tab labels
t4_widths.py 9 widths × 4 repo states, asserts zero column overflow. ~2 min
t5_states.py the three upstream states, empty states
t6_actions.py stage, discard, list-goes-empty. Mutates repos/noup, restores it

Pass a path as argv[1] to run any of them against a copy elsewhere on disk.

Requirements

  • cmux
  • the Rust toolchain — cargo on PATH (rustup.rs)
  • git
  • python3, for the tests
  • a Nerd Font, for the file-type glyphs

Uninstall

cmux-changes uninstall

Removes the command, its config and its control in ~/.config/cmux/dock.json. Reload the Dock afterwards.

License

MIT

About

A cmux Dock control for git changes: diff, stage, discard, and open a file in your IDE, following the selected workspace.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages