TUI walker over canonical answer-IR (com.blockether.vis.internal.render/->ast).
Pure data: IR -> vector of lines. Each line is a vector of styled runs. The screen layer turns runs into ANSI / Lanterna cells.
Why a dedicated walker (and not ir/render :plain):
Canonical-IR invariants we rely on (see internal.render docstring):
:span / raw bodies of :code / :c / :kbd;:span;[:br {}];Run shape: {:text String ; never empty, never contains '\n' :style #{:bold :italic :code :dim :link :heading :marker :quote} :href String? ; present iff :link in style :node any?} ; opaque node identity for click/select
Public API: (ir->lines ir width) ; total walker (ir->lines ir width opts) Opts: :heading-prefix? bool ; render '#'-style markers (default false; bold suffices) :code-fence? bool ; render ``` lines around code blocks (default false) :max-lines int ; hard cap (default unlimited)
TUI walker over canonical answer-IR (`com.blockether.vis.internal.render/->ast`).
Pure data: IR -> vector of lines. Each line is a vector of styled
runs. The screen layer turns runs into ANSI / Lanterna cells.
Why a dedicated walker (and not `ir/render :plain`):
- We need styling metadata per character run (bold / italic / inline
code background / link target) so the TUI can paint colours and
attach click + selection regions.
- We need word-wrap at an arbitrary terminal width with hanging
indent inside lists and quotes.
- We need stable per-node identifiers so selection / hover / click
can map a glyph back to its IR node.
Canonical-IR invariants we rely on (see internal.render docstring):
- text only inside `:span` / raw bodies of `:code` / `:c` / `:kbd`;
- no '\n' inside `:span`;
- hard breaks are explicit `[:br {}]`;
- attrs map present on every vector node.
Run shape:
{:text String ; never empty, never contains '\n'
:style #{:bold :italic :code :dim :link :heading :marker :quote}
:href String? ; present iff :link in style
:node any?} ; opaque node identity for click/select
Public API:
(ir->lines ir width) ; total walker
(ir->lines ir width opts)
Opts:
:heading-prefix? bool ; render '#'-style markers (default false; bold suffices)
:code-fence? bool ; render ``` lines around code blocks (default false)
:max-lines int ; hard cap (default unlimited)(ir->entries ir width)(ir->entries ir width opts)Drop-in replacement for the legacy render/markdown->entries.
Returns a vector of {:line :meta} maps where :line is the
sentinel-prefixed string the bubble painter consumes, and :meta
is per-line click-region metadata. nil :meta for ordinary content
lines.
:mode selects the marker set:
:answer (default) — answer-zone PUA chars (answer-bg paint)
:thinking — thinking-zone PUA chars (iter-header-bg + italic)
:channel — in-place channel/tool IR; plain paragraphs use no
background marker, structural rows keep explicit styling
This is the IR-side analogue of markdown->entries. Every
bubble rendering path that used to parse the rendered markdown
back into entries should call this directly on the canonical IR
— no markdown round-trip.
Drop-in replacement for the legacy `render/markdown->entries`.
Returns a vector of `{:line :meta}` maps where `:line` is the
sentinel-prefixed string the bubble painter consumes, and `:meta`
is per-line click-region metadata. `nil` `:meta` for ordinary content
lines.
`:mode` selects the marker set:
`:answer` (default) — answer-zone PUA chars (answer-bg paint)
`:thinking` — thinking-zone PUA chars (iter-header-bg + italic)
`:channel` — in-place channel/tool IR; plain paragraphs use no
background marker, structural rows keep explicit styling
This is the IR-side analogue of `markdown->entries`. Every
bubble rendering path that used to parse the rendered markdown
back into entries should call this directly on the canonical IR
— no markdown round-trip.(ir->inline-sentinel-string ir)Flatten a canonical IR into a single sentinel-wrapped inline string
suitable for chrome-row labels. NO block markers — just inline
sentinels (INLINE_BOLD_ON/OFF, INLINE_CODE_ON/OFF, etc.) wrapping
styled runs. Hard breaks [:br] become spaces (single line
contract). Used by chrome labels and other surfaces that previously
ran through markdown->inline.
Flatten a canonical IR into a single sentinel-wrapped inline string suitable for chrome-row labels. NO block markers — just inline sentinels (`INLINE_BOLD_ON/OFF`, `INLINE_CODE_ON/OFF`, etc.) wrapping styled runs. Hard breaks `[:br]` become spaces (single line contract). Used by chrome labels and other surfaces that previously ran through `markdown->inline`.
(ir->lines input width)(ir->lines input width opts)Walk canonical IR (or any input that ir/->ast accepts) at a given
terminal width and return a vector of styled lines.
Walk canonical IR (or any input that `ir/->ast` accepts) at a given terminal `width` and return a vector of styled lines.
(ir->lines-tail input width tail-n)(ir->lines-tail input width tail-n opts)Render only the last tail-n styled lines of the IR.
Walks the top-level blocks BACKWARD, accumulating a cheap
per-block line estimate (text-chars / content-width), until
accumulated estimate ≥ tail-n * 2 (slack covers blank-collapse
ir->lines and (take-last tail-n).Per-frame cost = O(visible-tail), independent of total body
length. The semantically-correct path for an auto-scrolled
tail-pinned bubble (vs the broken :max-lines cap which would
give the FIRST tail-n).
tail-n must be positive. If the body has fewer total estimated
lines than the budget, this falls back to a full walk.
Render only the last `tail-n` styled lines of the IR. Walks the top-level blocks BACKWARD, accumulating a cheap per-block line estimate (text-chars / content-width), until accumulated estimate ≥ `tail-n * 2` (slack covers blank-collapse + per-block trailing blanks). Then renders ONLY those tail blocks via the normal `ir->lines` and `(take-last tail-n)`. Per-frame cost = O(visible-tail), independent of total body length. The semantically-correct path for an auto-scrolled tail-pinned bubble (vs the broken `:max-lines` cap which would give the FIRST tail-n). `tail-n` must be positive. If the body has fewer total estimated lines than the budget, this falls back to a full walk.
(ir->lines-window input width start num)(ir->lines-window input width start num opts)Render only rows [start, start+num) of the IR.
Internally walks blocks forward with :max-lines (start + num + slack) so the walker short-circuits via the A1 blocks->lines
path - work is O(start + num), not O(body). Output is
bit-identical to (subvec (ir->lines ir w opts) start (+ start num)) when both are in range; result is clamped if the body has
fewer lines.
Use cases:
start = 0: equivalent to (:max-lines num) head-cap.start > 0: scrolled into a long bubble; only render the
window. Note: skipping is still done by the walker producing
start lines then discarding them - true zero-cost prefix
skip would require per-block estimate-based skipping which
this fn does not do.num must be positive. start must be non-negative.
Render only rows `[start, start+num)` of the IR.
Internally walks blocks forward with `:max-lines (start + num +
slack)` so the walker short-circuits via the A1 `blocks->lines`
path - work is O(start + num), not O(body). Output is
bit-identical to `(subvec (ir->lines ir w opts) start (+ start
num))` when both are in range; result is clamped if the body has
fewer lines.
Use cases:
- `start = 0`: equivalent to `(:max-lines num)` head-cap.
- `start > 0`: scrolled into a long bubble; only render the
window. Note: skipping is still done by the walker producing
`start` lines then discarding them - true zero-cost prefix
skip would require per-block estimate-based skipping which
this fn does not do.
`num` must be positive. `start` must be non-negative.(ir->sentinel-strings ir width)(ir->sentinel-strings ir width opts)One-shot helper: canonical IR → vector of sentinel-prefixed strings
ready for the existing bubble painter. Composes ir->lines with
the sentinel adapter. :mode (:answer, :thinking, or :channel)
selects the marker set.
One-shot helper: canonical IR → vector of sentinel-prefixed strings ready for the existing bubble painter. Composes `ir->lines` with the sentinel adapter. `:mode` (`:answer`, `:thinking`, or `:channel`) selects the marker set.
(lines->plain lines)Concatenate the text of every run in lines. Useful for tests +
clipboard fallback (preferred clipboard path: ir/render :markdown).
Concatenate the text of every run in `lines`. Useful for tests + clipboard fallback (preferred clipboard path: `ir/render :markdown`).
(lines->sentinel-strings lines)(lines->sentinel-strings lines opts)Convert walker output (vector of {:runs :block-tag :block-level?}
maps) into the painter's sentinel-prefixed string contract. Each
line: <block-marker><inline-sentinel-wrapped body>. :mode
selects the marker set (:answer default, :thinking, or :channel).
Convert walker output (vector of `{:runs :block-tag :block-level?}`
maps) into the painter's sentinel-prefixed string contract. Each
line: `<block-marker><inline-sentinel-wrapped body>`. `:mode`
selects the marker set (`:answer` default, `:thinking`, or `:channel`).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 |