A fast, keyboard-driven terminal UI for Git — a lazygit-style workflow, written in Zig.
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.
The Status panel's about splash, complete with a rotating ASCII donut.
Status, Files, Branches, Commits and Stash panels with a live diff preview and a context-sensitive footer.
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.
brew install simoarpe/ziggity/ziggityThat taps simoarpe/homebrew-ziggity
and installs the binary — the short form of:
brew tap simoarpe/ziggity
brew install ziggityUpgrade with brew upgrade ziggity. Unlike a downloaded binary, a Homebrew
install isn't quarantined, so it runs on macOS without a Gatekeeper prompt.
Every release ships static binaries for macOS, Linux, and Windows — no Zig toolchain required.
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 PATHOn 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/ziggityDownload 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.
Each release includes a checksums.txt:
sha256sum --ignore-missing -c checksums.txt # macOS: shasum -a 256 --ignore-missing -c checksums.txtRequirements: 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 suiteThe 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.
- Whole lazygit workflow — status, files, branches, commits, and stash panels with diff previews and a context-sensitive footer.
- Hunk and line-level staging —
entera 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, andrebase --ontofrom a marked base. - History & inspection — the real
git log --graphDAG (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-selection —
v/shift+arrowsselect 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 fetchkeeps 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 touches —
prepare-commit-msghook 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
gitsubprocesses, no libgit2, fully remappable keys, themeable colours, and custom commands.
A visual tour of the main features, captured from a real terminal session (see
docs/screenshots).
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.
The Commits panel previews each commit's diff, including its GPG signature status;
enter opens the commit's changed files.
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.
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.
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.
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.
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.
Mark any ref and diff another against it (W), with reverse and three-dot
(merge-base) options. Destructive actions route through clear, explicit menus.
? opens the keybindings overlay, scrolled to the panel you're on. Selecting the
Status panel shows an about screen with a live animation.
Working tree & staging
- Files panel from
git status --porcelain -z; stage/unstage a file (space) or everything (a). - Hunk and line-level staging:
enteropens a staging view to stage/unstage individual lines (vfor a range) or whole hunks (tabswitches 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 toa/b/c. Selecting a folder shows the combined diff beneath it; in the Files panelspacestages 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 (
don 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 obviousorigin/<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 place —space/enteron a worktree (orenteron a submodule) re-roots the app onto it, with aparent / currentbreadcrumb andescto 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 (checkoutspace, reset HEADg, branchn), 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 (tabswitches fields). The editor nudges you toward the 50/72 rule: the summary shows a live character count (yellow, turning red once it exceedscommit_summary_limit— default 50), and the description shows a soft vertical guide at the body-wrap column (commit_body_guide— default 72,0off). When the editor opens, the repo'sprepare-commit-msghook 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), andxverifies the selected commit's signature on demand (result in a dialog) — no per-row cost. - A navigable changed-file list per commit (
enter);dthere discards a file's changes from that commit (rebase + amend). - Commit graph viewer (
ctrl+l): the realgit log --graphDAG in git's colours, loaded off-thread. The default view shows the current branch and its upstream (so incoming commits to pull are visible);atoggles all branches.@jumps to the current commit (HEAD),pto the first parent,enterjumps 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), createfixup!(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 (spacein a commit's file list), then apply it to the working tree (forward/reverse), remove it from its source commit, or reset it. rebase --ontofrom a marked base (B), and mid-rebase amend (m's menu can amend the stoppededitcommit and continue).- Conflict resolution:
enteron a conflicted file opens a per-conflict resolver with line numbers —j/kwalk between conflicts,o/t/bkeep ours/theirs/both for the current one,uundoes the last pick, and the file is staged automatically once the last conflict is resolved.mstill offers the whole-op continue/abort actions;MERGING/REBASINGshown in the Status panel.
Multi-selection, diffing & stash
- Range select in any list:
vtoggles a sticky range,shift+arrowsextend 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, seegit diffbetween 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 defaultWIP on …name). Apply / pop / drop / rename (r) / write to a patch file (w→stash-<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.
Press ? in the app for the full, always-current overlay. The essentials:
| Key | Action |
|---|---|
1–5 |
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: quitR: refresh?: keybindings help overlay (j/kto scroll), opened to the section for the current panelctrl+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 clipboardo: open the selected commit or branch on its remote hostW: 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.entercollapses/expands the folder under the cursor;spacestages a whole directory (Files) or adds it to the custom patch (commit files); the/root nests everything and diffs all changes when selectedctrl+b: files status filter menuj/kor arrows: move selection ·h/lor arrows: cycle side panelsH/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,enterthere 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; presszagain orescto restore the normal layout1–5: focus status / files / branches / commits / stash[/]: switch panel tabs (Files/Worktrees/Submodules · Local/Remotes/Tags · Commits/Reflog) or the staging unstaged/staged sideenter: inspect in the main panel; on a commit, drill into its changed files; on a file, open the staging viewenteron a file → staging view:j/kby line,vrange,spacestage line(s)/hunk,[/]switch side,\split view,c/Acommit/amend,escbackspace: 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 itg/F(Branches Local): reset to the branch / force-checkoutT/N(Branches Local): tag the branch / move its commits to a new branchG/s(Branches Local): open the PR page / branch sort menuspace(Remotes/Tags): check out the remote branch or tagn/P/g/d(Tags): new tag / push to a remote / reset onto it / deleteg/t(Commits): reset menu / revertx(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 branchT/a(Commits): tag the commit / change its authory/ctrl+r: copy-attribute menu / clear the cherry-pick selectioni(Commits): interactive rebase plan editorctrl+l(Commits): commit graph viewer (j/kmove,@current commit,pfirst-parent,H/Lpan,atoggle all-branches,ctrl+ocopy,enterjump,escclose; mouse scroll/drag/click)G(Commits): open the PR page ·B: mark arebase --ontobaseW: diff the selected commit/branch against another marked ref/(Commits): filter the log ·b(Commits): bisect menuctrl+p: custom patch menu;spacein a commit's files adds it to the patcha: stage all (or unstage all) ·s(Files): stash menud/D: discard menu for the file / discard all (confirmed)c·w(Files): commit · commit--no-verifyi/y/ctrl+f(Files): add to.gitignore/ copy path / make afixup!r(Stash): rename the selected stashe/x/u(Remotes): edit URL / remove remote / set upstreamm: merge/rebase actions (continue, amend+continue, abort) while in progress — available from any panel, so you never have to switch to Files to continue/abortf/p/P: fetch / pull / pushesc: step back one level (deselect, clear filter, exit diffing, cancel a prompt, leave a panel); never quits — onlyqdoes
ziggity loads its defaults, then reads (each overriding the previous):
- the path in
ZIGGITY_CONFIG <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 listKey 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.
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.
e opens the file under view in an editor. The command is chosen in order:
editor_command— your explicit template. Use{{filename}}for the path (quoted for you); if omitted, the path is appended.editor_preset— a named built-in:vi,vim,nvim,lvim,nano,emacs,micro,helix,kakoune,vscode,sublime,zed,bbedit,xcode.- Auto-detection — the first of git
core.editor,$GIT_EDITOR,$VISUAL,$EDITOR(matched to a preset, or run as a terminal editor). - Fallback —
vim.
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.
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
ieditor (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.
zig build # debug build
zig build test --summary all # run all tests
zig build -Doptimize=ReleaseFast # optimized build| 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 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/printAnsihelpers 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
printSegmentis by-reference, and it's used only for panel/popup titles — dynamic titles must live in an App-owned buffer (app.*_title_buf).
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.
You can also help for free: star the repo, report bugs, and tell a friend. 💛
MIT © 2026 Simone Arpe.














