Liking cljdoc? Tell your friends :D

com.fulcrologic.fulcro.tui.driver

The side-effecting driver for the TUI rendering target: it renders a Fulcro app to a terminal and runs the input loop.

This is the edge that ties together the pure TUI pipeline in com.fulcrologic.fulcro.tui (layout, paint, diff, focus, input) with a concrete com.fulcrologic.fulcro.tui.terminal/Terminal. Use application to build a synchronous raw Fulcro app whose renders repaint the terminal, mount! (or run!) to attach a terminal and start the keyboard input loop, and step! to drive a single deterministic iteration (used by tests).

State/runtime keys (single source of truth):

  • Focus & carets & scroll are owned by com.fulcrologic.fulcro.tui (see that ns).
  • The attached terminal and the bookkeeping for incremental painting live in the app RUNTIME-ATOM under this namespace's keys (see ::terminal, ::prev-buffer, ::placed, ::last-size).

This is JVM/babashka only (plain .clj).

Viewport scrolling: tui/place lays a viewport's single child out at its natural size into a virtual rect, and tui/render-buffer blits only the window [scroll-x scroll-y w h] of that virtual content into the viewport's ::rect. Scroll offsets are stored in the state-map under :com.fulcrologic.fulcro.tui/scroll keyed by viewport id; render! injects them onto the placed tree (inject-scroll), follow-focus! advances them after focus changes to keep the focused node visible, and PageUp/PageDown page-scroll via viewport-scroll-key!. Up/Down arrows move focus item-to-item (in tui/process-key!) and follow-focus! autoscrolls to track the focused item.

The side-effecting *driver* for the TUI rendering target: it renders a Fulcro app to a terminal
and runs the input loop.

This is the edge that ties together the pure TUI pipeline in `com.fulcrologic.fulcro.tui` (layout,
paint, diff, focus, input) with a concrete `com.fulcrologic.fulcro.tui.terminal/Terminal`. Use
`application` to build a synchronous raw Fulcro app whose renders repaint the terminal, `mount!`
(or `run!`) to attach a terminal and start the keyboard input loop, and `step!` to drive a single
deterministic iteration (used by tests).

State/runtime keys (single source of truth):
  * Focus & carets & scroll are owned by `com.fulcrologic.fulcro.tui` (see that ns).
  * The attached terminal and the bookkeeping for incremental painting live in the app
    RUNTIME-ATOM under this namespace's keys (see `::terminal`, `::prev-buffer`, `::placed`,
    `::last-size`).

This is JVM/babashka only (plain `.clj`).

Viewport scrolling: `tui/place` lays a viewport's single child out at its natural size into a
virtual rect, and `tui/render-buffer` blits only the window `[scroll-x scroll-y w h]` of that
virtual content into the viewport's `::rect`. Scroll offsets are stored in the state-map under
`:com.fulcrologic.fulcro.tui/scroll` keyed by viewport id; `render!` injects them onto the placed
tree (`inject-scroll`), `follow-focus!` advances them after focus changes to keep the focused node
visible, and PageUp/PageDown page-scroll via `viewport-scroll-key!`. Up/Down arrows move focus
item-to-item (in `tui/process-key!`) and `follow-focus!` autoscrolls to track the focused item.
raw docstring

applicationclj

(application {:keys [root-class initial-state remotes]})

Returns a synchronous raw Fulcro app suitable for TUI rendering. Builds a rapp/fulcro-app with synchronous transactions (stx/with-synchronous-transactions), the given :root-class, optional :remotes, and render hooks wired so that every state-change repaints through render! (when a terminal has been attached). When :initial-state is provided the app's state is initialized from the root class. opts:

  • :root-class - the (required) TUI root component class (built with tui/defsc).
  • :initial-state - when truthy, initialize app state from root-class.
  • :remotes - optional Fulcro remotes map.

No terminal is attached here; attach one with attach!/mount!.

Returns a synchronous raw Fulcro app suitable for TUI rendering. Builds a `rapp/fulcro-app` with
synchronous transactions (`stx/with-synchronous-transactions`), the given `:root-class`, optional
`:remotes`, and render hooks wired so that every state-change repaints through `render!` (when a
terminal has been attached). When `:initial-state` is provided the app's state is initialized from
the root class. `opts`:

  * `:root-class`    - the (required) TUI root component class (built with `tui/defsc`).
  * `:initial-state` - when truthy, initialize app state from `root-class`.
  * `:remotes`       - optional Fulcro remotes map.

No terminal is attached here; attach one with `attach!`/`mount!`.
sourceraw docstring

attach!clj

(attach! app terminal)

Attaches terminal to app and performs the initial paint. Stashes the terminal in the runtime atom, enters the terminal (term/t-enter!), registers a resize handler that repaints on a terminal size change (t-on-resize!render!), sets initial focus to the first node in the current tree's focus-order when ::tui/focus is unset, and renders once. Returns app. Starts no loop.

Attaches `terminal` to `app` and performs the initial paint. Stashes the terminal in the runtime
atom, enters the terminal (`term/t-enter!`), registers a resize handler that repaints on a terminal
size change (`t-on-resize!` → `render!`), sets initial focus to the first node in the current tree's
`focus-order` when `::tui/focus` is unset, and renders once. Returns `app`. Starts no loop.
sourceraw docstring

caret-screen-positionclj

(caret-screen-position focused-node rect caret)

Returns [x y visible?] for the hardware cursor given the placed focused-node (or nil), its effective on-screen rect (or nil when the node has none / is scrolled out of view), and the caret index caret.

For a single-line :input, the cursor is placed at the rect origin advanced by the display width of the value up to caret, clamped to lie within the rect, and visible.

For a multiline :input (tui/multiline-input?), the value is wrapped to the rect's width, the caret's visual [row col] is computed (tui/caret->rowcol), and the cursor is placed at rect-origin + (row - top-line, col) where top-line is the input's injected internal scroll (::tui/text-scroll). If that visual row is scrolled out of the rect's [0, h) window the cursor is hidden.

For any other focused node the cursor is placed (visible) at the rect origin. When rect is nil the cursor is hidden at the origin.

Returns `[x y visible?]` for the hardware cursor given the placed `focused-node` (or `nil`), its
effective on-screen `rect` (or `nil` when the node has none / is scrolled out of view), and the
caret index `caret`.

For a single-line `:input`, the cursor is placed at the rect origin advanced by the display width of
the value up to `caret`, clamped to lie within the rect, and visible.

For a multiline `:input` (`tui/multiline-input?`), the value is wrapped to the rect's width, the
caret's visual `[row col]` is computed (`tui/caret->rowcol`), and the cursor is placed at
`rect-origin + (row - top-line, col)` where `top-line` is the input's injected internal scroll
(`::tui/text-scroll`). If that visual row is scrolled out of the rect's `[0, h)` window the cursor
is hidden.

For any other focused node the cursor is placed (visible) at the rect origin. When `rect` is `nil`
the cursor is hidden at the origin.
sourceraw docstring

focused-screen-rectclj

(focused-screen-rect app placed focus-id)

Returns the on-screen ::tui/rect for the focused node focus-id within placed, accounting for an enclosing scrolled viewport, or nil when the focused node is scrolled out of its viewport's window. For a node NOT inside a viewport, returns the node's own placed ::tui/rect (its absolute rect). For a node inside a viewport, its on-screen rect is viewport-content-origin + (virtual-origin - scroll); if that lands outside the viewport's content window, nil is returned (cursor hidden).

Returns the on-screen `::tui/rect` for the focused node `focus-id` within `placed`, accounting for an
enclosing scrolled viewport, or `nil` when the focused node is scrolled out of its viewport's window.
For a node NOT inside a viewport, returns the node's own placed `::tui/rect` (its absolute rect).
For a node inside a viewport, its on-screen rect is `viewport-content-origin + (virtual-origin -
scroll)`; if that lands outside the viewport's content window, `nil` is returned (cursor hidden).
sourceraw docstring

follow-focus!clj

(follow-focus! app placed)

Adjusts viewport scroll state so the currently focused node stays visible, then returns app. Using the placed tree, finds the focused node's enclosing viewport (tui/focus-viewport-context) and the focused node's VIRTUAL rect. Computes the minimal scroll that brings that rect into the viewport's content window (tui/scroll-to-show), clamps it (tui/clamp-scroll), and writes it to the viewport's scroll state (tui/set-viewport-scroll!) when it differs. A no-op when the focused node is not inside a viewport or the enclosing viewport has no :id.

Adjusts viewport scroll state so the currently focused node stays visible, then returns `app`.
Using the `placed` tree, finds the focused node's enclosing viewport (`tui/focus-viewport-context`)
and the focused node's VIRTUAL rect. Computes the minimal scroll that brings that rect into the
viewport's content window (`tui/scroll-to-show`), clamps it (`tui/clamp-scroll`), and writes it to
the viewport's scroll state (`tui/set-viewport-scroll!`) when it differs. A no-op when the focused
node is not inside a viewport or the enclosing viewport has no `:id`.
sourceraw docstring

initial-node-treeclj

(initial-node-tree app)

Returns the current pure TUI node tree for app (root class + db->tree), for computing the initial focus. Returns nil if the app has no state/root yet.

Returns the current pure TUI node tree for `app` (root class + `db->tree`), for computing the
initial focus. Returns `nil` if the app has no state/root yet.
sourceraw docstring

inject-scrollclj

(inject-scroll app tree)

Returns the placed tree with scroll state injected for the next paint, and records each multiline input's effective wrap width on app's runtime (tui/set-input-width!).

For every :viewport node it sets ::tui/scroll from app's scroll state (the state-map key ::tui/scroll, keyed by viewport id), clamped via tui/clamp-scroll against the viewport's ::tui/virtual-size and its content-area view size. Viewports without an :id keep their default {:x 0 :y 0}.

For every multiline :input node (tui/multiline-input?) it records the input's content width (its placed content-area width) so key handling wraps at the painted width, then computes the internal top visual-line offset (tui/text-scroll-top) from the input's caret so the caret row stays visible, and assocs it under ::tui/text-scroll.

Walks the placed tree (including nested viewport content).

Returns the placed `tree` with scroll state injected for the next paint, and records each multiline
input's effective wrap width on `app`'s runtime (`tui/set-input-width!`).

For every `:viewport` node it sets `::tui/scroll` from `app`'s scroll state (the state-map key
`::tui/scroll`, keyed by viewport id), clamped via `tui/clamp-scroll` against the viewport's
`::tui/virtual-size` and its content-area view size. Viewports without an `:id` keep their default
`{:x 0 :y 0}`.

For every multiline `:input` node (`tui/multiline-input?`) it records the input's content width
(its placed content-area width) so key handling wraps at the painted width, then computes the
internal top visual-line offset (`tui/text-scroll-top`) from the input's caret so the caret row
stays visible, and assocs it under `::tui/text-scroll`.

Walks the placed tree (including nested viewport content).
sourceraw docstring

mount!clj

(mount! app)
(mount! app {:keys [terminal global-keymap]})

Attaches a terminal to app and starts the keyboard input loop on a new thread. opts:

  • :terminal - the Terminal to drive (default (term/jline-terminal); tests pass a string-terminal).

Returns a handle map {:app :terminal :thread :running?} where :running? is an atom that, when set false, stops the loop. The loop reads keys with term/t-read-key; a nil (EOF) read or :running? becoming false terminates it; term/t-leave! is always called on exit.

Attaches a terminal to `app` and starts the keyboard input loop on a new thread. `opts`:

  * `:terminal` - the `Terminal` to drive (default `(term/jline-terminal)`; tests pass a
    `string-terminal`).

Returns a handle map `{:app :terminal :thread :running?}` where `:running?` is an atom that, when
set false, stops the loop. The loop reads keys with `term/t-read-key`; a `nil` (EOF) read or
`:running?` becoming false terminates it; `term/t-leave!` is always called on exit.
sourceraw docstring

placed-tree-ofclj

(placed-tree-of app)

Returns the placed tree most recently painted for app (from the runtime atom), or nil.

Returns the placed tree most recently painted for `app` (from the runtime atom), or `nil`.
sourceraw docstring

position-cursor!clj

(position-cursor! app terminal placed)

Positions the hardware cursor of terminal for app against the placed tree: finds the focused node (tui/current-focus), computes its on-screen rect (via focused-screen-rect, which accounts for a scrolled enclosing viewport and hides the cursor when the focused node is scrolled out of view), computes its caret position, and calls term/t-set-cursor!. Returns app.

Positions the hardware cursor of `terminal` for `app` against the `placed` tree: finds the focused
node (`tui/current-focus`), computes its on-screen rect (via `focused-screen-rect`, which accounts
for a scrolled enclosing viewport and hides the cursor when the focused node is scrolled out of
view), computes its caret position, and calls `term/t-set-cursor!`. Returns `app`.
sourceraw docstring

quit!clj

(quit! handle-or-app)

Stops the input loop for a mount!/run! handle (or, given an app, looks up its terminal): sets :running? false, best-effort interrupts the loop thread, and leaves the terminal. Returns handle-or-app.

Stops the input loop for a `mount!`/`run!` `handle` (or, given an `app`, looks up its terminal):
sets `:running?` false, best-effort interrupts the loop thread, and leaves the terminal. Returns
`handle-or-app`.
sourceraw docstring

render!clj

(render! app)

Paints app to its attached terminal. This is the driver's core render and is wired as the app's render hook so any state change repaints.

It (1) computes the pure node tree from app state (root class + db->tree), (2) reads the terminal size, (3) lays the tree out into a placed tree and paints it into a fresh cell buffer (or, when the terminal is below the root's declared :min-width/:min-height, a 'too small' buffer), (4) diffs against the previously-painted buffer and writes the resulting ANSI to the terminal (wrapping in a synchronized-output frame when the terminal supports it), (5) positions the hardware cursor at the focused input's caret (or hides/origins it otherwise), and (6) stashes the new buffer, placed tree, and terminal size in the runtime atom for the next frame. A change in terminal size invalidates the previous buffer so the next frame is a full repaint. A no-op when no terminal is attached. Returns app.

Paints `app` to its attached terminal. This is the driver's core render and is wired as the app's
render hook so any state change repaints.

It (1) computes the pure node tree from app state (root class + `db->tree`), (2) reads the terminal
size, (3) lays the tree out into a placed tree and paints it into a fresh cell buffer (or, when the
terminal is below the root's declared `:min-width`/`:min-height`, a 'too small' buffer), (4) diffs
against the previously-painted buffer and writes the resulting ANSI to the terminal (wrapping in a
synchronized-output frame when the terminal supports it), (5) positions the hardware cursor at the
focused input's caret (or hides/origins it otherwise), and (6) stashes the new buffer, placed tree,
and terminal size in the runtime atom for the next frame. A change in terminal size invalidates the
previous buffer so the next frame is a full repaint. A no-op when no terminal is attached. Returns
`app`.
sourceraw docstring

root-minclj

(root-min node-tree)

Returns {:min-width W :min-height H} for the root node-tree, reading the :min-width/ :min-height attrs of the root node and defaulting each to 1.

Returns `{:min-width W :min-height H}` for the root `node-tree`, reading the `:min-width`/
`:min-height` attrs of the root node and defaulting each to 1.
sourceraw docstring

run-blocking!clj

(run-blocking! app)
(run-blocking! app opts)

Mounts app (see mount!) and blocks until the input loop's thread finishes (the terminal reaches EOF or the loop is stopped). opts are passed to mount!. Returns the handle. (Named run-blocking! rather than run! to avoid shadowing clojure.core/run!.)

Mounts `app` (see `mount!`) and blocks until the input loop's thread finishes (the terminal
reaches EOF or the loop is stopped). `opts` are passed to `mount!`. Returns the handle. (Named
`run-blocking!` rather than `run!` to avoid shadowing `clojure.core/run!`.)
sourceraw docstring

runtimeclj

(runtime app)

Returns the (dereferenced) runtime map for app, or nil.

Returns the (dereferenced) runtime map for `app`, or `nil`.
sourceraw docstring

screen-ofclj

(screen-of app)

Returns the tui/screen (vector of row strings) of the buffer most recently painted for app, or nil if nothing has been painted yet.

Returns the `tui/screen` (vector of row strings) of the buffer most recently painted for `app`, or
`nil` if nothing has been painted yet.
sourceraw docstring

screen-styled-ofclj

(screen-styled-of app)

Returns the tui/screen-styled (vector of rows of styled cell maps) of the buffer most recently painted for app, or nil if nothing has been painted yet.

Returns the `tui/screen-styled` (vector of rows of styled cell maps) of the buffer most recently
painted for `app`, or `nil` if nothing has been painted yet.
sourceraw docstring

step!clj

(step! app key-event)
(step! app key-event global-keymap)

Runs one deterministic driver iteration for app: dispatches key-event and then repaints (render!). Returns app. Used by tests and the input loop.

Dispatch order:

  1. Viewport scroll keys (viewport-scroll-key!): if the focused node lies inside a viewport and key-event is PageUp/PageDown (or :up/:down on a non-input focus), the enclosing viewport is scrolled and the focus/input pipeline is skipped.
  2. Otherwise tui/process-key! handles focus/typing/activation/global-keymap.
  3. follow-focus! then adjusts viewport scroll so the (possibly newly) focused node stays visible.

The placed tree from the previous frame is used to locate the enclosing viewport for steps 1 & 3.

Runs one deterministic driver iteration for `app`: dispatches `key-event` and then repaints
(`render!`). Returns `app`. Used by tests and the input loop.

Dispatch order:
  1. Viewport scroll keys (`viewport-scroll-key!`): if the focused node lies inside a viewport and
     `key-event` is PageUp/PageDown (or `:up`/`:down` on a non-input focus), the enclosing viewport
     is scrolled and the focus/input pipeline is skipped.
  2. Otherwise `tui/process-key!` handles focus/typing/activation/global-keymap.
  3. `follow-focus!` then adjusts viewport scroll so the (possibly newly) focused node stays visible.

The placed tree from the previous frame is used to locate the enclosing viewport for steps 1 & 3.
sourceraw docstring

terminalclj

(terminal app)

Returns the terminal currently attached to app (from its runtime atom), or nil.

Returns the terminal currently attached to `app` (from its runtime atom), or `nil`.
sourceraw docstring

too-small-bufferclj

(too-small-buffer rows cols min-w min-h)

Returns a rowsxcols cell buffer painted with a centered "terminal too small" message asking for at least min-w x min-h. Used when the terminal is smaller than the root's declared minimum.

Returns a `rows`x`cols` cell buffer painted with a centered "terminal too small" message asking
for at least `min-w` x `min-h`. Used when the terminal is smaller than the root's declared minimum.
sourceraw docstring

viewport-scroll-key!clj

(viewport-scroll-key! app placed key-event)

If key-event is :page-up/:page-down and the currently focused node lies inside a viewport, scrolls that viewport's scroll state by a page and returns truthy (:handled). Returns nil (unhandled) otherwise, so the caller falls through to the normal key pipeline. placed is the current placed tree (for locating the enclosing viewport).

:up/:down are NOT handled here: they drive focus navigation in tui/process-key! (moving focus item-to-item through the focus ring), and follow-focus! keeps the focused item visible — so arrowing through a focusable list autoscrolls its viewport. PageUp/PageDown remain the explicit page-scroll for a focused viewport.

If `key-event` is `:page-up`/`:page-down` and the currently focused node lies inside a viewport,
scrolls that viewport's scroll state by a page and returns truthy (`:handled`). Returns `nil`
(unhandled) otherwise, so the caller falls through to the normal key pipeline. `placed` is the
current placed tree (for locating the enclosing viewport).

`:up`/`:down` are NOT handled here: they drive focus navigation in `tui/process-key!` (moving
focus item-to-item through the focus ring), and `follow-focus!` keeps the focused item visible —
so arrowing through a focusable list autoscrolls its viewport. PageUp/PageDown remain the explicit
page-scroll for a focused viewport.
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