Configurable launchers (git TUI and review)
Added by #75 / #76; the embedded PTY overlay landed in #35.
Two configurable launchers, the git TUI (lazygit by default) and the review tool, are driven by user-configurable .gwm.toml sections sharing the same mini-API. Both take a command template string, substitute placeholders, split with shell-words, and exec the result with cwd = worktree.path.
Each launcher has two bindings (issue #35 / #290) that share the same resolved command but differ in how the child is hosted:
| Key | Action slug | How it runs |
|---|---|---|
l |
lazygit_pty |
git TUI in an embedded PTY overlay (~90 % of the screen) |
L |
lazygit_fullscreen |
git TUI fullscreen, honours [git_tui] fullscreen |
r |
review_pty |
review tool in an embedded PTY overlay (~90 % of the screen) |
R |
review_fullscreen |
review tool fullscreen, honours [review] fullscreen |
All four slugs are rebindable under [tui.keys].
The embedded PTY overlay (l / r)
Section titled “The embedded PTY overlay (l / r)”
The PTY variants run the launcher inside the TUI: no alt-screen swap, no suspend. The child program is driven by a real pseudo-terminal (portable-pty) and rendered as a tui-term widget in a modal sized to roughly 90 % × 90 % of the terminal. The worktree list stays visible behind the overlay border.
- Keystrokes are forwarded straight to the child, so lazygit / your review tool behave exactly as they do in a normal terminal.
Esccloses the overlay and returns to the list. Inside lazygit, its ownqquits lazygit, and the overlay auto-closes when the child exits.- The PTY variants ignore the section’s
fullscreenfield. That field only governs the fullscreen bindings below. The PTY overlay is always embedded.
The fullscreen variants (L / R)
Section titled “The fullscreen variants (L / R)”The L / R bindings keep the original launch model. They read the same resolved command but host the child outside the overlay:
| Step | What happens |
|---|---|
| 1 | Read the [git_tui] or [review] section from .gwm.toml (defaults below). |
| 2 | Resolve placeholders: {path}, {base}, {head}, {diff}. |
| 3 | Split the result into argv with shell-words (handles quoting like a POSIX shell). |
| 4 | Probe argv[0] against $PATH. Missing binary → status-bar error, no spawn, no flicker. |
| 5 | If fullscreen = true → suspend the TUI (raw mode off, alt-screen left), spawn Command::status(), restore on exit. |
| 6 | If fullscreen = false → keep the TUI in the alt-screen, spawn Command::output(), block until exit, drop the first line of stderr on the status bar. |
The {diff} placeholder is lazy: gwm only materialises a tempfile holding git diff {base}..{head} when the template references {diff}. The tempfile’s lifetime is bound to the spawned process (its Drop impl unlinks on completion), so the reviewer always sees a consistent snapshot.
fullscreen = falseis synchronous. The TUI is unresponsive until the child process exits:Command::output()waits for it. Fine for quick print-only tools (claude --print,gh pr view --web); pickfullscreen = truefor anything long-running so the TUI is properly suspended and your terminal stays usable.
[git_tui]: the git TUI (l / L)
Section titled “[git_tui]: the git TUI (l / L)”Default: lazygit -p {path}, fullscreen.
[git_tui]# command template (single placeholder: {path}). Pre-v0.6 behaviour# is the default — your existing configs see zero change.command = "lazygit -p {path}"
# suspend the TUI for the call (recommended for TUIs that own the alt-screen)fullscreen = trueAny tool that takes a worktree path works:
[git_tui]command = "gitui -d {path}" # gitui
[git_tui]command = "tig --all" # tig (run inside the worktree)fullscreen = true
[git_tui]command = "code -n {path}" # VS Code in a new windowfullscreen = false # IDE forks, don't suspend[review]: the review tool (r / R)
Section titled “[review]: the review tool (r / R)”No default: both r and R are inert until configured.
Two equivalent ways to configure it:
A) free-form command (full control)
Section titled “A) free-form command (full control)”[review]command = "claude --print 'review the diff {base}..{head}'"fullscreen = falsedefault_base = "main" # optional, see "base resolution chain" belowB) tool = "<preset>" (sugar)
Section titled “B) tool = "<preset>" (sugar)”[review]tool = "lumen" # shorthand for the table below| Preset | Expanded command |
fullscreen |
|---|---|---|
lumen |
lumen diff {base}..{head} |
true |
claude |
claude --print 'review the diff {base}..{head}' |
false |
codex |
codex review {base}..{head} |
false |
aider |
aider --message 'review {base}..{head}' |
true |
gh |
gh pr view --web |
false |
If both command and tool are set in the same block, command wins and the TUI shows a one-shot warning at startup (“your tool = X is shadowed by command”) so you notice the dead config.
Placeholders
Section titled “Placeholders”| Placeholder | Available in | Expanded to |
|---|---|---|
{path} |
both | absolute path of the selected worktree |
{base} |
[review] only |
result of the base resolution chain |
{head} |
[review] only |
the current branch name (branch.<name> in git config) |
{diff} |
[review] only |
absolute path of a tempfile holding git diff {base}..{head} (lazy, see above) |
Referencing {diff} in a [git_tui] template is a config error caught at load time (the git TUI launcher doesn’t carry the repo handle needed to materialise the diff).
Base resolution
Section titled “Base resolution”The {base} placeholder follows this chain, and the first non-empty hit wins:
branch.<name>.merge: the branch’s upstream tracking ref, if any.branch.<name>.gwm-base: set automatically bygwm createso the original parent stays recoverable even when the upstream is dropped.[review].default_base: the user-configured fallback in.gwm.toml.dev: gwm’s project convention (only ifdevexists locally).main: universal git default (final sentinel, guaranteed non-empty).
The chain is implemented in src/launcher.rs::resolve_review_base. The fallback prefers dev only when it exists locally: returning dev blindly when the repo only has main would make the launcher’s subsequent git rev-list / git diff calls fail loudly (caught by Copilot’s review on PR #76).
Interaction with gwm doctor
Section titled “Interaction with gwm doctor”gwm doctor (post-v0.6) probes the resolved [git_tui] binary (plus the [review] binary when a review launcher is configured) against $PATH as part of its external binaries on PATH check:
- Any launcher binary missing → Warning (exit code
1), naming the binaries that aren’t on$PATH. The review launcher is only probed when you’ve actually configured one (commandortool), so an unconfigured[review]is never flagged.
See Integrations → gwm doctor for the full check breakdown.
Related
Section titled “Related”- Keybindings: where
l/L/r/Rlive in the key map, plus the rebind summary - Configuration →
.gwm.tomlschema: full field listing with types and defaults