Liking cljdoc? Tell your friends :D

com.blockether.vis.ext.channel-tui.transient

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 :restore!}. :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. A band wipes ITS OWN rows and nothing above them; a host that pages bands of DIFFERENT heights passes :restore! ((fn [from to]), from dialogs/frame-restorer), and the rows a taller band covered are given back to the HOST instead of blanked.

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 :restore!}`.
           `: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. A band wipes ITS OWN rows and nothing above them; a host
           that pages bands of DIFFERENT heights passes `:restore!`
           (`(fn [from to])`, from `dialogs/frame-restorer`), and the rows a
           taller band covered are given back to the HOST instead of blanked.

  `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.
raw docstring

band-geometryclj

(band-geometry {:keys [hint-row min-row]} n)

PURE: which row of region every part of a band n display rows tall lands on.

: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 display row :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 band — or a short terminal — stops at the host's content top instead of climbing over it.

The row count is the parameter, not the spec, because a band is not always a transient: the human-input form paints its own plan rows into exactly this geometry, and both must land on the same chrome.

PURE: which row of `region` every part of a band `n` display rows tall
lands on.

  `: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 display row
  `: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 band — or a short
terminal — stops at the host's content top instead of climbing over it.

The row count is the parameter, not the spec, because a band is not always a
transient: the human-input form paints its own plan rows into exactly this
geometry, and both must land on the same chrome.
sourceraw docstring

band-regionclj

(band-region cols rows content-top)

PURE: the rectangle an in-session BAND paints into on a cols×rows terminal whose content starts at content-top.

The session frame is SIDELESS — the prompt is two horizontal rules with no rails — so a band that takes it over borrows exactly that: rules inset [[band-pad]] columns, text one column further in, and :is-sideless true so the chrome paints rules instead of ├───┤ junctions and wipes the FULL terminal width (a band sits on the live transcript, not on a modal's own paper). :hint-row is the prompt box's own closing rule (always rows - 3, whatever height the editor grew to), which keeps the echo area's two footer rows below the band alive; :min-row is the floor, so however tall the band the header and the top of the transcript stay on screen.

PURE: the rectangle an in-session BAND paints into on a `cols`×`rows`
terminal whose content starts at `content-top`.

The session frame is SIDELESS — the prompt is two horizontal rules with no
`│` rails — so a band that takes it over borrows exactly that: rules inset
[[band-pad]] columns, text one column further in, and `:is-sideless true`
so the chrome paints rules instead of `├───┤` junctions and wipes the FULL
terminal width (a band sits on the live transcript, not on a modal's own
paper). `:hint-row` is the prompt box's own closing rule (always `rows - 3`,
whatever height the editor grew to), which keeps the echo area's two footer
rows below the band alive; `:min-row` is the floor, so however tall the band
the header and the top of the transcript stay on screen.
sourceraw docstring

checkclj

(check spec)

nil when spec is a legal transient, else ONE line saying why. The same judge run! uses, answering instead of throwing: a producer's own test — or a caller assembling a spec from live data — asks BEFORE a terminal is involved, and gets prose a human can read.

nil when `spec` is a legal transient, else ONE line saying why. The same
judge [[run!]] uses, answering instead of throwing: a producer's own test —
or a caller assembling a spec from live data — asks BEFORE a terminal is
involved, and gets prose a human can read.
sourceraw docstring

clear-rows!clj

(clear-rows! g {:keys [left inner-w is-sideless cols]} from to)

Blank rows from..to (INCLUSIVE) inside the host's frame: dialog paper across the inner columns and the frame's plain edge back in both border columns.

Wiping only the band's INNER columns leaves whatever the host painted in the two border columns: a status buffer's own section separator survived as stray / junctions beside the popup. Capping separators put their own junctions back afterwards.

A SIDELESS region (band-region) has no border columns and no paper of its own: it wipes the FULL terminal width, because anything it does not repaint is the live transcript showing through between its rules.

A transient erases exactly the rows it paints. A host that repaints BANDS OF DIFFERENT HEIGHTS into one rectangle — the model picker paging a catalog — owns the rows between its content and the band, and erases them with this before running the next page.

Blank rows `from`..`to` (INCLUSIVE) inside the host's frame: dialog paper
across the inner columns and the frame's plain edge back in both border
columns.

Wiping only the band's INNER columns leaves whatever the host painted in the
two border columns: a status buffer's own section separator survived as stray
`├`/`┤` junctions beside the popup. Capping separators put their own junctions
back afterwards.

A SIDELESS region (`band-region`) has no border columns and no paper of its
own: it wipes the FULL terminal width, because anything it does not repaint
is the live transcript showing through between its rules.

A transient erases exactly the rows it paints. A host that repaints BANDS OF
DIFFERENT HEIGHTS into one rectangle — the model picker paging a catalog —
owns the rows between its content and the band, and erases them with this
before running the next page.
sourceraw docstring

columnsclj

(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.
sourceraw docstring

draw-rule!clj

(draw-rule! g {:keys [left inner-w is-sideless]} row)

One horizontal rule of the popup's chrome, on whatever frame region names.

A framed popup draws a capped ├───┤ separator that joins the host's rails; a SIDELESS band has no rails for a junction to join, so it draws the prompt's own inset line instead. One function, so a band and a modal wear the same chrome and neither grows a second copy of it.

One horizontal rule of the popup's chrome, on whatever frame `region` names.

A framed popup draws a capped `├───┤` separator that joins the host's rails;
a SIDELESS band has no rails for a junction to join, so it draws the prompt's
own inset line instead. One function, so a band and a modal wear the same
chrome and neither grows a second copy of it.
sourceraw docstring

geometryclj

(geometry region spec)

PURE: band-geometry for spec's OWN display rows — where every part of the popup lands.

PURE: [[band-geometry]] for `spec`'s OWN display rows — where every part of
the popup lands.
sourceraw docstring

heightclj

(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.
sourceraw docstring

hint-pairsclj

(hint-pairs spec)

The footer THIS transient shows: what a command key does, the way out, and what a flag key does ONLY when the spec actually has a flag. A band whose keys are all commands (the draft band spends c/d/s/k on verbs) must not advertise a -key nothing responds to.

The footer THIS transient shows: what a command key does, the way out, and
what a flag key does ONLY when the spec actually has a flag. A band whose
keys are all commands (the draft band spends `c`/`d`/`s`/`k` on verbs) must
not advertise a `-key` nothing responds to.
sourceraw docstring

item-argclj

(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 commands (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 commands (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.
sourceraw docstring

item-by-idclj

(item-by-id spec id)

PURE: the spec item carrying id, scanning every group in order. nil when the band does not offer that command right now — which is how a caller that fires a command WITHOUT a keystroke (a slash that names it) finds out the band never showed it.

PURE: the spec item carrying `id`, scanning every group in order. nil when
the band does not offer that command right now — which is how a caller that
fires a command WITHOUT a keystroke (a slash that names it) finds out the
band never showed it.
sourceraw docstring

item-by-keyclj

(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.
sourceraw docstring

key-glyphclj

(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 (sp/command-types) show the bare key. Tolerant of a partial item: an unknown type is not a command.

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 (`sp/command-types`) show the bare
key. Tolerant of a partial item: an unknown type is not a command.
sourceraw docstring

layoutclj

(layout spec)

PURE: everything ONE frame needs from spec, computed ONCE — the display rows and how many there are, the grid columns every row aligns to, the hint-pairs its footer shows, the height a host sizes its box with, and the keystroke index its key loop dispatches on.

Painting used to walk the whole spec twice per frame (once for geometry, once for the body) and scan every group again for each keystroke; a run builds this once and the loop reads it.

PURE: everything ONE frame needs from `spec`, computed ONCE — the display
[[rows]] and how many there are, the grid [[columns]] every row aligns to,
the [[hint-pairs]] its footer shows, the [[height]] a host sizes its box
with, and the keystroke index its key loop dispatches on.

Painting used to walk the whole spec twice per frame (once for geometry,
once for the body) and scan every group again for each keystroke; a run
builds this once and the loop reads it.
sourceraw docstring

paint!clj

(paint! host 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. A host that paints many frames computes layout once instead.

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.
A host that paints many frames computes [[layout]] once instead.
sourceraw docstring

rowsclj

(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.
sourceraw docstring

run!clj

(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.

The contract is checked ONCE per run, here: an illegal spec, an unpaintable region or a host that cannot answer keystrokes throws :vis/transient-invalid-spec / -invalid-region / -invalid-host before a single cell is painted, and a :read-option that hands back something no row can carry throws :vis/transient-invalid-option instead of painting it. Not once per keystroke: the loop below runs on data already known legal.

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.

The contract is checked ONCE per run, here: an illegal spec, an unpaintable
region or a host that cannot answer keystrokes throws
`:vis/transient-invalid-spec` / `-invalid-region` / `-invalid-host` before a
single cell is painted, and a `:read-option` that hands back something no row
can carry throws `:vis/transient-invalid-option` instead of painting it. Not
once per keystroke: the loop below runs on data already known legal.
sourceraw docstring

toggleclj

(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).
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close