Low-level drawing primitives wrapping Lanterna TextGraphics. All rendering code should use these instead of raw Java interop.
String width here means display columns the terminal will paint,
NOT Java .length() and NOT (count s). Those count UTF-16 code units;
a single emoji like 📁 is two code units but two columns,
a CJK glyph like 日 is one code unit but two columns, a flag
sequence 🇵🇱 is four code units, one grapheme,
two columns. Mix the two and your alignment drifts.
Use display-width and truncate-cols for any string that could
contain non-ASCII. Plain count/subs are still allowed for ASCII
internals (box-drawing strings we authored, single-glyph keystroke
labels, etc.) where the answer is identical and the call is a hot path.
Low-level drawing primitives wrapping Lanterna TextGraphics. All rendering code should use these instead of raw Java interop. Width-math contract =================== String width here means **display columns the terminal will paint**, NOT Java `.length()` and NOT (count s). Those count UTF-16 code units; a single emoji like 📁 is two code units but two columns, a CJK glyph like 日 is one code unit but two columns, a flag sequence 🇵🇱 is four code units, one grapheme, two columns. Mix the two and your alignment drifts. Use `display-width` and `truncate-cols` for any string that could contain non-ASCII. Plain `count`/`subs` are still allowed for ASCII internals (box-drawing strings we authored, single-glyph keystroke labels, etc.) where the answer is identical and the call is a hot path.
(align s width mode)Align s to width display columns by mode:
:left pad on the right (default for any unknown mode)
:right pad on the left
:center pad both sides
:justify full-justify the words (flush both margins).
Align `s` to `width` display columns by `mode`: :left pad on the right (default for any unknown mode) :right pad on the left :center pad both sides :justify full-justify the words (flush both margins).
(ansi-fold-cols s max-cols)Like fold-cols, but ANSI-SGR aware: char-fold s into a vector of
segments each at most max-cols display columns wide, never splitting a
grapheme cluster and never counting an \u001b[..m escape toward the
width. The SGR sequence active at a cut is RE-OPENED at the head of the
next segment (the cut segment closed with \u001b[0m), so a
syntax-highlighted line that folds keeps each token's color across the
break instead of being clipped at the bubble edge.
This is lanterna's TerminalTextUtils/ansiFoldColumns (>= 3.1.5-vis.26) —
the same grapheme/EAW-aware column engine as fold-cols/display-width;
ESC-free input takes the plain fold fast path. nil/empty returns [""].
Like `fold-cols`, but ANSI-SGR aware: char-fold `s` into a vector of segments each at most `max-cols` display columns wide, never splitting a grapheme cluster and never counting an `\u001b[..m` escape toward the width. The SGR sequence active at a cut is RE-OPENED at the head of the next segment (the cut segment closed with `\u001b[0m`), so a syntax-highlighted line that folds keeps each token's color across the break instead of being clipped at the bubble edge. This is lanterna's `TerminalTextUtils/ansiFoldColumns` (>= 3.1.5-vis.26) — the same grapheme/EAW-aware column engine as `fold-cols`/`display-width`; ESC-free input takes the plain fold fast path. nil/empty returns `[""]`.
(ansi-slice-cols s start width)Return the display-column WINDOW [start, start+width) of s as a string —
the horizontal less -S clip the code pager paints each row with (CHOP, not
fold). ANSI-SGR aware: \u001b[..m escapes never count toward a column, the
SGR active at the window's LEFT edge is RE-OPENED at the head of the result,
escapes that fall INSIDE the window are kept inline, and the result is closed
with \u001b[0m whenever any SGR was emitted — so a syntax-highlighted row
keeps its colors when scrolled sideways.
This is lanterna's TerminalTextUtils/ansiSliceColumns (>= 3.1.5-vis.26).
ESC-free input is a plain grapheme-safe column slice (never splits a
cluster). Negative start clamps to 0; non-positive width yields "".
Return the display-column WINDOW `[start, start+width)` of `s` as a string — the horizontal `less -S` clip the code pager paints each row with (CHOP, not fold). ANSI-SGR aware: `\u001b[..m` escapes never count toward a column, the SGR active at the window's LEFT edge is RE-OPENED at the head of the result, escapes that fall INSIDE the window are kept inline, and the result is closed with `\u001b[0m` whenever any SGR was emitted — so a syntax-highlighted row keeps its colors when scrolled sideways. This is lanterna's `TerminalTextUtils/ansiSliceColumns` (>= 3.1.5-vis.26). ESC-free input is a plain grapheme-safe column slice (never splits a cluster). Negative `start` clamps to 0; non-positive `width` yields "".
(ansi-truncate-cols s max-cols)ANSI-SGR-aware column TRUNCATE (hard CLIP to a prefix): keep at most max-cols
display columns of s, never splitting a grapheme and never counting an
\u001b[..m escape toward the width. Escapes are kept inline VERBATIM (no
re-open / trailing reset — unlike ansi-slice-cols); a malformed / non-SGR
control escape renders as a single middle dot so a raw ESC (0x1b) never reaches
the grapheme splitter (which throws on it, blanking the TUI before first paint).
This is lanterna's TerminalTextUtils/ansiTruncateColumns (>= 3.1.5-vis.29) —
the CHOP sibling of ansi-fold-cols/ansi-slice-cols, same grapheme/EAW-aware
column engine. nil/empty or max-cols <= 0 yields "".
ANSI-SGR-aware column TRUNCATE (hard CLIP to a prefix): keep at most `max-cols` display columns of `s`, never splitting a grapheme and never counting an `\u001b[..m` escape toward the width. Escapes are kept inline VERBATIM (no re-open / trailing reset — unlike `ansi-slice-cols`); a malformed / non-SGR control escape renders as a single middle dot so a raw ESC (0x1b) never reaches the grapheme splitter (which throws on it, blanking the TUI before first paint). This is lanterna's `TerminalTextUtils/ansiTruncateColumns` (>= 3.1.5-vis.29) — the CHOP sibling of `ansi-fold-cols`/`ansi-slice-cols`, same grapheme/EAW-aware column engine. nil/empty or `max-cols <= 0` yields "".
(blit-line! g x y line fg bg mods)Paint line at (x,y) in fg/bg with SGR mods, caching the segmented
TextCharacter[] across frames and painting it via the fork's pre-segmented
putString overload (0 alloc on a cache hit).
CONTRACT: line must already be sanitized + tab-expanded (no raw C0 controls,
tabs, or newlines) — the same guarantee the markdown projector and put-str!s
sanitize-for-lanterna provide — because the array path does NOT run
lanterna's prepareStringForPut (tab expansion / newline truncation). For
already-clean single lines it is byte-identical to .putString(String),
including double-width CJK/emoji column advance.
Paint `line` at (x,y) in fg/bg with SGR `mods`, caching the segmented `TextCharacter[]` across frames and painting it via the fork's pre-segmented `putString` overload (0 alloc on a cache hit). CONTRACT: `line` must already be sanitized + tab-expanded (no raw C0 controls, tabs, or newlines) — the same guarantee the markdown projector and `put-str!`s `sanitize-for-lanterna` provide — because the array path does NOT run lanterna's `prepareStringForPut` (tab expansion / newline truncation). For already-clean single lines it is byte-identical to `.putString(String)`, including double-width CJK/emoji column advance.
(blit-styled-line! g x y line base-fg base-bg code-fg code-bg err-fg inherited)Paint a sentinel-carrying line at (x,y), caching its fully-resolved
TextCharacter[] across frames and painting it via the fork's pre-segmented
putString overload (0 alloc on a hit). Byte-identical to the per-run
putString walk. Leaves g's SGR/color state untouched (each cell carries its
own style), so the inherited set active at entry is preserved for the
wrapping styled form -- no explicit restore needed. Shares line-cell-cache
with blit-line! via a :sty-tagged key (distinct shape, never collides).
Paint a sentinel-carrying `line` at (x,y), caching its fully-resolved `TextCharacter[]` across frames and painting it via the fork's pre-segmented `putString` overload (0 alloc on a hit). Byte-identical to the per-run `putString` walk. Leaves g's SGR/color state untouched (each cell carries its own style), so the `inherited` set active at entry is preserved for the wrapping `styled` form -- no explicit restore needed. Shares `line-cell-cache` with `blit-line!` via a `:sty`-tagged key (distinct shape, never collides).
(boxed-horiz-line widths left junction right)Return a bordered horizontal rule, e.g. ┌──┬───┐.
Return a bordered horizontal rule, e.g. `┌──┬───┐`.
(center-text s w)Center string within w terminal columns, padding both sides.
Truncates (column-aware) if too wide. Backed by the lanterna fork's
TerminalTextUtils/center (>= 3.1.5-vis.25).
Center string within `w` terminal columns, padding both sides. Truncates (column-aware) if too wide. Backed by the lanterna fork's `TerminalTextUtils/center` (>= 3.1.5-vis.25).
(clamp x lo hi)Clamp x into the inclusive range [lo, hi]. Primitive-long in and out —
the one canonical range clamp every dialog / header / provider view shares.
Delegates to the lanterna fork's TerminalTextUtils/clamp (>= 3.1.5-vis.27)
so the whole channel reuses ONE primitive range clamp.
Clamp `x` into the inclusive range [lo, hi]. Primitive-long in and out — the one canonical range clamp every dialog / header / provider view shares. Delegates to the lanterna fork's `TerminalTextUtils/clamp` (>= 3.1.5-vis.27) so the whole channel reuses ONE primitive range clamp.
(clear-styles! g)Remove all active text styles.
Remove all active text styles.
(col-prefix-end s max-cols)Return the char-index i such that (subs s 0 i) is the longest
prefix of s whose display-width is <= max-cols AND that does NOT
split a grapheme cluster.
Use this when you need both the prefix and the position of the
un-consumed remainder (e.g. in word-wrapping). For just the prefix,
truncate-cols is friendlier.
Delegates to the lanterna fork's TerminalTextUtils/columnPrefixLength
(>= 3.1.5-vis.24). Returns 0 for nil/empty or non-positive max-cols.
Return the char-index `i` such that `(subs s 0 i)` is the longest prefix of `s` whose `display-width` is <= `max-cols` AND that does NOT split a grapheme cluster. Use this when you need both the prefix and the position of the un-consumed remainder (e.g. in word-wrapping). For just the prefix, `truncate-cols` is friendlier. Delegates to the lanterna fork's `TerminalTextUtils/columnPrefixLength` (>= 3.1.5-vis.24). Returns 0 for nil/empty or non-positive `max-cols`.
(cursor-pos col row)Create a TerminalPosition for cursor placement.
Create a TerminalPosition for cursor placement.
(disable! g & modifiers)Disable one or more text styles.
Disable one or more text styles.
(display-width s)Number of terminal columns s will occupy when painted by lanterna.
Delegates to the lanterna fork's TerminalTextUtils/displayColumns
(>= 3.1.5-vis.24): grapheme clusters honoured, CJK + emoji counted as
two columns, ASCII as one, inline-span sentinels (\uE110..\uE119) as
zero, C0 control bytes sanitized to / first. This measures by the
exact same rule AbstractTextGraphics.putString paints by, so what we
measure always matches what the renderer emits.
s is coerced via (str s) first: a caller that maps display-width
over a string (handing us a Character / number) must never crash the
render thread with a ClassCastException. nil → 0.
Returns 0 for nil/empty input.
Number of terminal columns `s` will occupy when painted by lanterna. Delegates to the lanterna fork's `TerminalTextUtils/displayColumns` (>= 3.1.5-vis.24): grapheme clusters honoured, CJK + emoji counted as two columns, ASCII as one, inline-span sentinels (\uE110..\uE119) as zero, C0 control bytes sanitized to `/` first. This measures by the exact same rule `AbstractTextGraphics.putString` paints by, so what we measure always matches what the renderer emits. `s` is coerced via `(str s)` first: a caller that maps display-width over a string (handing us a Character / number) must never crash the render thread with a ClassCastException. nil → 0. Returns 0 for nil/empty input.
(dot-cell! g col row fg)Overlay a bottom-flush ▁ mark in fg colour onto the SINGLE cell at
(col,row), keeping its existing background, but ONLY when that cell is
currently a blank space OR an existing ▁ mark. ▁ (LOWER ONE EIGHTH
BLOCK) sits flush on the cell's bottom edge — the SAME row as an
underline-cell! border — so the
running tab's marching marks line up with the ready tab's underline instead
of floating one line high like a baseline . did (which read as flicker).
Only blank padding cells are touched, so the number, | separator,
label, and close ✕ (non-blank) are never clobbered — but a prior ▁ may be
RE-COLOURED, which lets a running tab paint a dim base line then overlay a
bright marching band on top. No-op off-screen or over
any non-blank glyph.
Overlay a bottom-flush `▁` mark in `fg` colour onto the SINGLE cell at (col,row), keeping its existing background, but ONLY when that cell is currently a blank space OR an existing `▁` mark. `▁` (LOWER ONE EIGHTH BLOCK) sits flush on the cell's bottom edge — the SAME row as an `underline-cell!` border — so the running tab's marching marks line up with the ready tab's underline instead of floating one line high like a baseline `.` did (which read as flicker). Only blank padding cells are touched, so the number, ` | ` separator, label, and close ✕ (non-blank) are never clobbered — but a prior `▁` may be RE-COLOURED, which lets a running tab paint a dim base line then overlay a bright marching band on top. No-op off-screen or over any non-blank glyph.
(draw-box! g left top w h)Draw a single-line bordered box at (left, top) of size wxh. Draws corners, edges. Does NOT fill interior.
Draw a single-line bordered box at (left, top) of size wxh. Draws corners, edges. Does NOT fill interior.
(draw-centered! g left row width text)Draw text centered at row within [left, left+width).
Draw text centered at row within [left, left+width).
(draw-selection-marker! g col row selected?)(draw-selection-marker! g col row selected? marker-fg)Paint the selection marker at (col, row) when selected? is
truthy. Unselected rows get nothing — the surrounding row fill is
expected to already cover those cells.
The glyph is rendered BOLD. Provide marker-fg to give it its
own color (e.g. dialog-hint-key); when omitted, whatever fg is
currently set on g is used.
See the Selection marker block above for the project-wide
rationale. Callers that need the prefix as a STRING (to inline
into a row label) should use selection-prefix instead.
Paint the selection marker at (col, row) when `selected?` is truthy. Unselected rows get nothing — the surrounding row fill is expected to already cover those cells. The glyph is rendered BOLD. Provide `marker-fg` to give it its own color (e.g. `dialog-hint-key`); when omitted, whatever fg is currently set on `g` is used. See the `Selection marker` block above for the project-wide rationale. Callers that need the prefix as a STRING (to inline into a row label) should use `selection-prefix` instead.
(draw-separator! g left right row)Draw a horizontal separator with T-junctions at left/right edges.
Draw a horizontal separator with T-junctions at left/right edges.
(draw-space-around! g left row width items)Draw items spread across row within [left, left+width) with space-around.
Draw items spread across row within [left, left+width) with space-around.
(draw-space-between! g left row width items)Draw items spread across row within [left, left+width) with space-between.
Draw items spread across row within [left, left+width) with space-between.
(draw-tabs! g
tabs
{:keys [left row width active-id gap fg bg active-fg active-bg
inactive-fg inactive-bg bordered?]})Draw a tab strip and return its geometry.
Required opts: :left, :row, :width, :active-id, :fg, :bg,
:active-fg, :active-bg, :inactive-fg, :inactive-bg.
Optional: :gap, :bordered?.
This primitive knows layout and drawing only. Callers own domain actions and click-region registration.
Draw a tab strip and return its geometry. Required opts: `:left`, `:row`, `:width`, `:active-id`, `:fg`, `:bg`, `:active-fg`, `:active-bg`, `:inactive-fg`, `:inactive-bg`. Optional: `:gap`, `:bordered?`. This primitive knows layout and drawing only. Callers own domain actions and click-region registration.
(ellipsize s max-cols)(ellipsize s max-cols marker)Shorten s to at most max-cols terminal columns, appending an ellipsis
marker (default …) when truncation happens. Grapheme-cluster + EAW safe
and WIDTH-CORRECT — the marker's own display width is charged against the
budget, so the result never exceeds max-cols (even for tiny budgets, where
the marker itself is truncated to fit). nil s → treated as empty;
max-cols <= 0 → ""; a string already within budget is returned as-is.
The single canonical column-ellipsis for the TUI — dialogs/table/header
delegate here. Backed by the lanterna fork's TerminalTextUtils/ellipsize
(>= 3.1.5-vis.24).
Shorten `s` to at most `max-cols` terminal columns, appending an ellipsis `marker` (default `…`) when truncation happens. Grapheme-cluster + EAW safe and WIDTH-CORRECT — the marker's own display width is charged against the budget, so the result never exceeds `max-cols` (even for tiny budgets, where the marker itself is truncated to fit). nil `s` → treated as empty; `max-cols <= 0` → `""`; a string already within budget is returned as-is. The single canonical column-ellipsis for the TUI — `dialogs`/`table`/`header` delegate here. Backed by the lanterna fork's `TerminalTextUtils/ellipsize` (>= 3.1.5-vis.24).
(enable! g & modifiers)Enable one or more text styles. modifiers are SGR constants (BOLD, ITALIC, etc.).
Enable one or more text styles. `modifiers` are SGR constants (BOLD, ITALIC, etc.).
(expand-tabs s)Replace every hard TAB in s with spaces up to the next tab-width tab
stop, counted from the START of s (column 0).
WHY: the fork's putString expands tabs to fixed 4-column stops at PAINT
time, but the width measure / soft-wrap (display-width / fold-cols,
which sanitize a TAB to a single /) count each tab as ONE column. So
-bearing tool output (e.g. gh issue list, any tab-separated columns)
was folded as if tabs were 1 column wide, then OVERFLOWED the bubble's
right edge once paint re-expanded them. Expanding up front makes
measure == fold == paint and leaves putString no tabs to re-expand.
Column tracking is by char count (ASCII tool output); nil/empty → "".
Tab-free input is returned unchanged (same instance). Backed by the
lanterna fork's TerminalTextUtils/expandTabs (>= 3.1.5-vis.30).
Replace every hard TAB in `s` with spaces up to the next `tab-width` tab stop, counted from the START of `s` (column 0). WHY: the fork's `putString` expands tabs to fixed 4-column stops at PAINT time, but the width measure / soft-wrap (`display-width` / `fold-cols`, which sanitize a TAB to a single `/`) count each tab as ONE column. So ` `-bearing tool output (e.g. `gh issue list`, any tab-separated columns) was folded as if tabs were 1 column wide, then OVERFLOWED the bubble's right edge once paint re-expanded them. Expanding up front makes measure == fold == paint and leaves `putString` no tabs to re-expand. Column tracking is by char count (ASCII tool output); nil/empty → "". Tab-free input is returned unchanged (same instance). Backed by the lanterna fork's `TerminalTextUtils/expandTabs` (>= 3.1.5-vis.30).
(fill-rect! g col row w h)(fill-rect! g col row w h ch)Fill a rectangle at (col, row) of size wxh with the given char (default space).
Fill a rectangle at (col, row) of size wxh with the given char (default space).
(fold-cols s max-cols)Character-fold s into a vector of segments, each at most max-cols
display columns wide, never splitting a grapheme cluster.
This is a terminal-style SOFT WRAP, the soft-wrap primitive shared by
the agent code rail and the tool-result code blocks: unlike word-wrap it
never drops or reflows whitespace — the bytes are preserved exactly and a
break is inserted only at the column boundary. So a pathologically wide
single line (a one-line git_commit message arg, a wide clj_eval
value map) folds at the bubble edge instead of overflowing or being
clipped, while indentation and in-row column alignment survive.
The fold itself is lanterna's TerminalTextUtils/foldColumns — the same
grapheme/EAW-aware text-flow family the screen paints with (displayWidth
/ wordWrap / justify). A line already within budget comes back [s]
unchanged, so normal multi-line source is untouched and only the over-wide
rows fold. nil/empty returns [""].
Input carrying an ESC (0x1b) is returned unfolded — foldColumns is
plain-text-only (the grapheme splitter throws on ESC); callers that need
ANSI-aware width clip such rows separately.
Character-fold `s` into a vector of segments, each at most `max-cols` display columns wide, never splitting a grapheme cluster. This is a terminal-style SOFT WRAP, the soft-wrap primitive shared by the agent code rail and the tool-result code blocks: unlike word-wrap it never drops or reflows whitespace — the bytes are preserved exactly and a break is inserted only at the column boundary. So a pathologically wide single line (a one-line `git_commit` message arg, a wide `clj_eval` value map) folds at the bubble edge instead of overflowing or being clipped, while indentation and in-row column alignment survive. The fold itself is lanterna's `TerminalTextUtils/foldColumns` — the same grapheme/EAW-aware text-flow family the screen paints with (`displayWidth` / `wordWrap` / `justify`). A line already within budget comes back `[s]` unchanged, so normal multi-line source is untouched and only the over-wide rows fold. nil/empty returns `[""]`. Input carrying an ESC (0x1b) is returned unfolded — `foldColumns` is plain-text-only (the grapheme splitter throws on ESC); callers that need ANSI-aware width clip such rows separately.
(horiz-line n)Return a string of n horizontal box-drawing chars. Delegates to lanterna's
primitive terminal-rule builder instead of rebuilding via Clojure seqs.
Return a string of `n` horizontal box-drawing chars. Delegates to lanterna's primitive terminal-rule builder instead of rebuilding via Clojure seqs.
(inline-sentinel? g)True when g (a single grapheme String) is one of the eight inline
span sentinels above. Cheap range check, no map lookup.
True when `g` (a single grapheme String) is one of the eight inline span sentinels above. Cheap range check, no map lookup.
(invalidate-line-cells!)Drop every cached segmented line. Safe to call anytime (pure-function cache — worst case is recompute). Wired to the same settings/theme busts as the other caches so a color/width change can't paint a stale array.
Drop every cached segmented line. Safe to call anytime (pure-function cache — worst case is recompute). Wired to the same settings/theme busts as the other caches so a color/width change can't paint a stale array.
(joined-horiz-line widths junction)Return horizontal runs joined by junction, e.g. ──┬───.
Return horizontal runs joined by `junction`, e.g. `──┬───`.
(justify s width)(justify s width justify-last?)Word-wrap s to width columns, then FULL-JUSTIFY every line, leaving each
paragraph's final line left-aligned (the standard typographic convention).
Pass justify-last? true to stretch the final line too. Returns a vec of
lines.
Word-wrap `s` to `width` columns, then FULL-JUSTIFY every line, leaving each paragraph's final line left-aligned (the standard typographic convention). Pass `justify-last?` true to stretch the final line too. Returns a vec of lines.
(justify-line line width)Full-justify the words on line to EXACTLY width display columns by
distributing inter-word spaces (flush to both margins). A blank or
single-word line is left-aligned (right-padded) instead — nothing to
stretch.
Full-justify the words on `line` to EXACTLY `width` display columns by distributing inter-word spaces (flush to both margins). A blank or single-word line is left-aligned (right-padded) instead — nothing to stretch.
(pad-left s w)Pad string to w terminal columns, left-filling with spaces.
Truncates (column-aware) if too wide. Backed by the lanterna fork's
TerminalTextUtils/padLeft (>= 3.1.5-vis.25).
Pad string to `w` terminal columns, left-filling with spaces. Truncates (column-aware) if too wide. Backed by the lanterna fork's `TerminalTextUtils/padLeft` (>= 3.1.5-vis.25).
(pad-right s w)Pad string to w terminal columns, right-filling with spaces.
Truncates (column-aware) if too wide. Backed by the lanterna fork's
TerminalTextUtils/padRight (>= 3.1.5-vis.25).
Pad string to `w` terminal columns, right-filling with spaces. Truncates (column-aware) if too wide. Backed by the lanterna fork's `TerminalTextUtils/padRight` (>= 3.1.5-vis.25).
(paint-styled-line! g x y line base-fg base-bg code-fg code-bg)(paint-styled-line! g x y line base-fg base-bg code-fg code-bg err-fg)Paint a single line at (x, y) honouring inline span sentinels.
The line may contain interleaved text and INLINE_*_ON/OFF
sentinels (range \uE110..\uE11B). Sentinels themselves are NEVER
painted; they toggle the SGR style (BOLD/ITALIC/CROSSED-OUT/UNDERLINE),
the fg/bg colors (CODE), or the foreground to err-fg (ERR) for the
spans that follow.
base-fg / base-bg are the colors for non-code, non-error spans.
Code spans force code-fg / code-bg until INLINE_CODE_OFF; ERR spans
force err-fg (on base-bg) until INLINE_ERR_OFF. The 8-arg arity
defaults err-fg to base-fg, so an ERR sentinel is a no-op unless the
caller opts in with the 9-arg arity.
Robustness: walks by grapheme cluster, inherits any SGR modifiers active
on g at entry, tolerates dangling sentinels, and collapses to a single
cached blit when the line carries no sentinel (the common ASCII case).
Paint a single line at (x, y) honouring inline span sentinels. The line may contain interleaved text and `INLINE_*_ON`/`OFF` sentinels (range \uE110..\uE11B). Sentinels themselves are NEVER painted; they toggle the SGR style (BOLD/ITALIC/CROSSED-OUT/UNDERLINE), the fg/bg colors (CODE), or the foreground to `err-fg` (ERR) for the spans that follow. `base-fg` / `base-bg` are the colors for non-code, non-error spans. Code spans force `code-fg` / `code-bg` until INLINE_CODE_OFF; ERR spans force `err-fg` (on `base-bg`) until INLINE_ERR_OFF. The 8-arg arity defaults `err-fg` to `base-fg`, so an ERR sentinel is a no-op unless the caller opts in with the 9-arg arity. Robustness: walks by grapheme cluster, inherits any SGR modifiers active on `g` at entry, tolerates dangling sentinels, and collapses to a single cached blit when the line carries no sentinel (the common ASCII case).
(put-str! g col row text)Draw a string at (col, row). Control characters are sanitized.
Draw a string at (col, row). Control characters are sanitized.
(selection-prefix selected?)Return the leading marker string for a list/menu row.
Use this for rows where the marker can be inlined into the body
text (simple list items, checkbox rows, slash-command rows). For
rows where the marker must live OUTSIDE a fixed-column body
(file/session pickers, provider/model cards) call
draw-selection-marker! from the caller's row loop instead.
Return the leading marker string for a list/menu row. Use this for rows where the marker can be inlined into the body text (simple list items, checkbox rows, slash-command rows). For rows where the marker must live OUTSIDE a fixed-column body (file/session pickers, provider/model cards) call `draw-selection-marker!` from the caller's row loop instead.
Two-col selection marker. Selected rows show •+space, unselected
rows show two spaces, so the body content stays column-aligned.
The glyph MUST be display-width 1 so glyph+space == 2 cols and
exactly fills the reserved gutter. • is the project-wide selector
dot, kept bare (no VS-16) to avoid terminal width surprises.
Two-col selection marker. Selected rows show `•`+space, unselected rows show two spaces, so the body content stays column-aligned. The glyph MUST be display-width 1 so glyph+space == 2 cols and exactly fills the reserved gutter. `•` is the project-wide selector dot, kept bare (no VS-16) to avoid terminal width surprises.
(set-char! g col row ch)Draw a single character at (col, row).
Draw a single character at (col, row).
(set-colors! g fg bg)Set both foreground and background color in one call.
Set both foreground and background color in one call.
(space-around items w)Distribute items across w terminal columns with equal space
around each item. Like CSS justify-content: space-around. Backed by the
lanterna fork's TerminalTextUtils/spaceAround (>= 3.1.5-vis.25).
Distribute items across `w` terminal columns with equal space around each item. Like CSS justify-content: space-around. Backed by the lanterna fork's `TerminalTextUtils/spaceAround` (>= 3.1.5-vis.25).
(space-between items w)Distribute items across w terminal columns with equal gaps.
First item flush-left, last item flush-right, rest evenly spaced.
Like CSS justify-content: space-between. Backed by the lanterna fork's
TerminalTextUtils/spaceBetween (>= 3.1.5-vis.25).
Distribute items across `w` terminal columns with equal gaps. First item flush-left, last item flush-right, rest evenly spaced. Like CSS justify-content: space-between. Backed by the lanterna fork's `TerminalTextUtils/spaceBetween` (>= 3.1.5-vis.25).
(status-mark! g col row glyph fg bg)Paint a 1-col status glyph in colour fg on bg at (col,row) and return
the next col (col + STATUS_WIDTH) so the caller can place the label right
after. The reusable mark behind settings rows + resource rows.
Paint a 1-col status `glyph` in colour `fg` on `bg` at (col,row) and return the next col (`col + STATUS_WIDTH`) so the caller can place the label right after. The reusable mark behind settings rows + resource rows.
(styled g styles & body)Draw with styles temporarily enabled. Restores previous styles after body. Usage: (styled g [BOLD ITALIC] (put-str! g 0 0 "hello"))
Draw with styles temporarily enabled. Restores previous styles after body. Usage: (styled g [BOLD ITALIC] (put-str! g 0 0 "hello"))
(tab-at layout col)Return the tab geometry under col, or nil. Expects tab-layout output.
Return the tab geometry under `col`, or nil. Expects `tab-layout` output.
(tab-display-label {:keys [id label dirty? state]})Compact label for a tab map.
Supported tab keys:
:label / :id - visible base label.:dirty? - appends a dirty marker.:state - appends a compact state badge for running / verified /
accepted / error tabs.This is terminal-column aware when later passed through tab-layout, so
callers may use emoji or CJK labels without hand-padding.
Compact label for a tab map. Supported tab keys: - `:label` / `:id` - visible base label. - `:dirty?` - appends a dirty marker. - `:state` - appends a compact state badge for running / verified / accepted / error tabs. This is terminal-column aware when later passed through `tab-layout`, so callers may use emoji or CJK labels without hand-padding.
(tab-layout tabs left width active-id)(tab-layout tabs left width active-id {:keys [gap]})Return tab geometry for a horizontal tab strip.
tabs is a seq of maps. Returned maps include :left, :width,
:active?, and :text truncated to fit :width. Width math uses terminal
columns, not Java chars. When the strip is too narrow, later tabs may get
zero width; draw helpers skip those safely.
Return tab geometry for a horizontal tab strip. `tabs` is a seq of maps. Returned maps include `:left`, `:width`, `:active?`, and `:text` truncated to fit `:width`. Width math uses terminal columns, not Java chars. When the strip is too narrow, later tabs may get zero width; draw helpers skip those safely.
Columns the lanterna fork's putString advances a hard TAB to. The fork
expands tabs to FIXED tab stops at paint time; kept in sync here so tab
expansion done before layout matches what the painter would emit.
Columns the lanterna fork's `putString` advances a hard TAB to. The fork expands tabs to FIXED tab stops at paint time; kept in sync here so tab expansion done before layout matches what the painter would emit.
(truncate-cols s max-cols)Return the longest prefix of s that fits in at most max-cols
terminal columns, never splitting a grapheme cluster.
Edge cases honoured:
max-cols <= 0 returns "".display-width is exactly max-cols. This keeps
pad-right / pad-left idempotent under repeated truncation.Delegates to the lanterna fork's TerminalTextUtils/truncateColumns
(>= 3.1.5-vis.24).
Return the longest prefix of `s` that fits in at most `max-cols` terminal columns, never splitting a grapheme cluster. Edge cases honoured: - nil or `max-cols <= 0` returns `""`. - If a double-width grapheme would straddle the cut, it is dropped (NOT half-included), and one space is appended in its place so the returned string's `display-width` is exactly `max-cols`. This keeps `pad-right` / `pad-left` idempotent under repeated truncation. - Trailing zero-width inline-span sentinels (style closers) are always emitted, never stranded past the cut. Delegates to the lanterna fork's `TerminalTextUtils/truncateColumns` (>= 3.1.5-vis.24).
(truncate-middle s max-cols)Shorten s to at most max-cols columns by ELIDING THE MIDDLE behind a
single …, keeping both the HEAD and the TAIL. Ideal for file paths, where
the basename (tail) is as informative as the leading dirs — plain
truncate-cols drops the filename. Falls back to head truncation when there
isn't room for both sides plus the ellipsis. Grapheme-cluster safe.
Backed by the lanterna fork's TerminalTextUtils/truncateMiddle
(>= 3.1.5-vis.25).
Shorten `s` to at most `max-cols` columns by ELIDING THE MIDDLE behind a single `…`, keeping both the HEAD and the TAIL. Ideal for file paths, where the basename (tail) is as informative as the leading dirs — plain `truncate-cols` drops the filename. Falls back to head truncation when there isn't room for both sides plus the ellipsis. Grapheme-cluster safe. Backed by the lanterna fork's `TerminalTextUtils/truncateMiddle` (>= 3.1.5-vis.25).
(underline-cell! g col row)(underline-cell! g col row fg)Add the UNDERLINE modifier to the SINGLE already-painted cell at (col,row),
preserving its glyph + background. Reads the cell back via getCharacter
and re-sets it with the modifier folded in, so callers can paint a
PER-COLUMN border (a steady / marching underline) that the global
enable!/UNDERLINE attribute — which would underline every cell touched
after it — can't express. No-op when the cell is empty/off-screen.
3-arity keeps the cell's own foreground (the underline inherits it).
4-arity RECOLOURS the foreground to fg first, so the underline — which a
terminal draws in the cell's foreground colour — renders in fg. Recolour
is uniform across blank padding AND glyph cells, giving ONE consistent
underline shape/weight everywhere (no block-glyph vs SGR-line mismatch).
Add the UNDERLINE modifier to the SINGLE already-painted cell at (col,row), preserving its glyph + background. Reads the cell back via `getCharacter` and re-sets it with the modifier folded in, so callers can paint a PER-COLUMN border (a steady / marching underline) that the global `enable!`/`UNDERLINE` attribute — which would underline every cell touched after it — can't express. No-op when the cell is empty/off-screen. 3-arity keeps the cell's own foreground (the underline inherits it). 4-arity RECOLOURS the foreground to `fg` first, so the underline — which a terminal draws in the cell's foreground colour — renders in `fg`. Recolour is uniform across blank padding AND glyph cells, giving ONE consistent underline shape/weight everywhere (no block-glyph vs SGR-line mismatch).
(v-center-offset content-h container-h)Compute vertical offset to center content-h rows within container-h rows.
Backed by the lanterna fork's TerminalTextUtils/verticalCenterOffset
(>= 3.1.5-vis.25).
Compute vertical offset to center `content-h` rows within `container-h` rows. Backed by the lanterna fork's `TerminalTextUtils/verticalCenterOffset` (>= 3.1.5-vis.25).
(with-style g styles body-fn)Execute body-fn (fn [g] ...) with the given styles enabled, then restore.
Returns the result of body-fn.
Execute `body-fn` (fn [g] ...) with the given styles enabled, then restore. Returns the result of `body-fn`.
(word-wrap s width)Greedy word-wrap s into a vec of lines, each fitting width DISPLAY
columns. Breaks on whitespace; a token wider than width is hard-split at
grapheme boundaries (never mid-cluster); embedded newlines are honoured.
Blank input or width <= 0 yields [""].
Greedy word-wrap `s` into a vec of lines, each fitting `width` DISPLAY columns. Breaks on whitespace; a token wider than `width` is hard-split at grapheme boundaries (never mid-cluster); embedded newlines are honoured. Blank input or `width` <= 0 yields `[""]`.
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 |