GitHub - simoarpe/ziggity: ⚡️ Ziggity an ultra fast, keyboard driven terminal UI for Git, written in Zig.

23 min read Original article ↗

Ziggity icon

A fast, keyboard-driven terminal UI for Git — a lazygit-style workflow, written in Zig.

Zig 0.16 License: MIT Platform

ziggity brings the core lazygit workflow — stage, commit, branch, rebase, and inspect history without leaving the terminal — to a small, dependency-light Zig binary. It is not a line-by-line port: it keeps lazygit's feel while leaning on idiomatic Zig, explicit memory ownership, plain git subprocesses (no libgit2), and libvaxis for the UI.

Ziggity - the animated about splash with a spinning donut

The Status panel's about splash, complete with a rotating ASCII donut.

Ziggity - the main view

Status, Files, Branches, Commits and Stash panels with a live diff preview and a context-sensitive footer.


Contents

Installation

Ziggity needs the git command on your PATH at runtime — it drives git as a subprocess. Install via Homebrew, grab a prebuilt binary, or build from source.

Homebrew (macOS / Linux)

brew install simoarpe/ziggity/ziggity

That taps simoarpe/homebrew-ziggity and installs the binary — the short form of:

brew tap simoarpe/ziggity
brew install ziggity

Upgrade with brew upgrade ziggity. Unlike a downloaded binary, a Homebrew install isn't quarantined, so it runs on macOS without a Gatekeeper prompt.

Manual Installation

Every release ships static binaries for macOS, Linux, and Windows — no Zig toolchain required.

macOS / Linux

Grab the archive for your platform from the releases page — aarch64-macos or x86_64-macos for macOS, x86_64-linux-musl or aarch64-linux-musl for Linux (the musl builds are fully static and run on any distro) — then:

# set VERSION to the latest release and pick your OS/arch — see the releases page
VERSION=v0.1.0
curl -LO https://github.com/simoarpe/ziggity/releases/download/$VERSION/ziggity-$VERSION-aarch64-macos.tar.gz
tar -xzf ziggity-$VERSION-aarch64-macos.tar.gz
sudo mv ziggity /usr/local/bin/      # or any directory on your PATH
command -v ziggity                   # confirm it's on your PATH

On macOS the binary isn't notarized, so Gatekeeper may block it on first run. Clear the quarantine flag once:

xattr -d com.apple.quarantine /usr/local/bin/ziggity

Windows

Download ziggity-<version>-x86_64-windows-gnu.zip, unzip it, and put ziggity.exe in a folder on your PATH. Requires Git for Windows.

Note: Windows builds are cross-compiled and not yet smoke-tested on Windows — treat them as experimental for now.

Verify the Download (Optional)

Each release includes a checksums.txt:

sha256sum --ignore-missing -c checksums.txt   # macOS: shasum -a 256 --ignore-missing -c checksums.txt

Compile from Source

Requirements: Zig 0.16.0 and git on your PATH.

git clone https://github.com/simoarpe/ziggity
cd ziggity
zig build -Doptimize=ReleaseSafe     # binary at ./zig-out/bin/ziggity
zig build test                       # (optional) run the test suite

The build leaves the binary at ./zig-out/bin/ziggity. Add it to your PATH so you can run ziggity from anywhere — either copy it into a directory already on your PATH:

sudo cp zig-out/bin/ziggity /usr/local/bin/

or add zig-out/bin to your PATH (e.g. in ~/.zshrc or ~/.bashrc):

export PATH="$PWD/zig-out/bin:$PATH"

