Magit-style TRANSIENT as a REUSABLE, EMBEDDABLE component.
A transient is a VALUE — a spec — and this namespace knows how to lay it
out, paint it into a rectangle, and (optionally) run its key loop. It owns NO
screen, NO dialog chrome and NO Lanterna types, so ANY surface that can hand
over a TextGraphics and answer keystrokes can embed one: the magit status
buffer, the provider dialog, a modal of its own (dialogs/transient-dialog!),
or a panel that does not exist yet.
THREE values, three concerns:
spec WHAT the transient is — a pure, reusable description:
{:title "Commit" :groups [{:title "Arguments" :items [item …]} …] :read-option (fn [item current] str|nil)}
where an item is
{:key "h" :type :switch|:option|:action :id :no-verify :label "Disable hooks" :arg "--no-verify" :secret? false}.
FLAGS (:switch / :option) render with magit's leading - and
TOGGLE — press once to arm, again to disarm — while COMMANDS
(:action) fire once and close. Keys are CASE-SENSITIVE, exactly
like magit (-f is not -F). :read-option (impure, optional)
fetches an OPTION's value; nil (Esc) leaves it unchanged.
region WHERE it sits — the host's rectangle:
{:left :inner-w :hint-row :text-w :min-row :clear-above?}.
:left/:inner-w are the host frame's border column and inner
width, :hint-row the row the host's hint bar owns (the popup
REPLACES it), :text-w the text budget inside the padding.
:min-row is the first row the popup may touch — a tall transient
stops there instead of climbing over whatever the host keeps
visible — and :clear-above? also wipes from :min-row down, for
a host that repaints PAGES of different heights in one frame.
host HOW to talk to the terminal — the ONLY impure dependency:
{:g TextGraphics to paint into :hint-bar! (fn [g left row inner-w pairs]) :refresh! (fn []) flush the frame :read-key! (fn []):esc| Character | nil}
nil from :read-key! means "nothing actionable" — the loop simply
repaints. dialogs/transient-host builds the standard modal one.
The popup is bottom-anchored INSIDE the host's frame and reads
─── / bold title / ─── / groups / ─── / hint bar: the same chrome the
host gives any other titled section, never a second window pasted on top.
Magit-style TRANSIENT as a REUSABLE, EMBEDDABLE component.
A transient is a VALUE — a `spec` — and this namespace knows how to lay it
out, paint it into a rectangle, and (optionally) run its key loop. It owns NO
screen, NO dialog chrome and NO Lanterna types, so ANY surface that can hand
over a `TextGraphics` and answer keystrokes can embed one: the magit status
buffer, the provider dialog, a modal of its own (`dialogs/transient-dialog!`),
or a panel that does not exist yet.
THREE values, three concerns:
`spec` WHAT the transient is — a pure, reusable description:
`{:title "Commit"
:groups [{:title "Arguments" :items [item …]} …]
:read-option (fn [item current] str|nil)}`
where an `item` is
`{:key "h" :type :switch|:option|:action :id :no-verify
:label "Disable hooks" :arg "--no-verify" :secret? false}`.
FLAGS (`:switch` / `:option`) render with magit's leading `-` and
TOGGLE — press once to arm, again to disarm — while COMMANDS
(`:action`) fire once and close. Keys are CASE-SENSITIVE, exactly
like magit (`-f` is not `-F`). `:read-option` (impure, optional)
fetches an OPTION's value; nil (Esc) leaves it unchanged.
`region` WHERE it sits — the host's rectangle:
`{:left :inner-w :hint-row :text-w :min-row :clear-above?}`.
`:left`/`:inner-w` are the host frame's border column and inner
width, `:hint-row` the row the host's hint bar owns (the popup
REPLACES it), `:text-w` the text budget inside the padding.
`:min-row` is the first row the popup may touch — a tall transient
stops there instead of climbing over whatever the host keeps
visible — and `:clear-above?` also wipes from `:min-row` down, for
a host that repaints PAGES of different heights in one frame.
`host` HOW to talk to the terminal — the ONLY impure dependency:
`{:g TextGraphics to paint into
:hint-bar! (fn [g left row inner-w pairs])
:refresh! (fn []) flush the frame
:read-key! (fn []) `:esc` | Character | nil}`
nil from `:read-key!` means "nothing actionable" — the loop simply
repaints. `dialogs/transient-host` builds the standard modal one.
The popup is bottom-anchored INSIDE the host's frame and reads
`───` / bold title / `───` / groups / `───` / hint bar: the same chrome the
host gives any other titled section, never a second window pasted on top.(columns spec)PURE: {:key-w :label-w} column widths for the transient grid. Every group's
items share one key column and one description column, so flags and commands
line up as a grid exactly like magit's popup.
PURE: `{:key-w :label-w}` column widths for the transient grid. Every group's
items share one key column and one description column, so flags and commands
line up as a grid exactly like magit's popup.(geometry {:keys [hint-row min-row clear-above?]} spec)PURE: which row of region every part of the popup lands on, for spec.
:sep-row the band's opening separator
:title-row the bold title, alone on its row
:title-rule-row the title's OWN rule, closing the title band
:body-top first group header
:visible display rows that actually fit (overflow is dropped)
:foot-rule-row the rule directly above the hint bar
:foot-row the hint bar (the host's own hint row)
:wipe-top first row the popup wipes before painting
Everything is clamped to :min-row, so a tall transient — or a short
terminal — stops at the host's content top instead of climbing over it.
PURE: which row of `region` every part of the popup lands on, for `spec`. `:sep-row` the band's opening separator `:title-row` the bold title, alone on its row `:title-rule-row` the title's OWN rule, closing the title band `:body-top` first group header `:visible` display rows that actually fit (overflow is dropped) `:foot-rule-row` the rule directly above the hint bar `:foot-row` the hint bar (the host's own hint row) `:wipe-top` first row the popup wipes before painting Everything is clamped to `:min-row`, so a tall transient — or a short terminal — stops at the host's content top instead of climbing over it.
(height spec)PURE: rows the popup needs INSIDE the host's frame — its opening separator, the title, the title's rule, and every display row. The closing rule and the hint bar are the host's OWN bottom chrome, so they are not counted; a host sizes its box with this before it paints one.
PURE: rows the popup needs INSIDE the host's frame — its opening separator, the title, the title's rule, and every display row. The closing rule and the hint bar are the host's OWN bottom chrome, so they are not counted; a host sizes its box with this before it paints one.
The footer every transient shows: what a flag key does, what a command key does, and the way out.
The footer every transient shows: what a flag key does, what a command key does, and the way out.
(item-arg {:keys [type arg secret?]} value)PURE: the trailing git-argument cell magit shows for a FLAG: (--no-verify) for
a switch, (%topic=fix) for an option carrying value. nil for actions (a
command contributes no argument) and for flags that name none.
A :secret? true option NEVER renders what it holds: an armed API key shows
as (••••••), so a credential can be carried by a transient without being
echoed onto the screen or into a screenshot.
PURE: the trailing git-argument cell magit shows for a FLAG: `(--no-verify)` for a switch, `(%topic=fix)` for an option carrying `value`. nil for actions (a command contributes no argument) and for flags that name none. A `:secret? true` option NEVER renders what it holds: an armed API key shows as `(••••••)`, so a credential can be carried by a transient without being echoed onto the screen or into a screenshot.
(item-by-key spec ch)PURE: the spec item bound to single character ch (a Character or string),
scanning every group in order. nil when nothing is bound.
PURE: the spec item bound to single character `ch` (a Character or string), scanning every group in order. nil when nothing is bound.
(key-glyph {:keys [type key]})PURE: the key column glyph magit paints for one item. FLAGS (:switch /
:option) carry magit's leading - — -h, -t — so a toggle can never be
mistaken for a fire-once verb; COMMANDS (:action) show the bare key.
PURE: the key column glyph magit paints for one item. FLAGS (`:switch` / `:option`) carry magit's leading `-` — `-h`, `-t` — so a toggle can never be mistaken for a fire-once verb; COMMANDS (`:action`) show the bare key.
(paint! {:keys [g hint-bar!]}
{:keys [left inner-w text-w] :as region}
spec
state)Paint ONE frame of spec at state into region on host. Pure geometry,
one pass, no key handling — a host that owns its own event loop embeds a
transient with this plus toggle; run! is the batteries-included loop.
Paint ONE frame of `spec` at `state` into `region` on `host`. Pure geometry, one pass, no key handling — a host that owns its own event loop embeds a transient with this plus `toggle`; `run!` is the batteries-included loop.
(rows spec)PURE: the popup's display rows, top to bottom — {:kind :header :text},
{:kind :item :item} and {:kind :blank} spacers between groups.
The title carries its OWN rule underneath (see geometry), so the first
group header needs no blank margin of its own; groups after it still get one
from the trailing blank of the group before.
PURE: the popup's display rows, top to bottom — `{:kind :header :text}`,
`{:kind :item :item}` and `{:kind :blank}` spacers between groups.
The title carries its OWN rule underneath (see `geometry`), so the first
group header needs no blank margin of its own; groups after it still get one
from the trailing blank of the group before.(run! {:keys [read-key! refresh!] :as host} region spec)Paint spec into region on host and run its key loop until an ACTION
fires or the user cancels. Returns {:action id :switches #{…} :options {…}},
or nil on Esc. Flags toggle in place and keep the popup open; an OPTION calls
the spec's :read-option for a value.
Paint `spec` into `region` on `host` and run its key loop until an ACTION
fires or the user cancels. Returns `{:action id :switches #{…} :options {…}}`,
or nil on Esc. Flags toggle in place and keep the popup open; an OPTION calls
the spec's `:read-option` for a value.(toggle spec state ch)PURE reducer for ONE keystroke against a transient state
({:switches #{ids} :options {id val}}). Returns a map whose :kind tells
the impure caller what to do next:
{:kind :continue :state s'} a SWITCH flipped (or an unbound key — no-op)
{:kind :option :item it} an OPTION was hit; caller reads a value then
re-enters with it stored under [:options id]
{:kind :action :item it} an ACTION fires; caller runs it and closes.
Switches are the only kind this fn mutates; options/actions leave state
untouched (the impure loop finishes their job).
PURE reducer for ONE keystroke against a transient `state`
(`{:switches #{ids} :options {id val}}`). Returns a map whose `:kind` tells
the impure caller what to do next:
{:kind :continue :state s'} a SWITCH flipped (or an unbound key — no-op)
{:kind :option :item it} an OPTION was hit; caller reads a value then
re-enters with it stored under [:options id]
{:kind :action :item it} an ACTION fires; caller runs it and closes.
Switches are the only kind this fn mutates; options/actions leave `state`
untouched (the impure loop finishes their job).cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |