The application/lifecycle front door for the TUI rendering target: build a Fulcro app, attach a terminal, run the input loop, and stop it. This is also where the side-effecting render driver lives (it renders a Fulcro app to a terminal and runs the keyboard input loop).
This is the edge that ties together the pure TUI pipeline in com.fulcrologic.fulcro.tui.engine
(layout, paint, diff, focus, input) and the element generators in
com.fulcrologic.fulcro.tui.elements 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-blocking!) to attach a terminal and start the keyboard input loop, quit! to stop it,
and step! to drive a single deterministic iteration (used by tests).
State/runtime keys (single source of truth):
com.fulcrologic.fulcro.tui.engine (see that ns).::terminal, ::prev-buffer, ::placed,
::last-size).This is JVM/babashka only (plain .clj).
Viewport scrolling: engine/place lays a viewport's single child out at its natural size into a
virtual rect, and engine/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.engine/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 engine/process-key!) and follow-focus! autoscrolls to track the focused item.
The application/lifecycle front door for the TUI rendering target: build a Fulcro app, attach a
terminal, run the input loop, and stop it. This is also where the side-effecting render driver
lives (it renders a Fulcro app to a terminal and runs the keyboard input loop).
This is the edge that ties together the pure TUI pipeline in `com.fulcrologic.fulcro.tui.engine`
(layout, paint, diff, focus, input) and the element generators in
`com.fulcrologic.fulcro.tui.elements` 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-blocking!`) to attach a terminal and start the keyboard input loop, `quit!` to stop it,
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.engine` (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: `engine/place` lays a viewport's single child out at its natural size into a
virtual rect, and `engine/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.engine/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 `engine/process-key!`) and `follow-focus!` autoscrolls to track the focused item.The public TUI element API: the generators authors use to build a tree of terminal-native nodes
from a Fulcro component's render — vbox, hbox, box, text, input, button, line,
viewport, modal, picker — plus focused? (highlight the focused node). State changes use
Fulcro's own com.fulcrologic.fulcro.components/transact!; this namespace adds no transact.
A node is a plain map keyed by the com.fulcrologic.fulcro.tui.engine vocabulary (::engine/tag,
::engine/attrs, ::engine/children); prefer these generators over building the maps by hand.
The engine (com.fulcrologic.fulcro.tui.engine) lays the tree out and paints it; this namespace
depends on the engine for that node vocabulary and node?/*current-focus*.
This is JVM/babashka only (plain .clj).
The public TUI element API: the generators authors use to build a tree of terminal-native *nodes* from a Fulcro component's render — `vbox`, `hbox`, `box`, `text`, `input`, `button`, `line`, `viewport`, `modal`, `picker` — plus `focused?` (highlight the focused node). State changes use Fulcro's own `com.fulcrologic.fulcro.components/transact!`; this namespace adds no transact. A node is a plain map keyed by the `com.fulcrologic.fulcro.tui.engine` vocabulary (`::engine/tag`, `::engine/attrs`, `::engine/children`); prefer these generators over building the maps by hand. The engine (`com.fulcrologic.fulcro.tui.engine`) lays the tree out and paints it; this namespace depends on the engine for that node vocabulary and `node?`/`*current-focus*`. This is JVM/babashka only (plain `.clj`).
The pure TUI engine: the layout, paint, diff, focus, input, and overlay-compositing pipeline that
turns a tree of terminal-native nodes into a character cell buffer (and ANSI), plus the
component-render walker. This namespace also owns the node-structure vocabulary (the ::tag/
::attrs/::children keys and related specs) and the focus/scroll/caret state keys.
Build the node tree with the element generators in com.fulcrologic.fulcro.tui.elements; drive it
to a terminal with com.fulcrologic.fulcro.tui.application; paint to a concrete terminal via
com.fulcrologic.fulcro.tui.terminal.
A node is a plain map of the form:
{:com.fulcrologic.fulcro.tui.engine/tag :vbox ; one of `tags`
:com.fulcrologic.fulcro.tui.engine/attrs {...} ; layout/style/event attributes
:com.fulcrologic.fulcro.tui.engine/children [...]} ; child nodes, strings, and numbers
This is JVM/babashka only (plain .clj).
The pure TUI engine: the layout, paint, diff, focus, input, and overlay-compositing pipeline that
turns a tree of terminal-native *nodes* into a character cell buffer (and ANSI), plus the
component-render walker. This namespace also owns the node-structure vocabulary (the `::tag`/
`::attrs`/`::children` keys and related specs) and the focus/scroll/caret state keys.
Build the node tree with the element generators in `com.fulcrologic.fulcro.tui.elements`; drive it
to a terminal with `com.fulcrologic.fulcro.tui.application`; paint to a concrete terminal via
`com.fulcrologic.fulcro.tui.terminal`.
A node is a plain map of the form:
```
{:com.fulcrologic.fulcro.tui.engine/tag :vbox ; one of `tags`
:com.fulcrologic.fulcro.tui.engine/attrs {...} ; layout/style/event attributes
:com.fulcrologic.fulcro.tui.engine/children [...]} ; child nodes, strings, and numbers
```
This is JVM/babashka only (plain `.clj`).JVM (Clojure) Fulcro Inspect connector for TUI apps.
This is the small CLJ shim that fills the only gap preventing a plain JVM Fulcro app from
connecting to the standalone Fulcro Inspect (Electron) app: a DevToolConnectionFactory
that speaks Sente over a real websocket from the JVM.
The entire inspect event-generation pipeline (transactions, network, db-changed) and the
devtools-remote transport (connection, target, resolvers) are already CLJC and ship transitively
with Fulcro 3.10.x. The only CLJS-only piece is com.fulcrologic.devtools.electron.target
(it uses goog-define and enc/get-win-loc). This namespace is a CLJ analog of that factory.
Usage (debug only):
(require '[com.fulcrologic.fulcro.tui.inspect :as tui-inspect]) (tui-inspect/install!) ; register the JVM websocket factory ONCE (tui-inspect/add-inspect! app) ; attach inspect to your TUI app
Requires the Fulcro Inspect Electron app to be running (it hosts the websocket server on localhost:8237). Also requires the inspect pipeline to be enabled in CLJ via the JVM property:
-Dcom.fulcrologic.fulcro.inspect=true
(Without that property fulcro.inspect.tool/add-fulcro-inspect! is a no-op, because its body is
wrapped in the ilet macro that only emits in CLJ when that property is "true".)
JVM (Clojure) Fulcro Inspect connector for TUI apps. This is the small CLJ shim that fills the only gap preventing a plain JVM Fulcro app from connecting to the standalone Fulcro Inspect (Electron) app: a `DevToolConnectionFactory` that speaks Sente over a real websocket from the JVM. The entire inspect event-generation pipeline (transactions, network, db-changed) and the devtools-remote transport (connection, target, resolvers) are already CLJC and ship transitively with Fulcro 3.10.x. The only CLJS-only piece is `com.fulcrologic.devtools.electron.target` (it uses `goog-define` and `enc/get-win-loc`). This namespace is a CLJ analog of that factory. Usage (debug only): (require '[com.fulcrologic.fulcro.tui.inspect :as tui-inspect]) (tui-inspect/install!) ; register the JVM websocket factory ONCE (tui-inspect/add-inspect! app) ; attach inspect to your TUI app Requires the Fulcro Inspect Electron app to be running (it hosts the websocket server on localhost:8237). Also requires the inspect pipeline to be enabled in CLJ via the JVM property: -Dcom.fulcrologic.fulcro.inspect=true (Without that property `fulcro.inspect.tool/add-fulcro-inspect!` is a no-op, because its body is wrapped in the `ilet` macro that only emits in CLJ when that property is "true".)
A tiny, babashka-compatible profiler — the core of Tufte (p profile points, a profile
scope, and a println report) reimplemented with only plain atoms / volatiles / dynamic vars so
it runs under SCI. (Real Tufte does NOT load under babashka: its encore dependency uses a
defrecord implementing clojure.lang.Counted, which SCI rejects.)
Design goals:
p and profile are compile-time
gated on the fulcro.tui.perf system property: unless it is set when the calling code is
macro-expanded, both expand to just (do body...) — no runtime check, no volatile read, no
trace of profiling in the compiled code. So these points are safe to leave in shipped render
code, and the shipped library (compiled without the property) carries no profiling at all.src/main (the shipped render code calls p directly), but
pulls in nothing.p records both total (inclusive) and self (exclusive of
nested ps) time, so it is safe to instrument recursive code (e.g. the paint walker) and read
a meaningful per-id breakdown.Usage — first build the instrumentation IN by setting the property (JVM: -Dfulcro.tui.perf=1;
babashka: bb -Dfulcro.tui.perf=1 ...), then while running the TUI:
(perf/start!) ; enable + reset ;; ... exercise the code / interact with the app ... (perf/report) ; print the per-id table (self-time ranked) (perf/stop!) ; disable
Or scope it: (perf/profile {} (render! app)) enables for the dynamic extent, prints a report,
and returns the body value. Without the property set, start!/stop!/report still work but
have nothing to measure, since no p points were compiled in.
A tiny, babashka-compatible profiler — the core of Tufte (`p` profile points, a `profile`
scope, and a println report) reimplemented with only plain atoms / volatiles / dynamic vars so
it runs under SCI. (Real Tufte does NOT load under babashka: its `encore` dependency uses a
`defrecord` implementing `clojure.lang.Counted`, which SCI rejects.)
Design goals:
* **Truly zero overhead unless explicitly built in.** `p` and `profile` are *compile-time*
gated on the `fulcro.tui.perf` system property: unless it is set when the calling code is
macro-expanded, both expand to just `(do body...)` — no runtime check, no volatile read, no
trace of profiling in the compiled code. So these points are safe to leave in shipped render
code, and the shipped library (compiled without the property) carries no profiling at all.
* **No new dependencies.** Lives in `src/main` (the shipped render code calls `p` directly), but
pulls in nothing.
* **Self-time accounting.** Each `p` records both *total* (inclusive) and *self* (exclusive of
nested `p`s) time, so it is safe to instrument recursive code (e.g. the paint walker) and read
a meaningful per-id breakdown.
Usage — first build the instrumentation IN by setting the property (JVM: `-Dfulcro.tui.perf=1`;
babashka: `bb -Dfulcro.tui.perf=1 ...`), then while running the TUI:
(perf/start!) ; enable + reset
;; ... exercise the code / interact with the app ...
(perf/report) ; print the per-id table (self-time ranked)
(perf/stop!) ; disable
Or scope it: `(perf/profile {} (render! app))` enables for the dynamic extent, prints a report,
and returns the body value. Without the property set, `start!`/`stop!`/`report` still work but
have nothing to measure, since no `p` points were compiled in.A self-contained terminal abstraction for a TUI rendering target.
This namespace OWNS the normalized key-event model that downstream TUI code consumes, a pure
(JLine-free) key decoder, a Terminal protocol, a real JLine-backed implementation, and an
atom-backed fake (string-terminal) used for tests.
This is JVM/babashka only (plain .clj).
A self-contained terminal abstraction for a TUI rendering target. This namespace OWNS the normalized key-event model that downstream TUI code consumes, a pure (JLine-free) key decoder, a `Terminal` protocol, a real JLine-backed implementation, and an atom-backed fake (`string-terminal`) used for tests. This is JVM/babashka only (plain `.clj`).
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 |