Once installed, run ziggity inside any Git repository. Press ? for the in-app keybindings overlay (it opens scrolled to the panel you're on), and q to quit.

Highlights

  • Whole lazygit workflow — status, files, branches, commits, and stash panels with diff previews and a context-sensitive footer.
  • Hunk and line-level stagingenter a file to stage individual lines or hunks, with an optional side-by-side unstaged/staged split.
  • Full interactive rebase — drop/squash/fixup/edit/reword/move per commit, a plan editor (i), cherry-pick, custom patch building, autosquash, and rebase --onto from a marked base.
  • History & inspection — the real git log --graph DAG (ctrl+l) with first-parent jumps, ref-to-ref diffing (W) with reverse and three-dot (merge-base) modes, GPG signature verification (x), and log filtering (/).
  • Multi-selectionv / shift+arrows select a range in any list to act on many files, commits, branches, or stashes at once; * selects a branch's own commits.
  • Stays in sync — a quiet background git fetch keeps the inbound "commits to pull" arrow current on its own; slow and network operations run off the UI thread with a spinner while navigation stays live.
  • Thoughtful touchesprepare-commit-msg hook prefill, stash naming and a keep-the-working-tree snapshot, per-stash patch export, and bracketed paste so a pasted multi-line message never submits early.
  • Lightweight & explicit — one small binary, plain git subprocesses, no libgit2, fully remappable keys, themeable colours, and custom commands.

Screenshots

A visual tour of the main features, captured from a real terminal session (see docs/screenshots).

Hunk and Line-Level Staging

enter a file to open the staging view — stage or unstage individual lines or whole hunks (space), with an optional side-by-side unstaged/staged split.

Line-level staging view

Commits & History

The Commits panel previews each commit's diff, including its GPG signature status; enter opens the commit's changed files.

Commits panel with diff preview

ctrl+l opens the real git log --graph DAG in git's own colours. By default it shows the current branch and its upstream, so the commits you're behind by (the remote's lane) are visible right away; a toggles all branches. Jump to the current commit (HEAD) with @, or to a commit's first parent with p.

Commit graph viewer

Full interactive rebase: press i on a commit to open a plan editor — mark pick / drop / squash / fixup / edit, reorder with ctrl+j/ctrl+k, then run it.

Interactive rebase plan editor

Committing

c opens the commit dialog: a summary line plus an optional multi-line body, a live character count nudging the 50/72 rule, and (optionally) a prepare-commit-msg hook prefill.

Commit message dialog

Branches & Tags

The Branches panel shows ahead/behind arrows, upstream tracking and per-ref actions (merge, rebase, reset, checkout…); the Tags tab lists tags with their messages.

Branches panel

Tags tab

Stash

A Stash panel with a diff preview. The stash menu (s) offers all / +untracked / staged-only / a single file / keep-changes, each with an optional message — and w writes any stash to a git apply-able patch file.

Stash panel

Stash menu

Diffing & Menus

Mark any ref and diff another against it (W), with reverse and three-dot (merge-base) options. Destructive actions route through clear, explicit menus.

Diffing menu

Reset menu

Help & the About Screen

? opens the keybindings overlay, scrolled to the panel you're on. Selecting the Status panel shows an about screen with a live animation.

Keybindings help overlay

About screen

Features

Working tree & staging
  • Files panel from git status --porcelain -z; stage/unstage a file (space) or everything (a).
  • Hunk and line-level staging: enter opens a staging view to stage/unstage individual lines (v for a range) or whole hunks (tab switches the unstaged/staged side; \ toggles a side-by-side split — see Staging layout).
  • Directory-tree view (`) for working-tree files and a commit's / branch's file list: a / root, collapsible folders (enter), single-child chains compressed to a/b/c. Selecting a folder shows the combined diff beneath it; in the Files panel space stages the whole folder.
  • Live fuzzy path filtering (smart-case subsequence) with recent-filter recall, plus a status filter (staged / unstaged / tracked / untracked).
  • Discard a file — or a whole folder in tree view (d on a directory) — via a menu (all / unstaged only), or discard everything (D, confirmed). Add to .gitignore (i), copy a path (y).
  • Scoped periodic auto-refresh for external changes, plus a full refresh when the terminal regains focus.
Branches, tags & remotes
  • Branches panel from git branch --format: a "time ago" column, per-branch tracking status ( in sync, / ahead/behind, (gone)), and the upstream ref shown only when it isn't the obvious origin/<same-name>.
  • Branch actions (Local tab): new (n), rename (R), delete (d), merge (M), rebase (r), fast-forward (f), checkout-by-name (c), reset (g), force-checkout (F), tag (T), move commits to a new branch (N), open the pull-request page (G), sort menu (s).
  • Tags tab: checkout (space, detached), create (n, lightweight or annotated; overwrite prompts for --force), push to a remote (P), reset onto it (g), delete (d, local / remote / both). One remote skips the remote prompt.
  • Remotes tab: list remotes, drill into a remote's branches, add (n), edit URL (e), remove (x), set the current branch's upstream (u), delete a remote branch (d).
  • Worktrees & Submodules tabs (Files panel): list, create/add, open, update, remove, a submodule bulk menu (b), and switching repos in placespace/enter on a worktree (or enter on a submodule) re-roots the app onto it, with a parent / current breadcrumb and esc to walk back out.
Commits & history
  • Recent commits from git log (loaded incrementally — the log grows as you scroll toward the end, so it's never capped), each row showing the author's initials in a stable per-author colour and a highlighted Conventional Commits prefix (type in accent, scope muted, breaking ! in red; highlight_conventional_commits). The short hash is tinted by push state — red for commits not yet on the remote, the usual yellow once pushed.
  • Three tabs ([ / ] to switch): Commits, a Reflog recovery view (checkout space, reset HEAD g, branch n), and a Divergence view of the current branch versus its upstream — ahead (your local commits to push) grouped above behind (incoming commits to pull). The Divergence tab is read-only: checkout, branch-off, copy/paste and diff work; history-editing keys are disabled.
  • Commit (c), commit --no-verify (w), amend (A) in a centered editor with a summary line and optional multi-line body (tab switches fields). The editor nudges you toward the 50/72 rule: the summary shows a live character count (yellow, turning red once it exceeds commit_summary_limit — default 50), and the description shows a soft vertical guide at the body-wrap column (commit_body_guide — default 72, 0 off). When the editor opens, the repo's prepare-commit-msg hook runs (as it would for an interactive commit) and pre-fills the fields — e.g. a branch-derived ticket prefix — editable before you commit (prepare_commit_msg_hook = true, disable to skip it).
  • Per-commit: reset (g, soft/mixed/hard), revert (t), checkout (space, detached), branch from it (n), move commits to a new branch (N), tag (T), change author (a), and a copy-attribute menu (y: hash/subject/author).
  • GPG signatures: a signed commit's diff shows git's verification block (--show-signature), and x verifies the selected commit's signature on demand (result in a dialog) — no per-row cost.
  • A navigable changed-file list per commit (enter); d there discards a file's changes from that commit (rebase + amend).
  • Commit graph viewer (ctrl+l): the real git log --graph DAG in git's colours, loaded off-thread. The default view shows the current branch and its upstream (so incoming commits to pull are visible); a toggles all branches. @ jumps to the current commit (HEAD), p to the first parent, enter jumps the selection; mouse scroll/drag/click supported.
  • Log filtering (/): by message (--grep), author, or path; persists across refreshes and shows in the panel title.
  • Bisect (b): mark the selected commit good/bad, then mark good/bad/skip until the first bad commit is found.
Interactive rebase & patches
  • Per-commit interactive rebase: drop (d), squash (s), fixup (f), edit (e), reword (r), move (ctrl+j/ctrl+k), create fixup! (F), autosquash (S).
  • Rebase plan editor (i): compose a plan for the commits down to the selected one, mark each action (with range-select to mark/reorder many at once), then run it as one rebase.
  • Cherry-pick: copy commits to a clipboard (c), paste onto HEAD (V), clear (ctrl+r).
  • Custom patch building (ctrl+p): toggle files into a patch (space in a commit's file list), then apply it to the working tree (forward/reverse), remove it from its source commit, or reset it.
  • rebase --onto from a marked base (B), and mid-rebase amend (m's menu can amend the stopped edit commit and continue).
  • Conflict resolution: enter on a conflicted file opens a per-conflict resolver with line numbers — j/k walk between conflicts, o/t/b keep ours/theirs/both for the current one, u undoes the last pick, and the file is staged automatically once the last conflict is resolved. m still offers the whole-op continue/abort actions; MERGING/REBASING shown in the Status panel.
Multi-selection, diffing & stash
  • Range select in any list: v toggles a sticky range, shift+arrows extend one, * (Commits) selects every commit unique to the branch. The action key then applies to the whole range — stage/discard/edit files, drop/squash/fixup/edit/move/revert or copy commits, delete branches/tags, drop stashes, toggle commit files into a patch, or discard files from a commit.
  • Diffing mode (W): mark a commit/branch, select another, see git diff between the two refs in the main panel. Re-open the menu to reverse the direction (swap which ref is the diff's "from"), toggle a three-dot merge-base diff (base...target — only the target's own changes since it diverged, the GitHub-PR view; the title shows [base X ...]), or exit.
  • Stash menu (s): stash all / +untracked / staged-only / just the selected file / keep-changes (snapshot into a stash but leave the working tree untouched). Each asks for an optional message (empty = git's default WIP on … name). Apply / pop / drop / rename (r) / write to a patch file (wstash-<n>.patch, including untracked, git apply-able) on the Stash panel.
UX & quality-of-life
  • Action feedback (lazygit-style): fast actions succeed silently with a one-line summary; only failures pop a dialog. Slow / multi-step actions (merge, rebase, autosquash, patch apply, fast-forward, bisect) and network ops (fetch/pull/push) run off the UI loop with a spinner — navigation stays live. Tunable via result_dialog / command_output.
  • Non-blocking fetch/pull/push (f/p/P) with an in-status indicator.
  • In-app credential entry: on an HTTPS auth failure, a username + masked password/token prompt feeds git for the session (no external helper needed).
  • Safe undo of the last operation (ctrl+z, reflog hard-reset after confirmation).
  • Find the fixup base for a staged change and make a fixup! (ctrl+f, via blame).
  • Copy a hash/branch/tag to the clipboard (ctrl+o, OSC 52); open a commit/branch on its remote host (o).
  • Command-log overlay (@), themeable colours, fully remappable keys, and user-defined custom commands.

Keybindings

Press ? in the app for the full, always-current overlay. The essentials:

Key Action
15 Focus Status / Files / Branches / Commits / Stash
h l / arrows Move focus between side panels
j k / arrows Move selection
tab Focus the Diff panel (and back)
z Maximize the Diff panel to full screen (z/esc to exit)
[ ] Switch the focused panel's tabs (or staging side)
enter / esc Inspect in the main panel / step back
space Stage file · checkout branch · apply stash (by focus)
c · a · d · D Commit · stage-all · discard menu · discard all
v · shift+arrows · * Start range · extend range · select branch commits
i · ctrl+p · ctrl+l Rebase plan · custom patch · commit graph
f · p · P Fetch · pull · push
z · @ · ? · q Undo · command log · help · quit
Full keybinding reference
  • q / ctrl+c: quit
  • R: refresh
  • ?: keybindings help overlay (j/k to scroll), opened to the section for the current panel
  • ctrl+z: undo the last operation (reflog reset, after confirmation)
  • @: command log (recent git commands ziggity ran)
  • ctrl+o: copy the selected hash / branch / tag to the system clipboard
  • o: open the selected commit or branch on its remote host
  • W: diffing mode — mark a ref, then select another to diff; re-open the menu to reverse the direction, toggle a three-dot merge-base diff, or exit (esc also exits)
  • mouse: click a panel to focus; wheel to navigate/scroll
  • /: filter files by path live; enter accepts; esc clears
  • `: toggle the directory-tree view — Files panel and a commit's / branch's file list. enter collapses/expands the folder under the cursor; space stages a whole directory (Files) or adds it to the custom patch (commit files); the / root nests everything and diffs all changes when selected
  • ctrl+b: files status filter menu
  • j/k or arrows: move selection · h/l or arrows: cycle side panels
  • H/L: scroll the focused panel left/right (diff, or a list wider than the panel)
  • tab: focus the Diff panel from any side panel (press again to return). Over a working-tree file, enter there opens its stage/unstage view (the panel title and footer show the hint)
  • z: maximize the Diff panel to full screen — the side panels hide and the diff fills the terminal; press z again or esc to restore the normal layout
  • 15: focus status / files / branches / commits / stash
  • [/]: switch panel tabs (Files/Worktrees/Submodules · Local/Remotes/Tags · Commits/Reflog) or the staging unstaged/staged side
  • enter: inspect in the main panel; on a commit, drill into its changed files; on a file, open the staging view
  • enter on a file → staging view: j/k by line, v range, space stage line(s)/hunk, [/] switch side, \ split view, c/A commit/amend, esc back
  • space: stage/unstage file · checkout branch · apply stash (by focus)
  • e: open the file under view in your editor (Files, a commit's files, the staging/patch view, or a working-tree file's diff — see Editor)
  • n: new branch from HEAD (Branches, Local)
  • c: checkout a branch/ref by name (resolves local, DWIM-tracks remote, detaches onto a tag/commit, or - for previous)
  • R: rename the selected local branch (refresh elsewhere)
  • f: fast-forward the selected local branch (fetch elsewhere)
  • d: delete the selected local branch (menu: delete / force delete)
  • M / r: merge the selected branch / rebase the current branch onto it
  • g / F (Branches Local): reset to the branch / force-checkout
  • T / N (Branches Local): tag the branch / move its commits to a new branch
  • G / s (Branches Local): open the PR page / branch sort menu
  • space (Remotes/Tags): check out the remote branch or tag
  • n/P/g/d (Tags): new tag / push to a remote / reset onto it / delete
  • g / t (Commits): reset menu / revert
  • x (Commits): verify the selected commit's GPG signature (result in a dialog)
  • space/n/N (Commits/Reflog): checkout (detached) / branch from it / move commits to a new branch
  • T / a (Commits): tag the commit / change its author
  • y / ctrl+r: copy-attribute menu / clear the cherry-pick selection
  • i (Commits): interactive rebase plan editor
  • ctrl+l (Commits): commit graph viewer (j/k move, @ current commit, p first-parent, H/L pan, a toggle all-branches, ctrl+o copy, enter jump, esc close; mouse scroll/drag/click)
  • G (Commits): open the PR page · B: mark a rebase --onto base
  • W: diff the selected commit/branch against another marked ref
  • / (Commits): filter the log · b (Commits): bisect menu
  • ctrl+p: custom patch menu; space in a commit's files adds it to the patch
  • a: stage all (or unstage all) · s (Files): stash menu
  • d / D: discard menu for the file / discard all (confirmed)
  • c · w (Files): commit · commit --no-verify
  • i / y / ctrl+f (Files): add to .gitignore / copy path / make a fixup!
  • r (Stash): rename the selected stash
  • e / x / u (Remotes): edit URL / remove remote / set upstream
  • m: merge/rebase actions (continue, amend+continue, abort) while in progress — available from any panel, so you never have to switch to Files to continue/abort
  • f / p / P: fetch / pull / push
  • esc: step back one level (deselect, clear filter, exit diffing, cancel a prompt, leave a panel); never quits — only q does

Configuration

ziggity loads its defaults, then reads (each overriding the previous):

  1. the path in ZIGGITY_CONFIG
  2. <repo>/.ziggity.ini

There is no auto-loaded global file (no ~/.config/ziggity/, no XDG path). To apply settings everywhere, point ZIGGITY_CONFIG at a file from your shell profile — e.g. export ZIGGITY_CONFIG="$HOME/.config/ziggity/config.ini" — and a repo's .ziggity.ini overrides those per-repo. Without any file, settings fall back to per-repo auto-detection (notably the editor).

All settings (annotated .ini)
side_panel_width_percent = 34
diff_context = 3

# The commit-summary character count (shown in the commit/reword dialog) turns
# red once it goes over this many characters; below it, it stays yellow. Default
# 50 (the git subject-length convention). Set to 0 to disable the red threshold
# so the count always stays yellow.
commit_summary_limit = 50

# Column of the soft vertical guide drawn in the commit/reword message body,
# marking the git body-wrap width. Default 72. Set to 0 to disable the guide.
commit_body_guide = 72

# Colour the Conventional-Commits prefix (`type(scope)!:`) in the commit list:
# type in accent, scope muted, breaking `!` in red. Default on.
highlight_conventional_commits = true

# Run the repo's prepare-commit-msg hook when the commit dialog opens and pre-fill
# the message from its output (e.g. a branch-derived ticket prefix), matching an
# interactive commit. Default on; set false to skip the hook at open time.
prepare_commit_msg_hook = true

# Seconds between idle background working-tree refreshes (git status, run off the
# UI thread). On a big repo a tight interval makes git status thrash; default 10.
# Set to 0 to disable the periodic refresh (it still refreshes after operations
# and when the terminal regains focus).
refresh_interval_secs = 10

# Seconds between quiet background `git fetch`es so "commits to pull" (the
# inbound arrow / behind count) updates on its own — ahead/behind is measured
# against the remote-tracking ref, which only advances on a fetch. It never
# prompts: if the remote needs credentials (and no helper has them cached) it
# fails silently. Set to 0 to disable the background fetch. Default 60.
fetch_interval_secs = 60

# Side-panel layout (lazygit-style): the Status panel is a fixed height, the
# Files/Branches/Commits lists share the rest equally, and Stash stays small
# unless it's focused. Enable the "accordion" to grow the focused list panel.
expand_focused_side_panel = false  # focused list panel expands, others shrink
expanded_side_panel_weight = 2     # how much bigger the focused panel gets
staging_split = auto               # staging layout: off | on | auto (default)

# Editor for `e`. With nothing set, auto-detected from git core.editor, then
# $GIT_EDITOR / $VISUAL / $EDITOR, falling back to vim. See "Editor" below.
editor_preset =                    # vim | nvim | nano | emacs | micro | helix | vscode | sublime | zed | ...
editor_command =                   # explicit template, e.g. "code --reuse-window -- {{filename}}"
editor_in_terminal =               # true = suspend the TUI (terminal editors); false = just launch (GUI)

# Action feedback (lazygit-style). Default: silent success + bottom-bar summary,
# dialogs only on failure.
result_dialog = on_error    # on_error (default) | always | never
command_output = show       # show (default) custom-command output in a dialog,
                            # or `silent` to follow result_dialog instead

# Skip the confirm-before prompt for individual destructive actions (all default
# false, i.e. confirmations stay on). Names match the action:
skip_confirm.discard_all = false
skip_confirm.merge_branch = false
skip_confirm.rebase_branch = false
skip_confirm.delete_tag = false
skip_confirm.delete_remote_branch = false
skip_confirm.remove_worktree = false
skip_confirm.remove_remote = false
skip_confirm.undo = false
skip_confirm.force_push = false        # auto --force-with-lease when a push is rejected
skip_confirm.force_push_plain = false  # auto --force when force-with-lease is rejected

# Any keymap field can be remapped with key.<name>:
key.quit = q
key.refresh = R
key.file_filter = /
key.open_status_filter = ctrl+b
key.discard = d
key.commit = c
key.push = P
key.select = space
key.rename = R
key.fast_forward = f
key.reset = g
key.revert = t
key.range_select = v
key.paste_commits = V
key.select_branch_commits = *
key.toggle_tree = `
key.toggle_fullscreen = z
key.conflict_menu = m
key.command_log = @

# Theme colors are terminal palette indices (0-255):
color.selected_bg = 4
color.inactive_selected_bg = 8   # selected row in an unfocused panel (dimmer)
color.active = 10
color.added = 10
color.removed = 9
color.staged = 10
color.unstaged = 9
color.warning = 11
color.hunk = 14
color.header = 13
color.accent = 14
color.muted = 8
color.hash = 3                   # commit short hashes in the log
color.tag = 3                    # a tag's annotation/subject in the Tags list

Key values may be a single character or one of space, enter, tab, esc, backspace, ctrl+x, or alt+x. Every binding is remappable via key.<name>, every color via color.<name>.

Custom commands bind a key to a shell command run in the repo root (output in a dialog by default, or the message line with command_output = silent; the view refreshes afterward):

command.C = git commit --amend --no-edit
command.ctrl+t = ctags -R .

Custom commands take precedence over built-in bindings and only run when you press their key, so a repo-local .ziggity.ini cannot run anything on its own.

Staging Layout (staging_split)

The staging view (enter/tab on a file) shows one pane (the active side) or two side by side (Unstaged | Staged). staging_split decides how each file opens:

staging_split One-sided file Mixed file (staged and unstaged)
off single single
on split split
auto (default) single split

\ toggles split/single for the current file only — it isn't remembered, so the next file always opens with the layout above. The layout is chosen when a file opens and isn't re-decided mid-edit.

Editor (e)

e opens the file under view in an editor. The command is chosen in order:

  1. editor_command — your explicit template. Use {{filename}} for the path (quoted for you); if omitted, the path is appended.
  2. editor_preset — a named built-in: vi, vim, nvim, lvim, nano, emacs, micro, helix, kakoune, vscode, sublime, zed, bbedit, xcode.
  3. Auto-detection — the first of git core.editor, $GIT_EDITOR, $VISUAL, $EDITOR (matched to a preset, or run as a terminal editor).
  4. Fallbackvim.

Terminal editors (vim, nano, emacs, micro, helix, kakoune, …) suspend ziggity and resume when you quit them; GUI editors (VS Code, Sublime, Zed, …) just launch. editor_in_terminal = true|false forces which behaviour to use.

Status & Roadmap

The lazygit-parity feature roadmap is complete. Known smaller gaps:

  • Redo — undo (ctrl+z) is implemented; redoing an undo is not.
  • Move a custom patch to a different commit — only apply / remove-from-commit are implemented.
  • Editing the live rebase todo mid-rebase — ziggity composes the whole plan up front in its i editor (with range-select), so there's no paused-rebase todo view to edit.
  • Reword in your external $EDITOR — ziggity rewords in its own in-app editor (r), which serves the same purpose.
  • Full lazygit config compatibility and score-based fuzzy ranking (the current fuzzy filter matches but preserves order).

See docs/ENHANCEMENTS_OVER_LAZYGIT.md for where ziggity deliberately diverges.

Development

zig build                                  # debug build
zig build test --summary all               # run all tests
zig build -Doptimize=ReleaseFast           # optimized build

Source Layout

File Responsibility
src/main.zig Process entry point and startup error reporting
src/app.zig App state, focus, selections, actions, refreshes
src/git.zig Git subprocess wrapper and parsers
src/tui.zig libvaxis event loop, layout, and rendering
src/model.zig Owned domain models and status derivation
src/config.zig Defaults and the keybinding/INI parser
src/actions.zig Action names shared by the app and key layers

Supporting modules split cohesive areas out of app.zig: staging.zig, patch.zig, commitops.zig, rebaseplan.zig, drills.zig, diffmode.zig, stash.zig, branches.zig, commits.zig, filetree.zig, commitgraph.zig, editor.zig, diff.zig, credentials.zig. All Git data is loaded through git commands rather than reimplementing Git internals.

libvaxis Gotcha: Cells Store Graphemes by Reference

libvaxis stores each screen cell's grapheme as a slice into the source text (Window.print does .grapheme = grapheme.bytes(segment.text) — it does not copy the bytes), and the frame is flushed after render() returns. So any text drawn via vaxis' printSegment/print must be backed by memory that outlives render() — a string literal or App-owned buffer, never a stack-local buffer (which dangles and renders as garbage / U+FFFD, intermittently).

In src/tui.zig:

  • The local print / printSpan / printAnsi helpers are safe with any buffer lifetime (they map bytes through a static glyph table), so formatting into a stack buffer is fine for list rows, popups, the footer, etc.
  • Only vaxis printSegment is by-reference, and it's used only for panel/popup titles — dynamic titles must live in an App-owned buffer (app.*_title_buf).

Sponsor

Ziggity is free and open source, built in spare time. If it saves you some of yours, you can support its development on Ko-fi — no pressure, no paywalled features, just a tip jar that helps keep the work going.

Support Ziggity on Ko-fi

ko-fi.com/simoarpe

You can also help for free: star the repo, report bugs, and tell a friend. 💛

License

MIT © 2026 Simone Arpe.